Add `npm run format:check` step to node.js.yml so PRs fail if any
file is unformatted. Also run a one-time full-codebase format to
clear accumulated drift (13 files) so the check starts green.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When using a Miyo remote server, the vault may be mounted at a
different path on the remote machine. This adds an optional
"Remote Vault Folder" setting that overrides the folder path sent
to Miyo when a custom server URL is configured.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(miyo): disable miyo on mobile without a remote server URL
Adds `shouldUseMiyo()` to `miyoUtils.ts` as the single source of truth
for whether Miyo should be active. On mobile, Miyo is silently ignored
unless an explicit remote server URL is configured, since local service
discovery is unavailable on mobile. The three previously duplicated
`shouldUseMiyo` checks in RetrieverFactory, findRelevantNotes, and
VectorStoreManager now delegate to this shared function.
Also drops the redundant `enableSemanticSearchV3` guard — the UI already
enforces that enabling Miyo enables semantic search and disabling semantic
search disables Miyo.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(miyo): align RelevantNotes UI check with shouldUseMiyo
Replaces the local `shouldUseMiyoIndex` predicate (which duplicated the
self-host grace-period logic and missed the mobile platform guard) with a
direct call to `shouldUseMiyo` from miyoUtils. This ensures the UI's
index-state check matches what the backend actually uses, so the
"Build Index"/"No relevant notes" state is always correct on mobile.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(miyo): update findRelevantNotes mock to use shouldUseMiyo
Replace the isSelfHostAccessValid mock with shouldUseMiyo, which is now
the single gate checked by findRelevantNotes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(miyo): rename vault name to remote vault path and align MiyoClient with server API
- Rename `miyoVaultName` setting to `miyoRemoteVaultPath` with migration from old key
- Rename UI field "Vault Name" → "Remote Vault Path (Optional)" and "Custom Miyo Server URL" → "Remote Miyo Server URL (Optional)"
- Always show Remote Vault Path field (not gated by server URL); both fields default to blank with no placeholder
- Show bot name indicator (remote vault path + local/remote label) under Enable Miyo when enabled
- Rename `sourceId` parameters in MiyoClient public methods to `vault` to match the actual server API field name
- Update `searchRelated` options property from `sourceId` to `vault`
- Update call sites in findRelevantNotes.ts accordingly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(miyo): remote vault path saves silently without triggering re-index
It is a remote-only identifier — changing it has no effect on the
local index, so no confirmation modal or re-indexing is needed.
Also drops the now-unused logWarn import.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(miyo): remote vault path only overrides when remote server URL is also set
getMiyoSourceId now requires both miyoRemoteVaultPath and miyoServerUrl to be
set before using the remote path — if only the path is set (no server URL),
it falls back to the auto-detected local vault path. The settings indicator
applies the same logic.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(miyo): rename getMiyoSourceId → getMiyoVault, resolveMiyoSourceId → resolveMiyoVault
Removes the last "sourceId" terminology — all Miyo identifiers now consistently
use the "vault" name that the server API expects.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(miyo): only show remote vault path indicator when remote server URL is set
Avoids showing the auto-detected local vault path under a "Remote vault path"
label, which was misleading when Miyo was running locally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(miyo): replace custom remote vault path input with plain text field
Removes the Apply button, pending state, useEffect, and handler — the field
now saves with the standard debounced onChange, matching the URL field above it.
Also drops the unused Input and useEffect imports.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(miyo): show effective vault path under Enable Miyo when enabled
Displays the auto-detected local vault path when no remote server is
configured, or the remote vault path override when both remote URL and
remote vault path are set.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(test): update findRelevantNotes test to use vault instead of sourceId
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The release agent now fetches and reads every PR's description before
writing release notes, instead of relying on titles alone. This
produces more accurate, user-facing release notes.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(openrouter): add per-model toggle for prompt caching
OpenRouter's Zero Data Retention (ZDR) endpoints do not support
Anthropic's automatic prompt caching (cache_control), causing 404
errors. This adds an `enablePromptCaching` option (default: true)
so users can disable it per model in settings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(openrouter): show question mark tooltip for prompt caching toggle
Display a visible HelpCircle icon next to the label instead of making
the label itself the hover target. Tooltip text: "Disable if your
OpenRouter endpoint uses Zero Data Retention (ZDR)..."
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: move prompt caching toggle to top-level model settings
The toggle was nested inside the advanced parameters section. Move it
to the top level alongside Base URL and API Key so it's immediately
visible to OpenRouter users.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(ui): simplify prompt caching to single checkbox with tooltip
Remove the redundant FormField heading — now just a checkbox labeled
"Prompt Caching" with a ? tooltip explaining ZDR.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: prompt caching applies to multiple providers, not just Anthropic
OpenRouter's cache_control supports OpenAI, Anthropic, DeepSeek,
Gemini, Grok, and others. Remove "Anthropic" from tooltip and docs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(ui): show enabled models without API keys as disabled in dropdown
Previously, enabled models without API keys were hidden from the chat
input model dropdown, making it confusing since "Enable" in settings
didn't mean the model would appear. Now they show as greyed-out disabled
items with a "Needs API key" label.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: reorder BUILTIN_CHAT_MODELS so enabled models are listed first
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(miyo): add customizable vault name setting for Miyo indexing
Allows users to set a custom vault name that Miyo uses as the source ID
for indexing, instead of the auto-detected vault path. When changed while
Miyo is enabled, clears the old index and triggers a full re-index under
the new name. Includes guidance for remote/multi-device setups to use a
consistent vault name across devices.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(miyo): compare resolved source IDs before clearing index on vault name change
Avoids unnecessary index clear and full re-index when the user types the
auto-detected vault path (the value that the blank field resolves to),
since the effective Miyo source ID is unchanged in that case.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(miyo): prevent applying an empty vault name
The Apply button is hidden when the input is blank, and the handler
guards against an empty string, so users cannot accidentally clear
the vault name and submit it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(miyo): allow clearing vault name to restore auto-detected vault path
An empty vault name is valid — it falls back to the auto-detected vault
path as the Miyo source ID. Removes the empty-string guard so users can
clear a previously set name and revert to the default behaviour.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(editFile): write-first with inline diff in chat
Replace the blocking accept/reject modal in editFile with a write-first
approach: changes are written to disk immediately, and a unified diff is
returned inline in the chat as a fenced \`\`\`diff block. Multiple
editFile calls in a single agent turn each produce their own diff
without requiring any user interaction.
Also cleans up associated rename/refactor of writeToFileTool →
writeFileTool across chain runner, streamer, and related tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(editFile): add Show Diff button and side-by-side diff view in ApplyView
- Pipe editFileDiffs through message types (ChatMessage, StoredMessage) and
MessageRepository so writeFile/editFile diffs are stored as a typed field
(same pattern as sources)
- Collect diffs in AutonomousAgentChainRunner.runReActLoop() by parsing the
JSON result of successful writeFile/editFile tool calls, then pass through
ReActLoopResult → handleResponse() → ChatMessage.editFileDiffs
- ChatSingleMessage reads message.editFileDiffs directly (instead of trying
to parse TOOL_CALL markers which don't exist in agent mode)
- ChatButtons shows a "Show diff" button when editFileDiffs is present;
clicking it opens ApplyView for each diff
- ApplyView redesigned: renders change blocks side-by-side (original left
with red highlights, modified right with green highlights) using the
SideBySideBlock component, unchanged blocks shown as plain text between
change blocks — restores the classic diff block presentation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Revert "feat(editFile): add Show Diff button and side-by-side diff view in ApplyView"
This reverts commit 80945493b48d50c26b9a01bc40de5eced2fcfd18.
* feat(editFile): show ApplyView with simple mode (no per-block buttons)
- editFile now shows the Apply View preview and blocks until user accepts/rejects
- Respects autoAcceptEdits setting (bypasses preview when enabled)
- Adds `simple` flag to ApplyViewState to hide per-block accept/reject/revert buttons
- Removes unused createPatch import
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(tools): update writeFile description to "Create or rewrite files in your vault"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address Codex review — GPT prompt, fuzzy match safety, tool ID migration
- modelAdapter: update GPT prompt to use editFile's oldText/newText params
instead of the old SEARCH/REPLACE block syntax
- ComposerTools: fix fuzzy match to replace only the matched span in the
original content; previously the entire file was rebuilt from the
fuzzy-normalized string, inadvertently applying trimEnd and quote/dash
normalization to text outside the matched span
- settings/model: migrate persisted autonomousAgentEnabledToolIds from
writeToFile→writeFile and replaceInFile→editFile on upgrade
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(editFile): add unit tests for fuzzy match via applyEditToContent
Extract pure replacement logic into applyEditToContent() for testability,
then wire editFileTool.func to call it (removes duplicated logic).
Tests cover:
- Exact match: basic replacement, deletion, NOT_FOUND, AMBIGUOUS
- Fuzzy via smart quotes (single, double), en-dash, NBSP
- Fuzzy via trailing whitespace (spaces, tabs)
- Key invariant: fuzzy match only replaces the matched span — smart quotes
and trailing spaces outside the span are left untouched
- Multiline fuzzy match with mixed artifacts
- CRLF and LF preservation
- BOM preservation
Also fixes mapFuzzyIndexToNormal to extend matchEnd to the full original
line length when the fuzzy match ends at end-of-line, preventing orphaned
trailing whitespace after replacement.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address Codex review — NFKC position mapping and legacy tag compat
- ComposerTools: fix mapFuzzyIndexToNormal to handle NFKC expansion
(e.g. Ⅳ → IV) by walking char-by-char within each line instead of
assuming a 1:1 column mapping; adds two unit tests covering this case
- ChatSingleMessage: normalise legacy <writeToFile> tags to <writeFile>
before rendering so saved chat history from before the tool rename
continues to display correctly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(modelAdapter): update assertions for editFile oldText/newText prompt
Replace stale SEARCH/REPLACE references with the new parameter names.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(editFile): replace sentinel strings with ApplyEditResult discriminated union
- `applyEditToContent` now returns `{ ok: true; content: string } | { ok: false; reason: 'NOT_FOUND' } | { ok: false; reason: 'AMBIGUOUS'; occurrences: number }` instead of raw sentinel strings, eliminating false positives when file content matches 'NOT_FOUND' or starts with 'AMBIGUOUS:'
- Update `editFileTool` to pattern-match on the discriminated union
- Update `cleanMessageForCopy` regexes to also strip legacy `<writeToFile>` tags from saved chat history
- Update all `applyEditToContent` test assertions to use the new structured return type
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(editFile): sanitize file path before vault lookup
Apply `sanitizeFilePath` in `editFileTool` to match the same treatment
`writeFileTool` already does, preventing "File not found" failures when
the path has a basename that exceeds 255 bytes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(editFile): guard against degenerate fuzzy match inside NFKC expansion
When `oldText` fuzzy-matches a span whose boundaries both map to the same
position in the original (e.g. searching "I" inside fuzzy "IV" derived from
"Ⅳ"), `matchStart >= matchEnd` and the replacement would be zero-width or
corrupt. Return NOT_FOUND instead of silently producing a wrong edit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(builtinTools): remove invalid daily:append/daily:prepend from prompt
The obsidianDailyNote tool schema only accepts 'daily', 'daily:read', and
'daily:path'. The prompt was incorrectly advertising non-existent
daily:append and daily:prepend commands, which would cause LLM-generated
tool calls to fail schema validation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(editFile): add Stage 3 to tolerate trailing newline mismatch at EOF
LLMs frequently append \n to the last line of oldText even when the file
has no final newline. Neither exact nor fuzzy match would find this, causing
avoidable NOT_FOUND failures. Stage 3 retries with one trailing \n stripped
from oldText (and mirrors the trim on newText to preserve the file's
no-trailing-newline format).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(tests): update editFile integration test to oldText/newText shape
The argumentValidator was still asserting the legacy args.diff SEARCH/REPLACE
payload. Updated to check args.oldText and args.newText to match the new
editFile tool contract.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(editFile): count overlapping matches to prevent wrong-location edits
countOccurrences was advancing by searchText.length, so overlapping patterns
like "aba" in "ababa" were counted as 1 (non-overlapping) instead of 2. This
caused an ambiguous match to be silently applied at the first position rather
than returning AMBIGUOUS. Advance by 1 to catch all overlapping occurrences.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(editFile): round-trip check guards partial NFKC-expansion fuzzy matches
The existing matchStart >= matchEnd guard only caught degenerate zero-width
spans (e.g. "I" in "Ⅳ"). A partial match like "V" against "Ⅳ" produces a
valid non-zero span [0,1) but would replace the entire source character even
though "V" doesn't exist standalone in the file.
Fix: after mapping indices back to the original, verify that
normalizeForFuzzyMatch(original[matchStart:matchEnd]) === workingSearch.
If not, the match straddled an NFKC expansion boundary → return NOT_FOUND.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(editFile): return structured failed result on edit errors
Replace plain string returns with `{ result: "failed", message }` objects
for all failure paths in editFileTool (file not found, text not found,
ambiguous match, and exceptions), matching writeFileTool's pattern so
the LLM and result formatters can reliably detect failures via the
`result` field. No-op case returns `result: "accepted"` since the edit
technically succeeded.
Also fix a stale tool name reference in builtinTools prompt instructions
(writeToFile → writeFile).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Switch LM Studio from /v1/chat/completions to /v1/responses via a thin
ChatLMStudio wrapper that patches LangChain compatibility issues
(text.format requirement, strict:null in tool definitions).
- New ChatLMStudio class with fetch wrapper for tool sanitization
- Opt-out toggle in model settings (useResponsesApi)
- Ping uses ChatLMStudio to test the correct endpoint
- ThinkBlockStreamer: strip special tokens from text content
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The disableIndexOnMobile guard was blocking all re-indexing on mobile,
including the Miyo backend which is a remote HTTP service with no local
index. Add isRemoteBackend() to SemanticIndexBackend interface so guards
in IndexEventHandler and VectorStoreManager can skip the check for
remote backends.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove daily:append and daily:prepend from the obsidianDailyNote tool.
These commands overlapped with writeToFile/replaceInFile, creating
ambiguity for the agent when the user asks to add content to a daily
note. Users should use writeToFile or replaceInFile instead.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(rename): Add automatic file renaming to match generated topic titles
Adds automatic file renaming so that notes reflect the finalized AI-generated
topic in their filename. I believe this restores previously expected behavior.
Refs #2005
* Fix chat file renaming to respect original project and hidden directories
- Pass captured project context to generateFileName for async rename,
ensuring the filename uses the project active at save time rather than global current-project state.
- Add fallback for hidden-directory files when reading epoch for rename,
using the vault adapter if metadataCache is missing, so AI-generated topic renaming works for hidden folders.
Fix#2240
* fix: honor explicit null project context in async chat renaming
Refactor ChatPersistenceManager to distinguish between 'undefined' project (fallback to global) and 'null' project (explicitly no project). This prevents a race condition where non-project chats were incorrectly moved into project directories upon async file renaming.
Key changes:
- Updated 'generateFileName' to use strict undefined checking for project fallback.
- Reordered method signatures for 'generateFileName', 'generateTopicAsyncIfNeeded', and 'renameFileToMatchTopic' to prioritize the project context.
- Verified all internal call sites have been updated to match the new signatures.
- Confirmed fix via unit tests and local compilation.
* fix(tools): improve daily note template workflow for past/future dates
Update prompt instructions for creating past/future daily notes to
use template:read for template content, with a fallback to ask the
user for the template path if the Templates plugin isn't configured.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(tools): list templates before reading in daily note workflow
The workflow told the agent to call template:read directly without
the required name argument. Now it explicitly lists templates first
to discover the name, then passes it to template:read.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(tools): route task queries to obsidianTasks instead of localSearch
Add explicit prompt instruction to always use obsidianTasks for task
queries, not localSearch. For time-based task queries, use verbose
mode and filter by file dates. localSearch searches note content but
doesn't understand markdown checkbox semantics.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(tools): reduce reasoning noise and fix tasks schema hint
Skip redundant success reasoning steps (e.g. "Listed vault tasks"
after "Listing vault tasks"). Only show result steps on failure or
when they add info (search source counts).
Also fix tasks tool schema: the LLM kept guessing command="list"
instead of "tasks" because z.literal lacked a clear hint. Added
'Must be exactly "tasks"' to the description.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(tools): add base:create command and .base active note support
Add `base:create` to the obsidianBases CLI tool, enabling creation of
new items in Obsidian Bases through natural language. Also add .base
files as recognized active notes so they appear in chat context.
Changes:
- Add base:create command with name, content, view params to CLI tool
- Support .base files as active notes in chat context
- Add TEXT_READABLE_EXTENSIONS and ALLOWED_NOTE_CONTEXT_EXTENSIONS to
constants.ts as single source of truth for file extension checks
- Use isTextReadableFile() in contextProcessor and utils instead of
hardcoded extension checks
- Register .base in MarkdownParser for content reading
- Fix inaccurate filter syntax in Base YAML prompt instructions
- Fix broken lint-staged eslint flag (--no-warn-ignored not in eslint 8)
- Update design doc with base:create in v1 tier and write policy
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(tools): improve LLM routing for .base file operations
Add explicit disambiguation in both writeToFile and obsidianBases
prompts to prevent the LLM from using Composer for .base operations.
writeToFile now excludes .base files by default, and obsidianBases
declares itself as the primary tool for all .base operations with
clear examples of when each tool should be used.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(tools): add execution-time guard for .base file routing
Add a code-level guard in executeSequentialToolCall that intercepts
writeToFile/replaceInFile calls targeting .base files and redirects
the LLM to use obsidianBases instead. This is more reliable than
prompt-only disambiguation since it works regardless of what the LLM
decides.
The guard only fires when obsidianBases is available (desktop). On
mobile or when the tool is disabled, writeToFile proceeds normally.
The error message tells the LLM exactly which commands to use, so it
self-corrects on the next iteration.
Also simplifies the prompt instructions by removing the verbose
disambiguation text that wasn't working reliably.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(composer): respect user auto-accept setting over LLM confirmation param
The writeToFile tool allowed the LLM to bypass the diff preview by
passing confirmation=false, even when the user had not enabled
auto-accept edits in settings. Now the preview is always shown unless
the user has explicitly enabled auto-accept. The LLM's confirmation
parameter is ignored.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(tools): add daily command to create today's daily note
The agent had no way to create a daily note - daily:append/prepend
require non-empty content and don't apply templates. Add the CLI
`daily` command which creates today's daily note with the user's
configured template and folder from the core Daily Notes plugin.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(tools): allow composer to create new .base files
The execution-time guard was blocking writeToFile for ALL .base paths,
including creating brand new .base files. Now the guard only redirects
when the .base file already exists (editing an existing base should
use CLI). Creating a new .base file via composer proceeds normally.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(tools): improve Base YAML filter syntax and add validation step
Fix LLM generating invalid Base YAML by adding explicit filter format
rules: single string or and/or/not objects only, bare YAML lists are
invalid. Also add a validation step that calls base:views after
creating a .base file to verify it parses correctly, enabling the
agent to self-correct on errors.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(tools): remove .base file execution guard
The guard was too aggressive - it blocked legitimate .base YAML edits
(adding columns, changing filters) because the CLI has no commands to
modify .base file structure. Editing .base YAML requires writeToFile.
Routing between CLI (base:create, base:query) and composer (YAML
edits) is now handled entirely by prompt instructions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: restrict embedded note segment parsing to markdown files only
Revert the embed gate from isTextReadableFile() back to .md-only.
The extractMarkdownSegment logic treats # as a heading reference,
but .base files use # for view names (![[Library.base#To Read]]).
Routing .base embeds through heading parsing would fail and return
an error block instead of usable context.
The chain restriction check (Plus-mode gating) correctly uses
isTextReadableFile since it's about access control, not parsing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(agent): improve inline citations, query dedup, and answer source priority
- Make extractSourcesSection handle --- separator and bare trailing
footnotes so agent mode citations become clickable
- Re-add citation format reminder before [User query]: via
getCitationFormatReminder + injectGuidanceBeforeUserQuery
- Move linkInlineCitations to run after all segments are rendered
- Improve query dedup metric to max(jaccard, containment) with 0.6
threshold for better subset-style duplicate detection
- Add 4-tier answer source priority: explicit context > vault > web >
baked-in knowledge
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* perf: defer linkInlineCitations until streaming completes
Skip DOM-walking citation linkage during streaming since the sources
section is incomplete and the DOM is rebuilt every chunk anyway.
Citations become clickable all at once when the stream finishes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address Codex review comments - HR sources validation and lastIndexOf
- Strategy 2 of extractSourcesSection now requires ALL non-empty lines
after the --- separator to be footnote definitions, preventing content
loss when --- appears as a divider before mixed content (P1)
- injectGuidanceBeforeUserQuery uses lastIndexOf instead of indexOf to
target the final [User query]: label, avoiding misinjection when tool
output contains that literal string (P2)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: citation placeholder badges during streaming
- Wrap raw [^N] footnote marks in placeholder spans during streaming
to prevent markdown renderer from showing bare superscript numbers
- Style placeholders as small muted superscript pills (copilot-citation-ref)
- Style final clickable citations as matching superscript pills with
accent color (copilot-citation-group)
- Seamless visual transition: same pill shape, muted -> accent colored
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: restore expanded search limits for time-range and tag queries
PR #2273 removed the agent-facing returnAll parameter but also
removed the implicit expanded limits that time-range and tag-focused
queries relied on. This restores the behavior: when a search has a
timeRange or tag terms, automatically use RETURN_ALL_LIMIT (100)
instead of the default maxSourceChunks, and pass returnAll to the
underlying retrievers. The agent still cannot explicitly request
returnAll via the tool schema.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: two-tier search result formatting to reduce context bloat for expanded queries
When search results exceed maxSourceChunks, split into two tiers:
- Tier 1 (first maxSourceChunks docs): full content XML as before
- Tier 2 (overflow docs): metadata-only with title, path, mtime, 150-char snippet
Achieves ~85% context reduction for expanded-limit queries while preserving
discoverability. Adds formatMetadataOnlyDocuments() as a pure, testable utility.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: two-tier search formatting for time-range and tag queries
- Add isFilterOnlyResults() and isTimeDominantResults() helpers in searchResultUtils
- Update formatMetadataOnlyDocuments() with configurable snippetLength (default 300)
- Update prepareLocalSearchResult() with three-case logic:
- Time-dominant: sort by mtime desc, top maxSourceChunks get full content, rest metadata-only
- Tag-only: all docs get metadata-only (no ranking available)
- Ranked: existing behavior unchanged
- Extract FILTER_SOURCES to module-level constant to avoid per-call Set allocation
- Add tests for isFilterOnlyResults, isTimeDominantResults, and custom snippetLength
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: update readNote instruction in metadata-only search results
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address Codex P1/P2 review findings for two-tier search
P1: Remove title-match from FILTER_SOURCES so explicit note references
([[Note Name]]) get full content in tier 1, not metadata-only. This
restores prior behavior where title-match queries provided full content
without requiring an extra readNote tool call.
P2: Fall back to parsing tags directly from the query string when
salientTerms has no tag terms. Mirrors the regex logic in FilterRetriever
so needsExpandedLimits is true even when salientTerms is incomplete.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* revert: remove unrelated Prettier formatting changes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: preserve no-results marker and sanitize metadata snippets
- Guard the metadata-only path with tier2Docs.length > 0 so empty-result
searches fall through to formatSearchResultsForLLM and return the
expected "No relevant documents found." marker instead of an empty string.
- Apply sanitizeContentForCitations to doc.content in
formatMetadataOnlyDocuments before slicing the snippet, preventing
leaked citation markers (e.g. [^12], [3]) from note content into the
tier-2 metadata prompt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: decouple timeDominant detection from filterOnly
Time-range queries via FilterRetriever often produce a mix of
"time-filtered" and "title-match" (daily notes from getTitleMatches)
docs. Since "title-match" is not in FILTER_SOURCES, isFilterOnlyResults
returns false for these result sets, making the old `timeDominant =
filterOnly && isTimeDominantResults(...)` always false — skipping the
mtime-based recency sort for time-range queries.
Fix by evaluating isTimeDominantResults independently: if any doc has
source "time-filtered", use mtime sort regardless of whether all docs
are filter-only.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: populate tagTerms from query when salientTerms misses hashtags
When salientTerms is incomplete (e.g. query="#python" with empty
salientTerms), tagTerms was always an empty array, so SelfHostRetriever
received no server-side tag filters despite expanding to RETURN_ALL_LIMIT.
Extract hashtags from the raw query using the same regex as FilterRetriever
as a fallback, and assign the result directly to tagTerms so all retriever
paths (including SelfHostRetriever) get proper tag constraints.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds `gemini-embedding-2-preview` to the builtin embedding model list.
This is Google's latest multimodal embedding model with 8K token input
limit and up to 3072 output dimensions, offering improved quality over
the existing gemini-embedding-001.
Closes#2298
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The pre-commit hook existed but was not executable and used raw
prettier instead of lint-staged. This fixes:
- Make .husky/pre-commit executable (was -rw-r--r--)
- Use `npx lint-staged` instead of raw prettier/eslint commands
- Remove deprecated husky v4 config from package.json (hooks key)
- Remove unnecessary `git add` from lint-staged (auto-staged in v15)
- Add eslint --fix to lint-staged for JS/TS files
All contributors now get automatic formatting + linting on commit.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(openrouter): enable prompt caching via cache_control field
Add `cache_control: { type: "ephemeral" }` to all OpenRouter invocation
params so Anthropic models automatically detect cache breakpoints and
reduce token costs. Other providers (OpenAI, DeepSeek, Gemini, Grok,
Groq) either auto-cache without this field or safely ignore it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(openrouter): gate cache_control to OpenRouter endpoints only
ChatOpenRouter is shared by LM_STUDIO and COPILOT_PLUS providers which
use their own OpenAI-compatible base URLs. Sending cache_control to
strict OpenAI-format backends causes 4xx request failures. Detect
OpenRouter by checking the baseURL and only inject cache_control when
talking to openrouter.ai.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update Claude Sonnet builtin model to claude-sonnet-4-6
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update builtin models - add gemini-3.1-flash-lite-preview, reduce enabled defaults
- Update Claude Sonnet to claude-sonnet-4-6
- Add gemini-3.1-flash-lite-preview (enabled, projectEnabled)
- Disable most builtin models by default to reduce clutter
- Keep enabled: Copilot Plus Flash, OpenRouter Gemini 2.5 Flash,
GPT-5.2, GPT-5 mini, Claude Sonnet 4.6, Gemini 2.5 Flash,
Gemini 3.1 Flash Lite Preview
- Update Bedrock example placeholder to claude-sonnet-4-6
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: unify Azure OpenAI and Azure Foundry into single Azure provider
Combines the two Azure providers into one. Users can now provide the
full endpoint URL as the base URL and skip the legacy instance name,
deployment name, and API version fields. The URL is automatically
normalized (strips /chat/completions, extracts api-version from query
params). Legacy field-based configuration still works for backward
compatibility.
Changes:
- Rename Azure OpenAI label to "Azure" with updated example host
- Remove azure-openai builtin model (users add models manually)
- Add normalizeAzureUrl() to handle full URLs pasted by users
- Make legacy Azure fields optional in ModelAddDialog when base URL set
- Update CURL generation to use base URL directly when provided
- Disable Responses API for Azure provider (not supported by Azure)
- Default api-version to 2024-05-01-preview when not specified
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address Codex review - Azure embedding validation and curl model field
- Require legacy Azure fields (instanceName, deploymentName, apiVersion) for
embedding models even when baseUrl is provided, since EmbeddingManager reads
those fields directly and never consumes baseUrl
- Include model name in Azure base-URL curl payloads for both chat and embedding
requests so the copied curl command works with Azure Foundry /models endpoints
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: show Azure legacy fields in UI for embedding models even when baseUrl is set
The renderProviderSpecificFields early-return was hiding instanceName,
embeddingDeploymentName, and apiVersion for all Azure models when baseUrl
was present. But validateFields() requires those fields for embedding models
regardless of baseUrl. Now only chat models skip the legacy UI fields when
baseUrl is provided; embedding models always render them.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: trim baseUrl before validation and skip Responses API verbosity for Azure
- Use model.baseUrl?.trim() in validateFields() and renderProviderSpecificFields()
so whitespace-only input cannot bypass required-field validation
- Skip text.verbosity in getOpenAISpecialConfig() for Azure models, since this
PR intentionally excludes Azure from useResponsesApi and the Responses-API
verbosity param would produce a broken payload for Azure GPT-5 with verbosity set
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: prevent agent ReAct loop from getting stuck with small local models
Small local models (e.g. qwen3.5-4b-mlx) get stuck calling localSearch
repeatedly with near-identical queries and never synthesizing an answer.
- Remove QueryExpander from agent loop to avoid model contention on
single-connection local models (retriever still expands internally)
- Add query deduplication that pre-filters tool call batches using
Jaccard word-overlap similarity before execution
- Force synthesis via raw model (without tools) after 2 consecutive
iterations where all tool calls are duplicates
- Add agent-specific system prompt guidance for search limits
- Strip leaked chat template role tokens from intermediate content
- Add intermediate model output logging for debugging
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: make inline citation numbers clickable and restore Max Sources setting
Inline citation numbers like [1], [2] in chat responses are now clickable
links that open the corresponding source note. Also restores the Max Sources
slider in QA settings, reverting the hardcoded DEFAULT_MAX_SOURCE_CHUNKS.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove excessive 32px bottom padding from chat view on desktop
The padding-bottom on .view-content used max(safe-area + 4px, 32px),
forcing a 32px minimum even on desktop where safe-area-inset-bottom is 0.
Simplified to safe-area + 4px so desktop gets minimal padding while
devices with safe areas still get proper spacing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(agent): track dedup queries post-execution to keep failed searches retryable
Move previousSearchQueries tracking from before to after each localSearch tool
call executes. Previously, queries were marked as seen before any tool ran, so
if a call was aborted or the loop threw early, those queries were still blocked
from retrying on the next iteration. Now each query is added to
previousSearchQueries immediately after its tool call returns, keeping the dedup
effective for completed calls while leaving transient failures retryable.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(citations): preserve Obsidian internal-link attributes on inline citation anchors
When building clickable inline citation links (e.g. [1], [2]), the previous code
only copied the href from the source section anchor. Obsidian vault-note links have
additional attributes such as data-href and class="internal-link" that are required
for in-app note navigation. Without them, clicking an inline citation resolves the
link as a plain relative URL instead of opening the corresponding note.
Fix: store the source anchor element rather than just the href, then copy all
attributes onto the generated citation anchor before setting the citation-specific
class and aria-label overrides.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(agent): only mark localSearch queries as seen after successful execution
Change the dedup tracking condition from always tracking queries post-execution
to only tracking when the tool call succeeds (result.success is true). This
ensures transient failures (e.g. temporary index unavailability) leave the
query retryable: if a localSearch fails, the model can issue the same query
again on the next iteration. Previously, even a failed execution would mark
the query as seen, blocking recovery.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: deduplicate inline citations after source consolidation
When multiple chunks come from the same note, the LLM cites them
separately. After consolidation maps them to the same source number,
adjacent duplicates like [1][1] or [1] and [1] now collapse to [1].
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: update GPT-5.2 to GPT-5.4 in builtin models
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add dynamic status bar clearance for desktop chat view
The chat input was hidden behind Obsidian's status bar on desktop.
Adds ChatViewLayout which measures the geometric overlap between
the view-content and the status bar, setting a CSS variable to
provide the exact padding needed. Adapts to any theme: no clearance
for auto-hide themes (opacity: 0) or themes that already account
for the status bar in their layout; correct clearance for themes
where the bar is visible and overlapping. Re-checks on theme
switches via debounced css-change listener.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: suppress thinking tokens during streaming when reasoning is disabled
When a local model (e.g. Qwen 3.5 via LM Studio) emits text-level
<think> tags but the user has not enabled the reasoning capability,
the thinking content was visibly rendered during streaming and only
stripped once the </think> tag arrived. Now tracks the position of
excluded think blocks across chunks and truncates fullResponse to
the safe prefix on each chunk, so thinking content never appears.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The chat settings popover (gear menu) was missing the "None (use
built-in prompt)" option that exists in the Advanced settings tab.
Users could not clear a custom system prompt from the chat settings.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Dual-anchor model: use selection.to for below, selection.from for above,
with focusPos for reverse selections, preventing panel from obscuring text
- Visual multi-line detection via coordinate comparison instead of logical
line numbers, fixing soft-wrap misclassification
- Extract computeVerticalPlacement() pure helper with regression tests
- Shared height constants (MODAL_MIN_HEIGHT_COMPACT/EXPANDED) eliminating
fragile three-way coupling between positioning, modal, and DraggableModal
- anchorBottom support in DraggableModal for upward growth on above placement
- Consistent "center in editor" fallback across all insufficient-space cases
for both Quick Ask and Quick Command
- Space checks use scrollRect (editor visible area) instead of window
- Multi-line selections center horizontally on the editor
- Horizontal clamp to scrollRect first, then viewport as safety net
- Reset DraggableModal transient state on reopen transitions
- Side lock to prevent flipping during streaming output
* fix: defense-in-depth overrides to prevent tiktoken CDN timeout in Plus mode (#2282)
Plus mode's planToolCalls uses invoke() on a streaming model, which
causes LangChain's _generate() to call _getEstimatedTokenCountFromPrompt
after streaming. This triggers getNumTokens -> encodingForModel -> CDN
fetch with 6 retries, hanging for minutes when tiktoken.pages.dev is
unreachable.
The existing getNumTokens instance override (PR #2160) is bypassed in
some production bundle configurations. Add overrides at two additional
levels: getNumTokensFromMessages and _getEstimatedTokenCountFromPrompt.
The latter returns 0 since actual token usage comes from API response
metadata.
Agent mode is unaffected because it uses stream() which bypasses
_generate() entirely.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use stream() instead of invoke() in planToolCalls to avoid tiktoken CDN fetch
Instance property overrides on getNumTokens are bypassed in esbuild
production bundles (prototype methods called directly). Instead of
trying to override methods, eliminate the problematic code path: replace
invoke() with stream() + chunk collection in planToolCalls.
invoke() goes through _generate() which, with streaming: true, calls
_getEstimatedTokenCountFromPrompt -> getNumTokensFromMessages ->
getNumTokens -> encodingForModel -> tiktoken CDN fetch (6 retries).
stream() goes through _streamResponseChunks() directly, bypassing
_generate() and the entire token estimation path. This is the same
approach Agent mode uses, which is why it was never affected.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: patch getNumTokens on prototype instead of instance for esbuild compat
Instance property overrides are bypassed in esbuild production bundles
because minified code resolves this.method() calls directly to the
prototype. Patch the prototype with a guard flag to ensure the tiktoken
CDN fetch is prevented in all code paths including production builds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: patch tiktoken on BaseLanguageModel prototype and convert invoke to stream
Two fixes for the tiktoken CDN timeout that blocks web search:
1. Walk up the prototype chain to patch getNumTokens on
BaseLanguageModel.prototype (where it's defined) instead of the
direct prototype. One patch now covers ALL model classes
(ChatOpenAI, ChatOpenRouter, etc.) instead of only the first
class instantiated.
2. Convert getStandaloneQuestion() from invoke() to stream() to
avoid _generate() -> _getEstimatedTokenCountFromPrompt() ->
getNumTokens() -> tiktoken CDN fetch path entirely. This is
the invoke() call triggered during @websearch tool execution.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: simplify tiktoken patch to one-time module-level override
Replace the per-instance prototype walking with a single module-level
patch on BaseLanguageModel.prototype.getNumTokens. Runs at import time
before any model is created, covering all model classes and all code
paths (createModelInstance, ping, etc.) with zero per-call overhead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: handle array content in getStandaloneQuestion stream loop
Use extractTextFromChunk() instead of a bare string check so that
providers sending content-part arrays (e.g. Anthropic) are handled
correctly during streaming. Previously array content was silently
dropped, which could produce an empty condensed question.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: drag relevant notes and sources into editor to insert wikilinks
Adds Obsidian-native drag-and-drop support to the Relevant Notes panel
and Sources modal, using app.dragManager so dropping onto an editor
automatically inserts [[wikilink]] without any custom drop handler.
- New useNoteDrag hook wraps app.dragManager.dragLink/onDragStart
- RelevantNotes: title link (expanded) and badge (collapsed) are draggable
- SourcesModal: source links are draggable (vanilla JS class context)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: prevent drop zone overlay when dragging from relevant notes
The chat container's "drop files here" overlay incorrectly appeared when
dragging notes from the relevant notes pane. Mark internal drags with a
custom data transfer type so the drop zone hook can skip them.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
YouTube now serves two different transcript panel types server-side:
- Classic ("In this video"): ytd-transcript-segment-renderer with .segment-timestamp
- Modern ("Transcript"): transcript-segment-view-model with .ytwTranscriptSegmentViewModelTimestamp
The old code only handled the classic panel, causing empty transcripts when
Web Viewer referenced a video with the modern panel.
Key changes:
- Panel-scoped queries via findTranscriptPanel() to avoid false matches
- Visibility-aware panel detection to skip hidden/stale panels
- extractFromPanel() auto-detects classic vs modern layout
- Close button uses captured panel element instead of hardcoded target-id
Local model servers (LM Studio, Ollama) sometimes leak chat template
control tokens like <|im_end|> into visible responses when stop tokens
are not configured on the server side.
Add stripSpecialTokens utility that removes known tokens (ChatML,
Llama 3, Gemma, Phi, Mistral, Qwen, DeepSeek, Command R) via a single
pre-compiled regex. Applied in ThinkBlockStreamer.handleDeepseekChunk
before content is appended to the response.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add Lucide icons to all commands for mobile toolbar display
* fix: restore reactive settings subscription in ApiKeyDialog
* fix: render LaTeX in Quick Ask/Command and fix text selection
- Extract shared preprocessAIResponse() for LaTeX delimiter normalization
and executable code block escaping (dataview/tasks)
- LaTeX normalization now skips fenced/inline code blocks
- Quick Ask: preprocess content before MarkdownRenderer call
- Quick Command: add markdown preview mode with edit toggle
- ChatSingleMessage: refactor to use shared preprocessAIResponse()
- Fix text selection in Quick Ask overlay (user-select on CM6 DOM)
- Add Copy button to command modal action bar
- Remove SelectedContent preview (editor highlight is sufficient)
* feat: add configurable context window size for Ollama models (#2275)
Replace hardcoded numCtx=131072 with a per-model configurable slider
in the model parameters UI. Default remains 131072 for backward
compatibility. Users can adjust it to reduce VRAM usage on GPUs with
limited memory.
Also replace raw provider strings with ChatModelProviders enum in
ModelParametersEditor for type safety, and add LM_STUDIO enum check
alongside existing "lm_studio" to handle both provider formats.
The agent was triggering returnAll too aggressively, causing massive token
usage. This removes returnAll from the tool schema, planning prompt, and
all search paths:
- Remove returnAll from localSearch Zod schema (no longer agent-settable)
- Remove OR stacking that expanded limits when time range or tags present
- Maintain similarity score floor at 0.1 (was dropping to 0.0)
- Remove returnAll from CopilotPlusChainRunner planning prompt and extraction
- Clean up agent prompt in builtinTools.ts
- All search paths now use DEFAULT_MAX_SOURCE_CHUNKS (30) consistently
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: pass timeRange to Miyo search path (#2267)
When Miyo is active, localSearchTool called performMiyoSearch without
the timeRange parameter, causing time-based queries like "what did I do
this week" to ignore date filters and return wrong results.
Thread timeRange through to performMiyoSearch, FilterRetriever, and
MiyoSemanticRetriever — all of which already support it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: gitignore .claude/worktrees/
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add obsidianBases CLI tool (read-only)
Add obsidianBases — a read-only category-based CLI tool for querying
Obsidian Base (database) files. Supports three commands: bases (list all
Base files), base:views (list views in a Base file), and base:query
(query data from a Base view).
Follows the same pattern as existing v1 CLI tools (obsidianProperties,
obsidianTasks, obsidianLinks, obsidianTemplates).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: clarify file/path field descriptions in obsidianBases schema
Change "Required" to "Used" in file description and add "Alternative to
file=" in path description to accurately reflect that either file OR path
satisfies the requirement, not both.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add Obsidian CLI daily/random read tools with fallback + reasoning summaries
* chore: move CLI design doc to designdocs/ and remove old TODO doc
- Move docs/OBSIDIAN_CLI_INTEGRATION_DESIGN.md to designdocs/OBSIDIAN_CLI_INTEGRATION.md
- Remove designdocs/todo/OBSIDIAN_CLI_INTEGRATION.md (replaced by PR's design doc)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: gate CLI tools behind Platform.isDesktopApp so they are invisible on mobile
- Move CLI tool registration from static BUILTIN_TOOLS array to registerCliTools()
- Only call registerCliTools() when Platform.isDesktopApp is true
- On mobile: tools are not registered, not shown in settings/UI/reasoning
- Update design doc with platform policy and CLI approach rationale
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update CLI design doc with finalized category-based tool roadmap
- Replace generic v0/v1/v2 tiers with category-based tool grouping
- Define tools: obsidianDailyNote, obsidianProperties, obsidianTasks,
obsidianFiles, obsidianLinks, obsidianTemplates, obsidianBases,
obsidianBookmarks, obsidianTags
- Document excluded commands (destructive, plugin/theme, sync, dev tools)
- Add design rationale for category-based approach
- Update rollout plan to match new tool structure
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: remove search commands from v1 roadmap
search and search:context are redundant with Copilot's existing
keyword + semantic search. Update obsidianFiles to only include
read, append, prepend, random:read. Add search to excluded list.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: restrict file writes to daily notes, add write operations policy
- Remove append/prepend from obsidianFiles (now read-only: read, random:read)
- Daily note append/prepend: direct execution, no Composer diff needed
- Property set/remove and task toggle: require light confirmation
- Arbitrary file writes excluded — use existing Composer tool instead
- Add Write Operations Policy section to design doc
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: remove redundant CLI tools from roadmap, add tool disambiguation
Remove `read` from obsidianFiles (redundant with readNote) and drop
obsidianTags category (redundant with getTagList). Add Tool
Disambiguation section documenting overlap analysis and prompt
instruction guidelines for CLI tools.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: defer confirmation-required CLI mutations from v1 to v2
Move property:set, property:remove, and task toggle/status to v2 tier.
v1 now contains only read-only tools + direct-execution daily note
writes. This ensures v1 ships without needing confirmation UX.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add Appendix A with v1 CLI command reference
Document all 12 v1 CLI commands with parameters, output format, and
real examples. Covers obsidianDailyNote, obsidianProperties,
obsidianTasks, obsidianRandomRead, and obsidianLinks tools. Includes
error response reference.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: implement v1 CLI tools (properties, tasks, links, daily note category)
Add 4 new category-based CLI tools for the Obsidian CLI integration:
- obsidianDailyNote: read, append, prepend, path (replaces v0 obsidianDailyRead)
- obsidianProperties: list vault properties, read per-note properties
- obsidianTasks: list and filter tasks across vault
- obsidianLinks: backlinks, outgoing links, orphans, unresolved links
All tools are desktop-only (gated by Platform.isDesktopApp), invisible on
mobile, and labeled as "Obsidian CLI: ... (Experimental)" in settings.
Includes shared error handling module (cliErrors.ts), reasoning summaries,
tool display names, disambiguation prompt instructions, and 21 new tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: prioritize env CLI binaries and align desktop runtime check
- resolveBinaryCandidates: move env var overrides (OBSIDIAN_CLI_BINARY/
OBSIDIAN_CLI_PATH) before the default "obsidian" binary so configured
overrides are tried first, preventing an incompatible PATH binary from
blocking a valid configured override.
- builtinTools: replace Platform.isDesktopApp with isDesktopRuntime()
(now exported) so CLI tools are registered on both modern isDesktopApp
and legacy isDesktop runtimes, matching the check used in the executor.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: single CLI master toggle, remove v0 overlap, extract buildCliParams
- Group all CLI tools under one "Obsidian CLI (Experimental)" section
with a single master toggle in settings UI
- Remove v0 obsidianDailyRead registration (superseded by v1 obsidianDailyNote)
- Extract buildCliParams helper to reduce repetitive param building
- Use "cli" category for all CLI tool registrations
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: align CLI section title with other tool items
Remove horizontal padding from CLI container box so SettingItem
title aligns flush with Vault Search, Web Search, etc. above it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: preserve raw stdout for daily:read to avoid stripping Markdown whitespace
Only trim stdout for non-read commands (append, prepend, path) where
whitespace is not semantically meaningful. Read commands return note content
where leading/trailing whitespace may be intentional.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add daily create command and obsidianTemplates tool to v1 CLI
- Add 'daily' command to obsidianDailyNote: creates today's daily note
from configured template if it doesn't exist
- Add obsidianTemplates category tool with 'templates' (list) and
'template:read' (read content) commands
- Register obsidianTemplates in builtinTools.ts with cli category
- Update AgentReasoningState and toolExecution with new tool/command cases
- Update obsidianDailyNote prompt instructions to mention 'daily' command
- Move obsidianTemplates from v2 to v1 in design doc; add daily command docs
- Add tests for daily command and full obsidianTemplates tool coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: remove daily command, keep only obsidianTemplates addition
The 'daily' CLI command is a UI action (opens the note in Obsidian) and
returns no content, making it unsuitable as an agent tool. Remove it.
Instead, the agent workflow for template-based daily note creation is:
obsidianTemplates template:read → daily:read (check existence) →
daily:prepend (write template content if needed)
Update obsidianDailyNote prompt instructions to describe this pattern.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: update CLI tool prompt instructions for template-based daily note workflow
- obsidianDailyNote: add step-by-step guide for creating daily notes from
templates (templates list → template:read → daily:prepend); note that
daily:append/prepend auto-create the note if it doesn't exist
- obsidianTemplates: clarify usage of 'templates' and 'template:read',
cross-reference the daily note creation workflow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: preserve raw stdout for read commands in ObsidianCliDailyTools
Same fix as d2c81b5 for ObsidianCliTools: daily:read and random:read
commands return full markdown note content, so trimming stdout can silently
alter formatting-sensitive notes. Both tools now return raw stdout.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: refactor GitHubCopilotChatModel to support tool calling
Refactored GitHubCopilotChatModel from BaseChatModel to ChatOpenAICompletions
to enable bindTools() for Agent Mode tool calling support.
Key changes:
- Extend ChatOpenAICompletions instead of BaseChatModel for native tool support
- Override _streamResponseChunks to fix Copilot-specific streaming issues:
1. Missing delta.role when proxying Claude models (defaults to "assistant")
2. Non-string delta.content arrays from Claude (normalized to string)
- Use public convertCompletionsDeltaToBaseMessageChunk API (not deprecated method)
- Inject Copilot auth via configuration.fetch wrapper with 401/403 retry
- Export COPILOT_API_BASE and add public token management wrappers
* fix: improve type safety and retry logic in GitHubCopilotChatModel
- Replace loose `[key: string]: any` params interface with type derived
from ChatOpenAICompletions constructor fields for proper type checking
- Limit auth retry to 401 only (not 403) to match GitHubCopilotProvider
behavior and avoid useless token refresh on permanent permission errors
- Replace 80-line _streamResponseChunks fork with minimal
_convertCompletionsDeltaToBaseMessageChunk override
- Remove unused imports (BaseChatModelParams, AIMessageChunk, etc.)
* refactor: remove dead code from GitHubCopilotProvider after ChatOpenAICompletions migration
Chat completions are now handled by GitHubCopilotChatModel (extends
ChatOpenAICompletions) with auth injected via configuration.fetch.
The old Provider-level request methods and their supporting types
are no longer referenced anywhere.
Removed:
- sendChatMessage, sendChatMessageStream, executeWithTokenRetry methods
- CopilotChatResponse, CopilotStreamChunk, CopilotRequestOptions interfaces
- CHAT_COMPLETIONS_URL, HTTP_STATUS_MESSAGES constants
- Unused imports: createParser, ParsedEvent, ReconnectInterval, FetchImplementation
* feat: add model policy terms UI, update Copilot headers, and fix review issues
- Update Copilot request headers to latest VSCode versions
- Cache model policy terms and surface activation guidance on 400 errors
- Extend GitHubCopilotModel interface with billing/policy/endpoint fields
- Show verification error with parsed Markdown links in ModelImporter
- Fix unused logInfo import, guard Request instanceof check,
case-insensitive "not supported" match, clear policy cache on resetAuth
* Add release agent definition for automated release PR creation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address Codex review comments on release agent
- Add package-lock.json to staged files in Step 7 (npm version also
updates it, so omitting it leaves a dirty worktree after commit)
- Raise PR list limit from 100 to 500 and add note about pagination
to avoid silently truncating changelogs with many merged PRs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* release: v3.2.4
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* release: add detailed changelog to v3.2.4 notes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* ci: add automated release workflow on PR merge
Triggers when a PR targeting master is merged with a strict semver title
(e.g., "3.2.3"). Builds the plugin and creates a GitHub Release with
main.js, manifest.json, and styles.css attached. Non-semver PR titles
are silently skipped. Includes manifest.json version match validation
and shell injection protection for release notes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: pin release checkout to merge commit SHA
Avoids race condition where a concurrent PR merge could cause the release
workflow to build a different commit than the one that triggered it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: prevent shell injection in release workflow and add idempotency check
Use env vars instead of inline ${{ }} expansion for PR title and version
outputs to prevent shell injection. Add pre-check to skip release creation
if the version tag already exists.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: pin release tag to merge commit SHA via --target flag
Ensures gh release create tags exactly the merge commit, preventing
a source/binary mismatch if concurrent merges land before the step runs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Browser's native fetch() fails with net::ERR_CERT_AUTHORITY_INVALID when
calling models.brevilabs.com. Using safeFetch routes through Obsidian's
requestUrl (Node/Electron layer), bypassing browser-level TLS validation.
Fixes#2250
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Renamed docs/ to designdocs/
- Moved draft/todo docs into designdocs/todo/ subfolder
- Added OBSIDIAN_CLI_INTEGRATION.md from master's todo/ folder
- Updated all docs/ path references in CLAUDE.md, AGENTS.md, and designdocs
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(chat-history): add infinite scroll pagination to ChatHistoryPopover
Progressively loads chat history in batches of 50 via IntersectionObserver
on a sentinel div, preventing UI lag for users with 1000+ conversations.
- Observer is created once on mount with empty deps; reads live pagination
state via paginationStateRef — no disconnect/reconnect as pages load or
search filters change
- Reset only fires when popover opens or search changes while open (not close)
- Status indicator is plain text (data is local/synchronous, not async)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(chat-history): use useLayoutEffect to prevent pagination reset render spike
Replace useEffect with useLayoutEffect for the displayCount reset so it
runs synchronously before the browser paints, eliminating the one-frame
render spike that occurred when reopening the popover with a large
displayCount from a previous scroll session.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: add UI rendering performance audit report
Comprehensive audit of React rendering inefficiencies across the chat UI,
state management, and streaming paths. 14 findings ranked by severity
(Critical/High/Medium/Low) with recommended fix priorities.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Load the Component used for MarkdownRenderer so that post-processor
children (e.g. advanced-table-xt SheetElement) can execute their
onload() lifecycle. Also add table border/padding styles to
.message-content to match Obsidian's native table appearance.