Commit graph

18 commits

Author SHA1 Message Date
Emt-lin
6d878e175c
feat(agent-projects): project-scoped Agent Mode — Agent Home PR2 (#2604)
* feat(agent-mode): project-scoped Agent Mode workspaces (PR2)

Turn Agent Mode "projects" into Claude Projects-style workspaces: entering a
project gives a scoped workspace with its own sessions, chat history, and
materialized context, while the global workspace becomes one special scope
(GLOBAL_SCOPE sentinel) sharing the same code path.

Session scope
- AgentSessionManager stores sessions by internal id but binds each to an
  immutable projectId; per-scope MRU, resolveSessionCwd, getSessionsForScope, and
  enterProject/exitProject all key off that projectId, and the active-session
  invariant (active.projectId === activeProjectId) keeps existing UI helpers
  untouched.
- Backend restarts / in-place replacement inherit the replaced session's
  projectId; project scope reuses the warm process instead of adopting the
  vault-root probe session.
- Re-entering a project opens as a fresh visit: conversational chats detach from
  the tab strip (kept live in the pool, listed in chat history) instead of
  dragging every prior tab back, while an unused empty landing is reused rather
  than stacked. The global workspace keeps its restore-the-last-tab behavior.

Context materialization (off-vault shared cache)
- projectContextMaterializer + contextCacheStore + manifestBuilder capture a
  project's URL/YouTube/PDF context once per session (single-flight per project)
  into a per-vault, off-vault store shared across projects
  (~/.obsidian-copilot/vaults/<vaultId>/context-cache/): a source referenced by N
  projects is converted once per vault, not N times. Paths derive from one source
  of truth (conversionsLocation.ts); getVaultId lives in utils/appPaths.
- Layout: shared remotes/ + files/ snapshots keyed by source identity (md5),
  per-project markers/<md5(projectId)>/ failure markers. A root-confined node:fs
  backend (createNodeContextCacheFs) writes atomically (temp+rename), throws on
  write/mkdir/read while list/remove/clear tolerate a missing target (the store
  treats a failed read as a cache miss), and never ascends past the cache root; it
  loads node:fs lazily behind the desktop boundary so Obsidian mobile never pulls
  it into the bundle.
- A per-artifact async-mutex lock makes two projects cold-converting the same
  source converge to a single fetch (the waiter re-reads meta inside the lock and
  cheap-skips). Shared snapshots are never reconciled per-project; a project's
  stale marker is cleared on a cheap-skip hit or when a usable stale snapshot
  survives a failed refresh.
- Cache semantics match the legacy CAG cache: a successful snapshot is kept
  indefinitely (fingerprint cheap-skip, no TTL); a failure with no usable
  snapshot writes a marker and is cheap-skipped on later automatic runs
  (re-surfacing the error) until the file changes or the user retries. A
  schemaVersion stamped into every snapshot/marker makes a future format change
  re-materialize instead of misreading stale content; MATERIALIZED_SOURCE_TYPES
  is the single source of truth for source kinds.
- The first prompt inlines a <project_context> block listing absolute snapshot
  paths (keyed by type:source so a same-URL web+youtube pair resolves correctly)
  so all three backends reach the shared store; opencode gets an
  external_directory allow for the cache root. Per-source status mirrors CAG's
  ProjectLoadTracker into the projectId-keyed agentProjectContextLoadAtom
  (remotes parallel, files sequential; each flips Ready/Failed as it settles) and
  the composer shows a context status icon and queues sends while materializing.
  The never-reject contract and contextSignature/warm reactivity are preserved;
  released CAG caches (.copilot/*) are untouched.

Instructions mirror
- project.md stays the single source of truth, with a marker-gated AGENTS.md
  mirror generated by ensureAgentsMirror: codex/opencode auto-discover it from
  the session cwd, claude gets the same composed instructions via
  getProjectProfile. A built-in project policy is layered into each project's
  instructions (a user-authored, unmarked AGENTS.md is left untouched).

Landing & project UI
- AgentLandingStack spine with project landing state, header, Welcome card,
  Project Chats tab variant, and project CRUD (anchored create popover, edit
  modal, overflow menu); shelf lists cap at 5 behind a View-all popover.
- One context editor (ProjectContextSourceEditor) is shared by the home shelf and
  the Edit Project modal; the Manage modal leads with a Links section whose single
  add affordance is the sidebar "+URL" popover (the old right-pane inline URL
  input is removed for consistency with Tags/Folders/Files). A three-state
  (processing / ready / failed) status popover offers inline per-source retry, its
  landing refresh deferred to popover-close and scope-guarded. The web/youtube
  classification is consolidated into one UrlKind type reused across every URL
  surface. Truncated previews mark the cut with a labelled divider and a "Copy
  full content" pill, and cap render so large snapshots don't freeze the modal.

History scope
- Agent chat history is scope-keyed: a project view lists only its own chats,
  while GLOBAL_SCOPE is the flat all-chats view. Native session history is scoped
  to projects and resumed transcripts hydrate from the session's scope cwd; legacy
  chats with no projectId resolve to GLOBAL_SCOPE. A failed resume/create during a
  cross-scope history load rolls back the active scope (rollbackHistoryLoadScope)
  so the active-session invariant can't be left split.

Todo / plan
- Unified backend todo lists across claude/codex/opencode feed the project info
  popover, with per-session todo-id tracking and symmetric plan clear.

Hardening & fixes
- contextCacheFs rejects `..`, absolute, and root-escaping paths; symlink escape
  is documented as out of the cache's threat model.
- A hard-disabled composer (orphaned project) blocks keyboard sends and queued
  flushes, not just pointer events, so a turn can't drain into a dead project.
- The per-message attachment envelope is renamed <copilot-context> ->
  <attached_context> to disambiguate from the project-wide <project_context>.

Tests
- 30+ suites covering session scope, the off-vault cache store/fs (including a
  real-filesystem dedup integration test), the materializer, AGENTS.md mirror,
  todo-plan pipeline, landing UI, the context-signature refresh state machine, and
  the opencode external_directory allow; plus a mobile load-boundary smoke guard
  over the cache consumers.

* fix(agent-mode): keep project edit modal open on save failure

ProjectInfoPopover's edit onSave swallowed updateProject errors into a
Notice, so onSave resolved and AddProjectModal closed — discarding the
user's edits on duplicate-name / folder-collision failures. Log and
rethrow instead so the modal keeps the form open and surfaces the error,
matching AgentProjectRowActions.handleEdit.

* fix(agent-mode): preserve project context after first-turn fan-out; clear off-vault markers on delete

A first-turn multi-agent fan-out set firstPromptSent while only the
ephemeral sub-sessions received the <project_context> block — the visible
backend is never prompted on that path. The next normal turn then injected
null, permanently stripping the project manifest from the main chat. Stop
the fan-out branch from consuming the first-turn flag so the next visible
turn delivers it. Adds a regression test.

Also clear the off-vault failure-marker bucket (markers/<md5(projectId)>)
in deleteProject so a project recreated under the same id can't inherit
stale negative-cache state. Desktop-gated + dynamically imported; the
helper roots a confined fs at the bucket and reuses recursive clear().

* fix(agent-mode): refresh empty project landing on System Prompt edit

An empty landing session bakes in its project instructions at creation
(Claude via systemPromptAppend, codex/opencode via the AGENTS.md mirror),
but the landing-refresh trigger keyed on the materialization signature,
which deliberately omits systemPrompt. A System-Prompt-only edit therefore
left the open landing untouched, so the first message ran with stale
instructions until a manual new chat.

Add getProjectLandingCaptureSignature (materialization signature + the
instruction body) and key the landing-refresh observer on it, leaving
getProjectContextSignature untouched so prompt edits never trigger
re-materialization. Adds tests.

* fix(agent-mode): preserve compose draft typed during landing refresh

The empty-landing context refresh replaces the session in place; its
draftEmpty guard ran before the async backend-startup window, so text the
user typed during that window landed in the old session's draft and was
pruned when the old session closed — breaking the guard's stated 'never
discard text the user has started typing' contract.

Add useAgentInputDrafts.migrateDraft and call it right after the swap to
carry any draft typed during startup onto the new session id. Writes
setDrafts directly to bypass the live-id guard; ordering is safe because
closeSession awaits cancel() before deleting the old session, so the
source draft is still present when migration runs. Adds tests.

* fix(agent-mode): don't reuse a project landing that captured stale instructions

The previous fix made the active landing refresh on a System-Prompt edit,
but enterProject's session-reuse path gated only on the materialization
dirty flag, which deliberately ignores systemPrompt. So editing a
non-active project's System Prompt then re-entering reused the old landing
and ran the next turn with stale captured instructions.

Track a per-session landing-capture signature (materialization signature +
instruction body) at creation and require a reuse candidate to match the
live project config. When spawning fresh, detach existing empty landings so
a stale blank one can't linger. Leaves the materialization dirty machinery
untouched (it must stay systemPrompt-insensitive). Adds a regression test.

* docs(agent-mode): note PR2 tag/extension routing and Tier-1 exclusion scope

Two DESIGN NOTEs at the project context materializer, recording decisions
already settled in PR2_DESIGN.md so future reviews don't re-flag them:
- tag/extension inclusions are forwarded as source labels and reached via the
  agent's native search; precise resolution (MCP tag_search) is a PR3 item.
- exclusions are Tier 1 (best-effort: don't materialize / don't feed); they do
  not hard-block the agent's native grep — that needs a Tier 2/3 sandbox,
  deferred past PR2.

* fix(agent-mode): let project sessions read opted-in off-vault context

The built-in project prompt's default-only-cwd rule buried its carve-out in
a negative clause, so agents read an off-vault materialized snapshot path
(~/.obsidian-copilot/.../context-cache/...) as a forbidden external file and
declined to read it — even though the project itself configured that source.

Restructure the built-in prompt by axis (write / read+exception / boundary)
and name the <project_context> block explicitly: sources listed there are
opt-in to read and search even outside cwd, and a -> <abs path> snapshot
pointer is read directly. Pin the cross-file <project_context> tag coupling
with a regression test that diffs the prompt against the manifest's real
output.

* refactor(agent-mode): unify agent context status into one shared view

Per-item conversion status (icon + label + lookup key) was duplicated across
three agent surfaces: the composer status popover, the Manage modal's Links
panel, and — newly needed — its File Context file list. Each had its own
status->icon mapping, and the file list had none at all (it was wired to the
CAG load atom, which the agent pipeline never populates, so agent files showed
no status while URLs did).

Introduce processingItemStatusView as the single source of truth
(processingSourceKey / buildProcessingItemLookup / getProcessingStatusLabel /
ProcessingStatusIcon) and route all three surfaces through it. The Manage modal
now builds one agent status lookup (gated to the agent variant) shared by both
the Links panel and the file list, so file rows finally show conversion status
keyed by file:<path>. The legacy CAG status stays separate (different cache,
different semantics). Adds useAgentProcessingItems({ enabled }) so CAG/mobile
callers skip the off-vault read entirely.

* feat(agent-mode): show conversion status + snapshot preview for project files

The context editor's File Context list showed no conversion status or preview
for agent project files, while the Links panel showed both for URLs — an
asymmetry, since file snapshots also live in the off-vault cache. Wire file
rows to the same agent status pipeline: a ready file now shows its status icon
and a 'view parsed content' arrow that opens the off-vault snapshot via
openAgentCachedItemPreview (the same opener URLs use). Markdown has no snapshot
so it shows neither; the CAG path is unchanged.

Collapse the per-prop enableLinks branching at the row into one
getFileRowStatusProps resolver (agent vs CAG dispatched once). Also corrects
stale comments flagged in review (STATUS_COLOR is CAG-badge-only; the agent
hook still subscribes to its atom but the value is unused when disabled).

* refactor(agent-mode): neutralize reviewer-dialogue comments; share source builder

Drop the "if a future review flags this, point them here" reviewer-meta
sentences from this branch's own comments (keeping all why/invariant
rationale), leaving the pre-existing base-convention instances untouched.

Extract buildAgentProcessingSources() so useAgentProcessingItems and
useAgentPersistentFailureCount construct the AgentProcessingSource[] shape
through one shared pure helper instead of two copies that could drift; each
hook still passes its own candidate set (draft vs saved), so behavior is
unchanged.

* fix(agent-mode): restore the <copilot-context> attachment envelope

The per-message attachment envelope was renamed <copilot-context> →
<attached_context> in an earlier feature commit (8b880361), bundled as an
incidental "disambiguate from <project_context>" tweak. That changed AI prompt
content emitted to EVERY agent chat (not just project-scoped ones) without an
explicit request, against the repo rule "Never modify AI prompt content unless
the user explicitly asks"; base v4-preview has used <copilot-context> since
Agent Mode was introduced. Revert the tag (the inner instruction text was
unchanged) so the established prompt contract is preserved. The two
claudeSessionTranscript tests already expected <copilot-context>, so this also
restores prod/test consistency.

* fix(agent-mode): supersede a stale in-flight context materialization on edit

The per-project single-flight let any non-force caller join the in-flight run
unconditionally. If a project's context was edited while an earlier
materialization was still running (slow URL/PDF conversion + a queued first
prompt), the next session joined the stale run and received the pre-edit
<project_context> / additionalDirectories — the queued send could flush with
the old source set, and the new links/files were only captured on a later chat.

Carry the run's context signature in the in-flight entry and join only when it
matches the caller's current record signature; a differing signature supersedes
via the existing take-over-slot path (which defers disk work until the prior run
settles, so no cache-file race). Regression test added to the single-flight
describe.

* fix(agent-mode): roll back project scope when auto-spawn fails

enterProject optimistically parks the prior scope, points activeProjectId
at the new project, detaches its tabs, and nulls activeSessionId before
awaiting the auto-spawn of the scope's first session. When that spawn
rejects (e.g. a missing backend binary), the callers only surface a
Notice, leaving the manager scoped to a project with no active chat until
the user manually recovers.

Wrap the project-scope auto-spawn in spawnEnteredScopeOrRollback, which
restores the previous scope's activeProjectId + active-session pointer and
re-notifies on failure, then rethrows so the caller still reports it. The
rollback is guarded on still being parked in the attempted scope so a
concurrent enterProject isn't clobbered — mirroring the existing
rollbackHistoryLoadScope precedent for the history-load path.

* fix(agent-mode): address review findings (URL trim perf, snapshot meta parse, docs)

Three PR-introduced fixes from the PR2 review plus a deferral note:

- urlTagUtils: rewrite trimUrlTrailingPunctuation from O(n²) (a full split
  scan per stripped char) to O(n) — pre-count brackets once, then walk the end
  pointer left. A pathological trailing-bracket paste no longer freezes the main
  thread. Semantics are unchanged (balanced brackets kept, unbalanced stripped,
  hidden punctuation re-trimmed). Adds a 100k perf tripwire test.

- contextCacheStore: anchor parseSnapshotMeta's close marker to a line boundary
  (\n--> ) instead of the first --> anywhere, which a source path containing -->
  would match inside the embedded JSON, truncating it and forcing a permanent
  cache miss. Adds a regression test.

- projects/state: correct doc comments — project.md is the SSOT, AGENTS.md is
  its generated mirror (was inverted).

- AgentSessionManager: DESIGN NOTE only — detached sessions on project re-entry
  are kept alive intentionally and not yet evicted; bounding the idle class is
  deferred (needs a per-session backend close primitive).
2026-06-28 00:33:22 -07:00
Logan Yang
f4da806b1c
feat(agent-settings): tab the Agents settings into per-backend panels (#2632)
* feat(agent-settings): tab the Agents settings into per-backend panels

Convert the Agents settings tab from stacked vertical sections into a
sub-tab strip (OpenCode / Claude / Codex / Quick Chat) built on the
existing setting-tabs primitive. The global default-backend picker and
MCP servers panel stay above the strip. Within each backend panel the
default-model picker now sits above the model enable list, followed by
the backend's binary/auth config. Quick Chat keeps its existing model
curation. Placement-only revamp; no default-model resolution, seeding,
or persistence changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Swdw8vKE3zczYJFzAboWG

* fix(agent-settings): anchor tab strip on switch; add Quick Chat icon + default-model picker

Switching sub-tabs no longer jumps the settings scroll: the tab strip is
pinned to its pre-switch viewport position when panel heights differ. The
Quick Chat tab gains a chat-bubble icon (was iconless) and a Default model
picker mirroring the per-agent panels, writing the shared defaultModelKey.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Swdw8vKE3zczYJFzAboWG

* chore(scripts): reload deploy-target vault by cwd; timestamp-only build label

The Obsidian CLI resolves its target vault from the working directory, so the
reload step now runs from $VAULT_PATH (was hitting the repo's own vault). The
manifest label is tagged with the build timestamp only, not the branch name.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Swdw8vKE3zczYJFzAboWG

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 20:31:04 -07:00
Zero Liu
ee870b4dce
fix(mobile): keep Agent Mode off the emulated-mobile load path (#2587)
* fix(mobile): keep Agent Mode off the emulated-mobile load path

`app.emulateMobile(true)` flips Platform.isMobile to true (stubbing Node's
built-ins to null) but leaves Platform.isDesktopApp true — so gating Agent Mode
on isDesktopApp still imported the @/agentMode barrel, whose module-scope
`promisify(execFile)` then crashed the whole plugin with
"Cannot read properties of null (reading 'promisify')". Add isDesktopRuntime()
(Platform.isDesktopApp && !Platform.isMobile) and gate every Agent Mode
load/registration on it so the barrel is never imported on a Node-less runtime.
Update the mobile-load smoke gate rule to enforce the new gate and add a unit test.

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

* chore(lint): ban Platform.isDesktopApp; route desktop gating through isDesktopRuntime()

Platform.isDesktopApp stays true under app.emulateMobile(true) (which stubs Node),
so it doesn't keep desktop-only/Node code off the emulated-mobile path. Add a
no-restricted-properties ESLint rule banning it (the canonical check in
src/utils/desktopRuntime.ts is exempt via an inline disable), and migrate all 14
existing usages — web viewer, encryption/keychain, Miyo discovery, web-tab and
@-mention chat hooks, and main.ts's web-viewer gates — to isDesktopRuntime().
Behavior is unchanged on real desktop and mobile; only emulateMobile now faithfully
hides these desktop-only features.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:18:30 +08:00
Logan Yang
775d832ee5
fix(mobile): guard Agent Mode off the mobile load path (#2577)
* fix(mobile): shim `process` so the bundle survives module-eval on mobile

Follow-up to #2576 (preview issue #125). After the EventEmitter fix, mobile now
crashes at load with "ReferenceError: Can't find variable: process".

Cause: Agent Mode is desktop-only but its modules are statically imported, so
they're evaluated on every platform. Several read the Node global `process` at
module scope (e.g. `InstallCommandRow.tsx`'s `const DEFAULT_LABEL =
process.platform === "win32" ? ...`, the codex/claude/opencode descriptors),
and bundled Node deps read `process.env` at init. Mobile's WebView has no
`process`, so evaluating any of them throws during import and kills the plugin.

Fix: prepend a minimal `process` shim in the esbuild banner —
`var process = globalThis.process || { env:{}, platform:"", ... }`. Desktop
(Electron) has a real `globalThis.process` and uses it unchanged; mobile gets
the stub, so module evaluation no longer throws. The desktop-only code paths
that actually use `process` never run on mobile.

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

* chore(test-vault): copy-deploy into iCloud-synced vaults for mobile testing

`npm run test:vault` symlinks main.js/styles.css by default, but iCloud syncs
file contents, not link targets, so a symlinked build never reaches the phone.
Detect iCloud vaults (path under "/Mobile Documents/") and copy real files
instead, so deploying to an iCloud vault makes the build testable on Obsidian
mobile. Mirrors the existing WSL copy-mode rule.

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

* fix(mobile): keep agent mode off load path

* test(mobile): add load smoke check

* fix(mobile): share agent chat mode constant

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 21:56:58 -07:00
Logan Yang
26bed2e5a4
scripts/test-vault.sh: WSL copy mode and self-vault guard (#2543)
The script previously symlinked `main.js` / `styles.css` into the test
vault and assumed the destination filesystem could resolve POSIX
symlinks. On WSL with a vault under `/mnt/c/...`, Obsidian on Windows
cannot follow WSL-flavored symlinks stored on DrvFs/9p, so the symlinks
appear broken and the plugin fails to load.

Detect WSL (`uname -r` contains "microsoft") with a `/mnt/`-mounted
vault and switch to file copy. macOS / native Linux keep the existing
symlink behavior — no change for the canonical developer path.

Also refuse to deploy when the worktree itself lives inside the target
vault. Otherwise the build artifacts and source tree become vault
content, Obsidian indexes the whole repo on the next reload, and the
plugin directory may coincide with the worktree (deploying onto itself).
2026-05-31 17:07:13 -07:00
Zero Liu
d2af89b71a
feat(skills): redesign skills discovery — discover in place, lazy migration (#2519)
* docs(design): add model management redesign spec + data model spec

Replaces the older MODEL_MANAGEMENT_REDESIGN.md with a more focused tech spec,
a data-model spec with review notes, and a portal-container contract doc that
the upcoming reimplementation will follow.

* boostrap new model managmeent

* introduce catalog download service

* Add service APIs

* Add provider and configured model registry

* implement verification logic

* Add backend config registry and byok setup api

* Add BYOK settings modal

* Fix lint error

* Add BYOK settings - agent enablement

* Fix opencode enable model UI

* Fix claude extended thinking

* Render model descriptions

* Fix model picker padding

* improve agent model enabler settings

* Add copilot plus flash support in opencode

* Add vault test with test fixture

* BYOK model management

* introduce model migration script

* feat(skills): redesign skills discovery — discover-in-place, lazy migration

Reverse the import-everything-to-canonical model. The Skills tab now
discovers skills wherever they live (canonical or any agent project
folder) on open — no rescan button, no consent dialog. Identical
duplicates across agents collapse into one row; same-name/different-body
copies stay as disambiguated rows. Cross-agent sharing now goes through
a single explanatory migrate-to-shared confirm, and agents write skills
at their native paths.

Adds project-skill discovery, recursive directory hashing for the
merge rule, a pure toggle-decision helper, and a project→canonical
migration path. Removes the old bulkMove/importDetector/spawnDirective
machinery and the ImportConsentDialog. Fixes the delete dialog to show
the real blast radius for project skills (no phantom canonical copy or
symlink). Includes a seed script for the discovery test scenarios.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 16:29:52 -07:00
Zero Liu
9712d52590
introduce model migration script 2026-05-28 16:03:46 -07:00
Zero Liu
e069b08070
Add vault test with test fixture 2026-05-28 16:02:48 -07:00
Zero Liu
c2ac27edef
feat(agent-mode): introduce Agent Mode feature
Squashed from 8 commits on zero/acp-test:
- feat(agent-mode): introduce Agent Mode feature (squashed)
- Fix eslint
- hide add context button
- chore: ban parent-relative imports and rewrite existing ones to @/ aliases
- wip(agent-mode): skills management + slash command revamp
- fix(agent-mode): initialize SkillManager before preload probes
- fix(build): align svgr jsxRuntime with tsconfig classic jsx
- fix(test): stub ItemView/WorkspaceLeaf in ChatSingleMessage obsidian mock
2026-05-20 19:05:41 -07:00
Zero Liu
f28ced14ff
fix(chain): stop writing chainType back inside setChain (apply-Plus freeze) (#2478)
* chore(test-vault): tag deployed manifest with branch + build timestamp

`npm run test:vault` now writes a real `manifest.json` (instead of a symlink
to the worktree copy) with the current git branch and a `yyyymmdd-HHMMSS`
build timestamp appended to `name` and prepended to `description`. Surfaces
the active worktree/build directly in Obsidian's Community plugins list, so
"is my latest build actually loaded?" is a glance instead of a guess.

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

* fix(chain): stop writing chainType back inside setChain (apply-Plus freeze)

Applying a Copilot Plus license key froze Obsidian. Root cause: ChainManager
ran multiple concurrent `createChainWithNewModel` invocations (one from the
constructor's `initialize()`, plus one each from the settings, model-key, and
chain-type subscribers firing on `applyPlusSettings`). Each captured
`chainType = getChainType()` at its start, awaited `setChatModel(...)`, then
resumed with a now-stale local value and wrote it back to `chainTypeAtom`
via `setChain` → `setChainType`. The write re-fired the chain-type
subscriber in ProjectManager, which scheduled another stale-closure
rebuild, and the atom bounced indefinitely between `LLM_CHAIN` and
`COPILOT_PLUS_CHAIN`.

Inlined `setChain` into `createChainWithNewModel` and removed the redundant
chainType write-back. The atom is owned by the UI dropdowns and
`applyPlusSettings`; this function only reads from it.

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

* docs(chain): fix outdated comment reference to removed test file

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 14:46:21 -07:00
Zero Liu
58edadefdb
Add npm run test:vault for fast worktree-to-vault plugin reload (#2477)
Adds a one-command dev workflow: install + build + symlink main.js /
manifest.json / styles.css from the current worktree into
$COPILOT_TEST_VAULT_PATH/.obsidian/plugins/copilot/, then reload the
plugin via the Obsidian CLI. Removes the manual build/copy/reload loop
across Conductor worktrees. Docs added to CONTRIBUTING.md and AGENTS.md.
2026-05-19 12:05:49 -07:00
Zero Liu
6ca2dc01ea
chore(eslint): enable no-explicit-any; fix ~395 violations (#2452)
* chore(eslint): enable no-explicit-any; fix ~395 violations

Switches @typescript-eslint/no-explicit-any from "off" to "error" and
replaces explicit `any` with proper types or `unknown` + narrowing
across ~100 source files and 15 test files. Eleven eslint-disable
comments remain: one for a BaseLanguageModel prototype patch and ten
for Orama<any> API surfaces where typed alternatives poison Orama's
internal inference to `never`.

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

* fix(scripts): cast chainContext to ChainManager in printPromptDebugEntry

The earlier `as any` → `as unknown` rewrite left buildAgentPromptDebugReport's
chainManager argument typed as `unknown`, which CI's `tsc -noEmit` (without
`--skipLibCheck`) flagged. Restore the cast through the real ChainManager type.

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

* chore(eslint): fix remaining no-explicit-any and unnecessary-type-assertion errors

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

* chore(eslint): drop redundant no-explicit-any rule

Already set to "error" by typescript-eslint/recommendedTypeChecked via
obsidianmd's recommended config.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 02:08:45 -07:00
Zero Liu
df6f455662
chore(eslint): enable @typescript-eslint/no-unsafe-argument and fix violations (#2440)
Removes the `off` override and fixes all 368 violations via type casts,
widened parameter types (unknown / string|null|undefined for functions
that defensively handle non-string inputs), and concrete types in place
of `any` for Lexical nodes, mock objects, and option handlers.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 21:40:26 -07:00
Zero Liu
2df1be0b4f
chore(lint): enable no-unsanitized/method and property (#2418)
Remove the explicit "off" overrides in eslint.config.mjs so the rules
from obsidianmd.configs.recommended apply. Add a scoped disable for the
sole violation in scripts/printPromptDebug.js, where the dynamic import
targets a controlled path under os.tmpdir() produced by esbuild.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 01:56:00 -07:00
Zero Liu
710acc2436
chore(lint): enable obsidianmd/no-global-this and fix violations (#2413)
Removes the deferred `"off"` override and rewrites the 55 violations
(all in test/mocks/Node-script files) to use `window.*` instead of
`global.*`. Production code was already cleaned up in #2401.

- 18 test files + __mocks__/obsidian.js + jest.setup.js: mechanical
  `global.X` → `window.X` (Jest runs under jsdom, so window === globalThis)
- scripts/printPromptDebugEntry.ts: inline eslint-disable — Node-only
  debug script with no window available

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 01:14:47 -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
Logan Yang
1e3e0786ef
Migrate to native tool call in plus and agent (#2123)
* Implement self-host mode
* Migrate from XML tool calls to native tool calls and reimplement agent mode
* Use ChatOpenRouter for copilot-plus-flash for native tool call sse support
* Fix QA exclusion for search v3
* Update plan and remove debug messages in agent
* Migrate chains off of xml and clean up related logic
* Implement agent reasoning block
* Update css for responsiveness
* Implement agent query pre-expansion and proper reasoning block display for search
* Update agent docs
* Refine agent reason block
* Refine agent reasoning UX
* Fallback to plus non-agent if native tool call is not supported by the model
* Fix time filter query
* Update self-host description
* Skip agent reasoning block and old tool call banners during chat save and load
* Fix projects mode switch to plus and chat auto saves as non-project bug
2026-01-26 18:35:27 -08:00
Logan Yang
5568b70e0f
Merge 3.1.0 preview (#1906)
* Building persistent memory for copilot (#1848)

* Implement chat input v3 (#1794)

* Fix chat crash React 409 (#1849)

* Fix mobile typeahead menu size (#1853)

* Enhance progress bar and bug fix (#1814)

* feat: Add edit context for progress card.
* fix: When adding a model, do not set the key in the single model setting.
* fix: fix the width of the popover on mobile.

* Refactor Brevilabs API integration to use models base URL (#1855)

- Updated constants to switch from BREVILABS_API_BASE_URL to BREVILABS_MODELS_BASE_URL for model-related API calls.
- Adjusted ChatModelManager and EmbeddingManager to utilize the new models base URL for configuration settings.

* feat: Add new chat history popover. (#1850)

* feat: Add new chat history popover.

* feat: Add open source file button to chat history popover

- Add ArrowUpRight button to each chat history item
- Implement openChatSourceFile method in main.ts
- Add onOpenSourceFile callback prop chain through components
- Open chat files in new Obsidian tabs when clicked
- Optimize error handling to prevent duplicate notices

* feat: Implement localSearch CiC prompting flow in CopilotPlusChainRunner (#1856)

* Properly extract response text in UserMemoryManager (#1857)

* feat: Temporarily disable autocomplete features (#1858)

* chore: Update version to 3.1.0-preview-250927 in manifest.json

* Support space in typeahead trigger and improve search (#1859)

* More chat input enhancement (#1864)

* Fix dropdown color
* Fix badge border in light theme
* Increase search result number
* support paste image
* fix folder context

* Enhance ChatPersistenceManager with filename sanitization tests (#1865)

- Added tests to ensure proper sanitization of wiki link brackets and illegal characters in filenames when saving chat messages.
- Updated filename generation logic to handle empty sanitized topics by defaulting to 'Untitled Chat'.
- Refactored imports in ChatPersistenceManager for better organization.

* Update ApplyView accept button styles for improved visibility and interaction (#1866)

- Adjusted the positioning of the action button to be further from the bottom of the viewport for better accessibility.
- Increased the z-index to ensure the button is always on top of other elements.
- Added a shadow effect to enhance the button's visibility against the background.

* Expose add selection to chat context to free users (#1867)

* New chat input improvements (#1868)

* Improve search logic in at mention search
* Subscribe to file changes
* Detect changes for folders and tags
* Make context badge tooltip always show
* Add full note path in preview
* Fix enter being blocked when no search result

* Rebuild "Active Note" and more chat input improvement (#1873)

* Performance improvement and extend "active note" to note typeahead menu (#1875)

* Add better agent prompt logging (#1882)

* Add read tool (#1883)

* Add readNote tool for reading notes in chunks

* Enhance readNote tool to support dynamic note path display and linked note extraction

* Refine readNote tool instructions for improved clarity and efficiency in note content retrieval

* Add support for readNote tool: integrate emoji, format results, and refine instructions

* Add inline citation reminder functionality to user questions

* Add an alert when user hits the maxToken limit (#1884)

* Refactor AutonomousAgentChainRunner (#1887)

* Refactor AutonomousAgentChainRunner to enhance agent workflow, streamline context preparation, and improve response handling

* Enhance type safety in addChatHistoryToMessages by specifying message structure

* Improve max iteration limit message for clarity and formatting

* Refactor tool display name handling for improved clarity and maintainability

* Refactor tool call ID generation and visibility handling for improved clarity and uniqueness

* Fix agent tool call ID (#1890)

* Enhance tool call ID generation and improve readNote handling in prompts

Update readNote description for clarity and improve prompt instructions

* Update instructions for registerFileTreeTool to clarify usage guidelines

* Clarify instructions for readNote tool to improve context inference and handling of partial note titles

* Add token counter (#1889)

* Fix index rebuild on semantic search toggle (#1891)

* Update token counter label (#1892)

* Implement tag search v3 (#1893)

* feat: Enhance TieredLexicalRetriever to support tag-based retrieval and improve search scoring

- Added support for returning all matching tags in TieredLexicalRetriever.
- Introduced new options for tag terms and returnAllTags in the retriever.
- Updated FullTextEngine to prioritize tag matches and improve scoring for documents with tags.
- Enhanced tokenization to handle hierarchical tags and prevent splitting hyphenated tags.
- Improved handling of frontmatter tags in documents.
- Updated search tools to accommodate new tag handling features.
- Added tests to verify the functionality of tag-based retrieval and scoring improvements.

* fix: Improve logging for query expansion in SearchCore

* feat: Add explanation for non-tag matches in FullTextEngine search results

* feat: Enhance QueryExpander to preserve standalone terms in tag handling and improve term validation

* feat: Enhance QueryExpander and TieredLexicalRetriever to improve tag handling and standalone term extraction

* Update version to 3.1.0-preview-251006 in manifest.json

* Normalize tag queries for case-insensitive matching and improve search functionality (#1894)

* Implement merge retriever (#1896)

* Fix note read (#1897)

* Enhance note resolution logic and add tests for wiki-linked notes and basename matching

* Implement note resolution outcome types and enhance readNoteTool tests for ambiguous matches

* Add deriveReadNoteDisplayName function and enhance readNoteTool tests for edge cases

* Fix note tool UI freeze (#1898)

* Update how tags work in context (#1895)

* Enhance file creation instructions in modelAdapter and update tool usage guidelines in builtinTools (#1901)

- Added instructions for confirming folder existence before creating new files in modelAdapter.
- Updated custom prompt instructions in builtinTools to clarify the use of getFileTree for folder lookups when creating new notes.

* Do not add url context for youtube url (#1899)

* Update YouTube Script command and modal title to indicate Plus feature (#1903)

* Enhance ChatPersistenceManager to handle file save conflicts (#1904)

* Enhance ChatPersistenceManager to handle file save conflicts and improve epoch handling

* Fix type checking for existing files in ChatPersistenceManager to prevent errors

* Fix verify add (#1905)

- Rename "Verify" button to "Test" in Add Model dialog
- Make verification not required for adding model in Set Keys
- Upgrade chatAnthropic client to fix the Top P -1 error for Claude Opus models

* Enhance XML parsing to handle tool calls missing closing tags

---------

Co-authored-by: Wenzheng Jiang <jwzh.hi@gmail.com>
Co-authored-by: Zero Liu <zero@lumos.com>
Co-authored-by: Emt-lin <41323133+Emt-lin@users.noreply.github.com>
2025-10-10 21:38:50 -07:00