Commit graph

946 commits

Author SHA1 Message Date
Logan Yang
bb013ef4bb
docs: add commit-signing setup; consolidate README ToC (#2409)
- CONTRIBUTING.md: new "Commit Signing" section with the 4-step SSH
  signing setup so future protection rules requiring signed commits
  can be turned on with team-ready instructions in place.
- README.md: remove the abbreviated ToC, promote the comprehensive
  one to the top, drop the self-referencing entry.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:19:00 -07:00
Zero Liu
15ac5844ed
fix(vault): respect user trash preference via FileManager.trashFile (W7/9) (#2405)
* fix(vault): respect user trash preference via FileManager.trashFile (W7/9)

Eighth of nine workspaces splitting #2397. Intentional behavior change:
file deletions now honor the user's Settings -> Files and links ->
Deleted files preference (system trash / .trash / permanent), instead
of unconditionally calling vault.delete(file, true).

- src/utils/vaultAdapterUtils.ts: add trashFile(app, file) helper that
  casts past the missing app.fileManager.trashFile typedef.
  createSyntheticTFile now uses Object.create(TFile.prototype) so
  instanceof TFile returns true.
- src/projects/ProjectFileManager.ts: constructor takes App instead
  of Vault; all vault.delete(file, true) -> trashFile(app, file).
  Project create rollback now trashes partial files (was permanent-
  delete before).
- src/projects/projectRegister.ts: constructor takes App.
- src/projects/projectMigration.ts: rollbackCreatedFile(app, ...),
  ensureProjectsMigratedIfNeeded(app), all delete sites use trashFile.
- src/main.ts: deleteChatHistory uses trashFile; ProjectRegister
  constructed with this.app.
- src/system-prompts/systemPromptManager.ts: prompt delete via trashFile.
- src/commands/customCommandManager.ts: command delete via trashFile.
- src/LLMProviders/projectManager.ts, src/components/Chat.tsx,
  src/components/chat-components/ProjectList.tsx: pass app (not vault)
  to ProjectFileManager.getInstance.
- __mocks__/obsidian.js: app.fileManager.trashFile jest mock.
- Tests updated to expect trashFile + App constructor arg.

W0, W1, and #2402 already merged. Behavior change requires manual
verification (test plan section 1 + 6).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(manifest): bump minAppVersion to 1.4.0

FileManager.trashFile (used by the vault deletion helper introduced in
W7/9) requires Obsidian 1.4+. Bumping minAppVersion ensures the plugin
store only offers this and future releases to compatible app versions;
users on older Obsidian builds stay on their current Copilot version
instead of receiving an update that would crash on delete.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 23:06:49 -07:00
Zero Liu
97165937bb
chore(providers): adopt non-deprecated LangChain APIs + remaining type cleanup (W6/9) (#2404)
Seventh of nine workspaces splitting #2397. Picks up provider-file
changes W1 deferred — primarily LangChain public-API renames that
aren't pure type changes.

- src/LLMProviders/BedrockChatModel.ts: _getType() -> getType()
  (non-deprecated method, same behavior)
- src/LLMProviders/BedrockChatModel.test.ts: matching mock-message
  rename across all streaming-decode tests
- src/LLMProviders/chainRunner/CopilotPlusChainRunner.ts: stringify
  non-string response.content via JSON.stringify instead of String()
  (avoids [object Object] when content is structured)

No semantic message-handling changes. All behavior preserved.

W0, W1, and #2402 already merged.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 23:00:56 -07:00
Zero Liu
858ce4c862
chore(styles): replace element.style with Tailwind + CSS vars (W5/9) (#2400)
* chore(styles): replace element.style with Tailwind + CSS vars (W5/9)

Sixth of nine workspaces splitting #2397. Replaces direct DOM
inline-style writes with Tailwind utility classes (addClass) and
CSS custom properties for dynamic values.

- src/components/modals/SourcesModal.tsx — addClass + tw-* classes
- src/components/modals/AddImageModal.tsx — input.style.display -> tw-hidden
- src/components/modals/CachePreviewModal.ts — modalEl.addClass tw-w/tw-max-w
- src/components/modals/project/AddProjectModal.tsx — !tw-max-h-[85vh]
- src/components/modals/project/context-manage-modal.tsx — tw-min-w-[50vw]
- src/components/quick-ask/QuickAskOverlay.tsx — body cursor/select via tw + CSS var
- src/components/ui/textarea.tsx — --copilot-autosize-height + tw-h-[var(...)]
- src/components/chat-components/ChatViewLayout.ts — removeProperty fix
- src/components/command-ui/draggable-modal.tsx — tw-left/top-[var(--copilot-drag-x/y,0px)]
- src/settings/SettingsPage.tsx — tw-select-text
- src/settings/v2/components/ModelEditDialog.tsx — tw-h-4/5
- src/system-prompts/SystemPromptAddModal.tsx — tw-h-4/5
- src/hooks/use-draggable.ts and use-resizable.ts: write
  --copilot-drag-x/y and --copilot-resize-cursor CSS variables instead
  of inline left/top/cursor; consumers reference them via Tailwind
  arbitrary-value classes

Behavior: visual-only changes; no logic changes. Reviewer should
walk through SCORECARD_WARNINGS_TEST_PLAN.md §3 (drag/resize, QuickAsk
positioning, draggable modal, typeahead positioning, inline pills).

W0 already merged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(styles): use Obsidian setCssProps API for CSS variable writes

Follow-up to the W5 commit. The Obsidian scorecard warning is
"Use the setCssProps function if the CSS properties need to change
dynamically", and the prior pass still wrote them via
element.style.setProperty / removeProperty. Routes the remaining 14
sites across 6 files through HTMLElement#setCssProps, which is the
Obsidian-sanctioned API for writing CSS custom properties.

- src/hooks/use-draggable.ts: --copilot-drag-x/y batched into one call
- src/hooks/use-resizable.ts: --copilot-resize-cursor set/clear
- src/components/quick-ask/QuickAskOverlay.tsx: same cursor pattern
- src/components/chat-components/ChatViewLayout.ts: --copilot-status-bar-clearance
- src/components/ui/textarea.tsx: --copilot-autosize-height
- src/utils/dom/dynamicStyleManager.ts: batches every set+clear into a single
  setCssProps call per invocation (CSSOM defines setProperty(name, "") to
  call removeProperty(name), so empty-string entries clear)

The single getPropertyValue read at src/components/CopilotView.tsx:112 is
a read, not a write, and is not flagged by the scorecard rule.

Verification: grep -rn "\.style\.setProperty\|\.style\.removeProperty" src
returns zero hits. npm run lint, npm run build, npm test (1962 tests) all
pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 22:50:05 -07:00
Zero Liu
8b01ef746e
Fix broken tests (#2408) 2026-05-12 22:28:12 -07:00
Zero Liu
6331631dd0
chore(popout): window-global API swaps — timers, globalThis, base64 (W3/9) (#2401)
Fourth of nine workspaces splitting #2397. Mechanical swaps to
window-namespaced globals for Obsidian popout window compatibility.
No semantic behavior change beyond "works correctly in a popout."

- setTimeout / clearTimeout / setInterval / clearInterval /
  requestAnimationFrame → window.* across all callsites
- globalThis → window (BedrockChatModel fetch.bind, scattered sites)
- atob / btoa → Buffer.from(..., 'base64') / Buffer.from(...).toString('base64')
  - src/encryptionService.ts (drops Node 'buffer' import; Buffer is global)
  - src/utils.ts safeFetch arrayBuffer() branch
  - src/LLMProviders/BedrockChatModel.ts decodeBase64ToUint8Array

Builds on top of #2398 (W0 — deps bump).

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 22:24:17 -07:00
Zero Liu
c67c4fc200
fix(popout): document ownership and selection listener fixes (W4/9) (#2406)
* fix(popout): document ownership and selection listener fixes (W4/9)

Fifth of nine workspaces splitting #2397. Real popout-window
correctness fixes — semantic choices about which window owns a DOM
operation, plus selection-listener captured-document fixes.

- document → activeDocument where a generic active-window lookup is
  correct (SourcesModal, ChatViewLayout, ItemView/Notice mocks,
  TabContext, ChatInput keydown listener, CopilotView keyboard
  observer, TypeaheadMenuPortal, AddImageModal, and ?? document
  fallbacks across draggable-modal, menu-command-modal,
  CustomCommandChatModal, use-draggable, use-resizable, and
  QuickAskOverlay).
- document → root.ownerDocument in ChatSingleMessage's
  linkInlineCitations so DOM nodes end up in the citation's window;
  same pattern in the processMessage streaming block via
  contentRef.current.ownerDocument.
- New getEditorDocument(editor) helper in BasePillNode. createDOM and
  exportDOM now accept the LexicalEditor; pills create DOM in the
  editor's window (correct popout behavior). All 6 pill subclasses
  updated to match the new signature.
- main.ts: selectionListenerDocument captured at listener registration;
  remove uses the same document (fixes leak when activeDocument has
  drifted before unload).
- chatSelectionHighlightController.ts: getActiveViewOfType(MarkdownView)
  replaces the brittle activeLeaf type comparison.
- Test setup: (window as any).activeDocument = window.document in
  ChatSingleMessage.test; window.document.createElement in
  AgentPrompt.test Notice mock.

Behavior fix: chat in a popout window now creates DOM nodes in the
popout's document, not the focused window's document.

W0, W1, and #2402 already merged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(popout): prefer element.doc/.win over ownerDocument and activeDocument

Switch popout-window-sensitive call sites to Obsidian's `.doc` / `.win`
augmentations so DOM ops, listeners, portals, and positioning follow the
element's actual owner window. Recreate Lexical root on view migration to
rebind input handling after a leaf moves between windows. Document the
decision order in AGENTS.md and polyfill `Node.doc` / `Node.win` in
jest.setup.js so tests still run under jsdom.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* improve image upload implementation

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 21:28:04 -07:00
Zero Liu
7619fb5578
chore(promises): explicit handling of floating promises (W2/9) (#2407)
Third of nine workspaces splitting #2397. Surfaces previously
swallowed promise rejections via .catch(logError) or `void` prefix.
No logic changes - only error-handling explicitness.

- ~67 floating promises now use void / .catch(logError)
- Promise-returning callbacks where void was expected are wrapped
- ConfirmModal accepts onConfirm/onCancel returning void | Promise<void>;
  rejections logged via logError
- Custom command runners wrap `result instanceof Promise` paths
- ChatUIState.replaceMessages is now async to match its delegate
- ChainManager initialize / createChainWithNewModel are explicitly
  voided with .catch(logError) at call sites
- dbOperations subscribeToSettingsChange uses void IIFE with try/catch

Behavior change: errors previously hidden by floating promises now
log via @/logger. No code paths changed.

W0, W1, and #2402 already merged.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:42:22 -07:00
Zero Liu
e470b5c70c
chore(types): tighten types and replace TFile/TFolder casts (W1/9) (#2403) 2026-05-12 16:18:22 -07:00
Logan Yang
b7ad36624a
perf(deps): route Cohere & Mistral through OpenAI-compat to cut bundle by 1.8MB (#2402)
Replace ChatCohere / ChatMistralAI / CohereEmbeddings with ChatOpenAI /
OpenAIEmbeddings pointed at each provider's OpenAI-compatible endpoint:

- Cohere: https://api.cohere.ai/compatibility/v1
- Mistral: https://api.mistral.ai/v1 (already OpenAI-shaped)

Dropping @langchain/cohere, @langchain/mistralai, cohere-ai, and
@mistralai/mistralai also eliminates the @aws-sdk/* and @smithy/* chain
that cohere-ai dragged in for Bedrock auth (a path this plugin doesn't use).

Production bundle: 5.21MB -> 3.39MB.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 14:53:59 -07:00
Zero Liu
2fb1552a49
chore(deps): bump deps ahead of scorecard cleanup (W0/9) (#2398)
First of nine workspaces splitting #2397 into focused PRs (see
designdocs/todo/SCORECARD_WARNINGS.md, landing in a later workspace).
This PR is deps-only so the source-touching workspaces (types,
promise safety, popout compat, styles, trash semantics, brevilabs
rewrite, eslint rules) can each land independently.

Dependencies:
- @codemirror/state, @codemirror/view: peer deps for newer @lexical/* usage
- @langchain/classic: required by langchain ^1 split layout
- openai: direct consumer in upcoming provider work
- uuid + @types/uuid: replaces ad-hoc id generation
- zod: schema validation used in upcoming type tightening

DevDependencies:
- eslint-plugin-obsidianmd: installed now but not wired into .eslintrc
  until the final workspace (W9) once all flagged sites are cleaned up

No source changes; no behavior changes. npm run lint / build / test
all pass on this branch.
2026-05-12 14:11:53 -07:00
Logan Yang
8f35b396bc
chore(models): bump built-in models to latest (May 2026) (#2396)
OpenAI:
- gpt-5.4 -> gpt-5.5 (new flagship, released 2026-04-24)
- gpt-5-mini -> gpt-5.4-mini
- gpt-5-nano -> gpt-5.4-nano (enum only)

Anthropic:
- claude-opus-4-6 -> claude-opus-4-7 (Opus 4.6 now legacy)
- Add claude-haiku-4-5 (disabled by default)

Google:
- gemini-3.1-flash-lite-preview -> gemini-3.1-flash-lite (GA 2026-05-07)

xAI:
- grok-4-1-fast -> grok-4.3 (grok-4-1-fast retiring 2026-05-15)

OpenRouter mirrors updated for all of the above.

ModelParametersEditor: gate Extra-High reasoning effort on
startsWith("gpt-5.5") instead of "gpt-5.4" so the option follows
the new flagship and does not accidentally surface on gpt-5.4-mini
or gpt-5.4-nano.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 11:34:07 -07:00
Zero Liu
358b373953
chore: remove orphaned RemoveFromIndexModal (#2395)
The modal and its REMOVE_FILES_FROM_COPILOT_INDEX command were orphaned in
the vault search v3 migration (#1703): the addCommand registration was
removed but the modal file and its constant entries were left behind.
Nothing in the codebase has instantiated it since. Deleting it also clears
the risk-report finding about contentEl.createEl("style", ...), which
Obsidian disallows at runtime.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 10:45:15 -07:00
Wenzheng Jiang
38a23358cd
fix(miyo): send portable folder-prefixed path to related-notes endpoint (#2393)
findRelevantNotes and MiyoIndexBackend.getDocumentsByPath were sending the
local absolute filesystem path as Miyo's file_path. On remote-vault setups
the indexed path is the server's absolute path, so the scroll filter
matched zero points and the related-notes endpoint always returned 404.

Replace getMiyoAbsolutePath with getMiyoFilePath, which returns
"<FolderName>/<vaultRelativePath>" — the inverse of
getVaultRelativeMiyoPath. Miyo's resolveFileInput remaps this format to
the registered folder's canonical absolute path, so it works identically
for local and remote setups.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 09:52:04 +09:00
齐柯宇 (Qi Kery)
e826889c11
fix: use /responses for GitHub Copilot codex models (#2320)
* fix: use /responses for GitHub Copilot codex models
Route Copilot codex models away from /chat/completions to avoid 400
errors, and keep ping/model verification aligned with runtime routing.

* refactor: deduplicate shouldUseGitHubCopilotResponsesApi calls and restore rationale comments

  Cache the result in useCopilotResponses in both createModelInstance() and
  tryPing() instead of calling the helper multiple times per flow.

  Restore Reason comments removed in c96a9d8 that document non-obvious
  constraints: why ChatOpenAICompletions is used over ChatOpenAI, the
  delta.role fallback to avoid broken tool_call_chunks, defaultRole?: any
  type choice, streamUsage/baseURL SDK behavior, and the typeof Request
  guard for Obsidian mobile runtimes.
2026-05-10 17:06:26 -07:00
HT
baa2b954dc
fix: prevent </div> from being consumed by indented code blocks in think sections (#2343)
When thinking content ends with a 4-space-indented line (e.g. Gemma's
bullet-point reasoning), markdown's indented code block rule would consume
the immediately-following </div> closing tag and render it as literal
"&lt;/div&gt;" text. Fix by always trimming section content and appending
\n before the closing tag so it lands on its own unindented line.

Also adds integration tests covering non-streaming, streaming-complete,
and streaming-unclosed think block paths.

Co-authored-by: H <trulyshelton@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 17:02:55 -07:00
Arjun Pramanik
bfc7a4e017
docs(ai): unify AGENTS.md and CLAUDE.md (#2358) 2026-05-09 14:27:22 -07:00
Octopus
c7a1d36b3b
fix: remove 100ms IME timeout causing Enter key delay (fixes #2354) (#2367)
The previous implementation used compositionstart/compositionend events
with a 100ms timeout to track IME state. This caused Korean (Hangul)
IME users to experience a noticeable delay when pressing Enter to send:
the composition-ending Enter was blocked, and subsequent Enters within
100ms were also blocked, requiring users to hold the key or wait.

In Chromium/Electron, the keydown event for IME-confirming Enter fires
with isComposing=true (before compositionend), so the explicit ref-based
tracking is redundant. Relying directly on event.isComposing and the
legacy keyCode 229 is sufficient and matches the simpler approach already
used in follow-up-input.tsx.

Co-authored-by: octo-patch <octo-patch@github.com>
2026-05-09 14:15:25 -07:00
Emt-lin
506de5477b
feat: migrate project storage from data.json to vault files (#2324)
* feat: migrate project storage from data.json to vault files

Migrate project definitions and metadata from data.json to individual
markdown files in the vault's projects directory.

- Project file manager with YAML frontmatter parsing and persistence
- Migration logic from legacy data.json format with unsupported/ backup
- Project UI components (AddProjectModal, ProjectList, ProgressCard)
- Chat history integration for the new storage model
- Status sorting, cache-first ordering, and URL removal in processing
- Atomic cache operations to prevent transient disappearance on rename
- Awaited context cache cleanup on all delete/rename/folder-switch paths
- Raw-content-first frontmatter parsing to avoid stale metadataCache
- BOM/CRLF support in frontmatter read and write helpers
- Accessibility: aria-labels on all icon-only buttons

* fix: address codex review feedback

- Defer vault listener registration from constructor to initialize()
  to avoid premature cache mutations and frontmatter writes during
  initial vault load (before onLayoutReady).

- Coerce numeric YAML project IDs to strings so bare numeric values
  like `copilot-project-id: 123` are not silently dropped.
2026-05-09 12:21:04 -07:00
Wenzheng Jiang
94737297b7
fix(miyo): strip vault folder-name prefix from indexed paths (#2390)
Miyo returns indexed file paths prefixed with the owning folder name
(the vault name), but getVaultRelativeMiyoPath was stripping the
absolute filesystem path prefix instead. As a result, the "List
Indexed Files" command rendered every entry as a broken Obsidian link
(e.g. [[MyVault/notes/foo.md]] instead of [[notes/foo.md]]).

Switch the strip logic to match what Miyo actually returns. Files from
other vaults pass through unchanged so cross-vault search results stay
intact. Always return a forward-slash-normalized path so callers see a
consistent separator. Drop the now-unused getVaultBasePath helper.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 21:21:07 +09:00
Zero Liu
37bea9a65c
fix(ollama): route through safeFetch when enableCors is set (#2382)
Other OpenAI-compatible providers (LM Studio, OpenAI-format, SiliconFlow,
etc.) already pass a fetch override so requests can go through Obsidian's
requestUrl API. The Ollama branch was missing this wiring, so toggling
"Enable CORS" on an Ollama model had no effect and mobile (WKWebView)
requests to http:// Ollama hosts failed with "Load failed" due to CORS /
mixed-content blocking.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 15:48:45 -07:00
Wenzheng Jiang
95d23b2387
release: v3.2.8 (#2369)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 20:43:24 +09:00
Wenzheng Jiang
2e3d6ba0d2
feat(miyo): add toggle to search all indexed content (#2353)
* feat(miyo): add toggle to search all indexed content across folders

Allow users to opt into searching everything in Miyo by omitting the
folder_name from search requests. This is enabled by default so Copilot
searches all indexed content. When disabled, searches are scoped to the
current vault folder only. Only affects the search endpoint — other Miyo
APIs (parseDoc, listFolderFiles, etc.) still send folder_name.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(miyo): default miyoSearchAll to false

Scope searches to the current vault folder by default, which is the
safer default. Users can opt into cross-folder search via the toggle.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 16:15:57 +09:00
Wenzheng Jiang
1960d86a3a
release: v3.2.7 (#2352)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 14:31:09 +09:00
Wenzheng Jiang
d8890ada6d
refactor(miyo): rename folder_path request param to folder_name (#2349)
Miyo server now supports folder_name, which better reflects the
semantic that we send a vault name rather than a filesystem path.
Also removes folder_path from response interfaces as the server
no longer returns it.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 11:49:20 +09:00
Wenzheng Jiang
c350f37d98
refactor(miyo): rename folderPath to folderName and send relative path in parse-doc (#2348)
The Miyo folder identifier is actually the vault name, not a filesystem
path. Rename getMiyoFolderPath -> getMiyoFolderName and all folderPath
params -> folderName across MiyoClient and callers to reflect this.

For parse-doc, send vault-relative file.path instead of absolute path,
and include folder_path in the request body for consistency with all
other Miyo endpoints. Wire-format keys (folder_path) are unchanged.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 11:05:24 +09:00
Logan Yang
8577705cba
fix(debug): remove idx column from search results debug table (#2344)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 05:15:55 -07:00
Wenzheng Jiang
ccf687b5cb
Use vault name for Miyo requests (#2342) 2026-04-05 22:21:20 +09:00
Wenzheng Jiang
7084705067
release: v3.2.6 (#2335)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 09:25:46 +09:00
Wenzheng Jiang
81fd4e5157
feat(miyo): add remote vault folder path override for remote servers (#2334)
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>
2026-03-30 09:17:17 +09:00
Wenzheng Jiang
47e61259d2
Refactor Miyo integration for folder API (#2331)
* refactor Miyo integration for folder API

* chore: remove Miyo related-search log

* docs: add Miyo Node Service API reference

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 09:05:34 +09:00
Wenzheng Jiang
c0d79efb7c
feat(miyo): disable miyo on mobile without a remote server URL (#2328)
* 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>
2026-03-27 09:12:23 +09:00
Wenzheng Jiang
d6283e3151
feat(miyo): rename vault name to remote vault path and align with server API (#2326)
* 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>
2026-03-26 16:24:29 +09:00
Logan Yang
23fb3b42ef
chore: improve release agent to read PR descriptions for better notes (#2319)
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>
2026-03-20 16:26:52 -07:00
Logan Yang
79877e3ef5
feat(openrouter): add per-model toggle for prompt caching (#2318)
* 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>
2026-03-20 16:20:54 -07:00
Logan Yang
01c2c7c9fc
release: v3.2.5 (#2314)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 15:00:02 -07:00
Logan Yang
8b2b694e21
fix(ui): show enabled models without API keys as disabled in dropdown (#2313)
* 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>
2026-03-19 06:47:50 -07:00
Logan Yang
8e9970afd3
feat: add PR pricing agent for sizing and pricing PRs (#2312)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 05:43:42 -07:00
Wenzheng Jiang
ddb16af59c
feat(miyo): add customizable vault name setting (#2311)
* 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>
2026-03-19 10:57:06 +09:00
Wenzheng Jiang
d308423315
Composer V2: Replace replaceInFile with editFile as the primary targeted-edit tool (#2305)
* 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>
2026-03-17 18:51:39 +09:00
Logan Yang
e3d47adfa2
feat(lm-studio): use Responses API for LM Studio models (#2306)
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>
2026-03-16 19:37:38 -07:00
Wenzheng Jiang
8b43355c77
fix(search): allow remote backends to re-index on mobile when disableIndexOnMobile is enabled (#2308)
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>
2026-03-17 10:21:38 +09:00
Logan Yang
ce7c51f336
refactor(tools): remove daily note append/prepend CLI commands (#2307)
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>
2026-03-16 05:49:49 -07:00
Michael Ryan
87ee9d0d56
fix(rename): Add automatic file renaming to match generated topic titles (#2240)
* 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.
2026-03-16 04:34:14 -07:00
Logan Yang
1bf571d63f
fix(tools): daily note template workflow for past/future dates (#2304)
* 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>
2026-03-15 23:30:05 -07:00
Logan Yang
00fa339506
feat(tools): add base:create command and .base active note support (#2303)
* 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>
2026-03-15 17:08:14 -07:00
Logan Yang
31492eb159
feat: enhance CLI tool instructions from obsidian-skills reference (#2301)
Improve agent prompt instructions for all Obsidian CLI tools based on
kepano/obsidian-skills reference:

- dailyNote: add inline flag, escape sequences, template workflow
- properties: add sort=count, file= vs path= targeting, modification tip
- tasks: add status character filter examples
- links: add counts, all flag, file targeting docs
- bases: add full YAML structure, filter syntax, formula functions,
  view types, property types, quoting rules, and embed syntax

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 14:19:44 -07:00
Logan Yang
31ad9b825c
fix(agent): improve inline citations, query dedup, and answer source priority (#2300)
* 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>
2026-03-14 23:41:31 -07:00
Logan Yang
728ccb25d4
fix: restore expanded search limits for time-range and tag queries (#2295)
* 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>
2026-03-14 20:45:22 -07:00
Logan Yang
89711d5136
feat: add Gemini Embedding 2 preview model support (#2299)
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>
2026-03-14 20:33:40 -07:00