Skip to content

V2.0.1 - #123

Merged
deepagent-ai merged 134 commits into
devfrom
fix/project-switch-freeze
Sep 23, 2026
Merged

V2.0.1#123
deepagent-ai merged 134 commits into
devfrom
fix/project-switch-freeze

Conversation

@deepagent-ai

Copy link
Copy Markdown
Contributor

Issue for this PR

Closes #

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Please provide a description of the issue, the changes you made to fix it, and why they work. It is expected that you understand why your changes work and if you do not understand why at least say as much so a maintainer knows how much to value the PR.

If you paste a large clearly AI generated description here your PR may be IGNORED or CLOSED!

How did you verify your code works?

Screenshots / recordings

If this is a UI change, please include a screenshot or recording.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

If you do not follow this template your PR will be automatically rejected.

…vider config loop

Config.get() overlays the SettingsStore DeepAgent runtime settings as a
settings-only pseudo provider.deepagent entry so gateway/intelligence
readers keep their historic options location. The provider config loop
treated that entry as a custom configured provider, so any user with
DeepAgent settings on disk got a ghost connected provider (19 catalog
models, source "custom") in the model picker without ever connecting.

Skip the entry when it carries no apiKey, no baseURL and no models.
Real connections via the auth key store ("api"), DEEPAGENT_API_KEY
("env"), or a genuine config/model-scoped entry are unaffected.
…et upgrades reach the handler

When a workspace is active, the workspace-routing middleware proxied /pty/*
requests (including WebSocket upgrades for /pty/{id}/connect) to the
workspace server. The workspace has no PTY handler, so the upgrade silently
dropped and the terminal timed out with 'Terminal creation timed out'.

Adding /pty to the local-rules table keeps all PTY REST and WebSocket
traffic on the control-plane process that owns the PTY sessions.
…lity egress

Wire-form rows (epoch millis, not Date instances) failed the Schema.is
guards in compatibilityEvent, silently skipping the legacy info rebuild.
Clients then received native V2 session shapes without a top-level
directory, which crashed the desktop sidebar sort in pathKey.
…shing

A session payload without a directory reached sortedRootSessions and the
undefined access took the whole layout down via the error boundary.
Return a neutral key so the row simply never matches a root.
Pending reviews carry a null diagnosis; the table mapping crashed the
whole command on 'review.diagnosis.status'.
@deepagent-code/plugin is not published to the registry, so the
unconditional per-config-directory install failed with a 404 on every
instance boot, and Plugin init blocks on waitForDependencies whenever
plugins exist — stalling instance-scoped requests (config.get,
provider.list) behind serialized doomed registry round-trips and
freezing project switches on slow networks.

- config: fork the install only for directories that actually host a
  file-based plugin (auto-discovered plugin(s)/*.{ts,js} or a declared
  file:// spec located under the directory); npm-packaged plugins
  install through Npm.add and never needed this.
- npm: cache registry 404s per (directory, packages) for the process
  lifetime so a repeated request skips the round-trip; the first
  failure still surfaces for diagnostics.
fff logs a per-file diagnostic line at debug level; on repos where its
git index queries systematically fail this flooded gigabytes of logs
(a 1.1GB / 4.1M-line fff log was observed). Clamp the forwarded level
to info when the app runs at DEBUG.
The watcher consumer is forkScoped, so disposing an instance interrupts
it — the satisfied end state, logged as WARN 'Location index runtime
stopped' with an opaque '[object Object]' cause on every project
switch. Swallow interrupt-only causes and pretty-print real failures.
A dispatch refused because the event type is not in the V2 admission
registry (e.g. the daily schedule.scan until its product lane ships)
can never succeed on retry, but was nacked through the full retry
budget into the DLQ with an ERROR per attempt. The bridge now raises a
typed EventNotRegisteredError and the dispatcher settles it as a
terminal drop (recordDrop + ack).
Drop unrouted /:dir/agent and /:dir/review pages, the orphaned
side-panel-oversight/side-panel-mcp wrappers, and the right-sidebar
MCP rail that duplicated the top-bar StatusPopover (V2.0.1-001 WS1 P0).
RejectedError/CorrectedError/DeniedError carried no message, and 15
tool mapError sites rewrote them to generic "Unable to ..." text, so
the model could not tell a user denial (or its feedback) from a
technical failure. Port the V1 wording onto the V2 error classes and
pass it through via a shared permissionFailureMessage helper
(V2.0.1-001 WS5/B1).
History rebuild discarded state.result for non-provider-executed tools
and synthesized an {error, content, structured} object that protocols
stringify to the literal "[object Object]", so every local tool error
(including permission denials) became unreadable after a history
reload. Reuse the persisted ToolResultValue when present
(BUG-V2.0-003, V2.0.1-001 WS5).
The UI hid reverted messages client-side while the model kept
receiving them, and goal-loop rollbacks rewound the workspace
silently. Publish an idempotent durable Synthetic notice (deterministic
ID keyed on session+mutationEpoch, resp. goal+tick) so the model can
re-calibrate. Autonomous paths pass notice:false so only the
actor-accurate rollback notice lands (V2.0.1-001 WS6 C2/B5).
Knowledge Governance + Repo&Wiki + Domain Packs merge into one
Knowledge dialog (review/wiki/packs tabs); Help moves to a Settings
About tab; Plugins becomes a Settings tab; Debug + Profiler + Context
evidence merge into one Dev panel; Usage&cost moves into the
SessionContextUsage dialog with an All-sessions tab; Browser rail is
desktop-only. Legacy stored panel modes fold to the new modes
(V2.0.1-001 WS1).
The duplicated TUI/app formatters move to @deepagent-code/sdk/transcript
as the canonical formatter for TUI /export, the GUI session.export
command, and the CLI. Adds file/patch/subtask/compaction part coverage,
message timestamps, title-based filenames, no-overwrite dedupe, a
visible GUI menu entry, and deepagent export --format md
(V2.0.1-001 WS2).
The compaction checkpoint wrapper now carries survival rules telling
the model to re-verify state with tools, and turn prep injects an
ephemeral volatile budget notice at 70%/90% of the same occupancy
estimate that drives compaction. normalizeState no longer resets an
explicitly configured maxTotalTokens (V2.0.1-001 WS6 B3/B4).
V2.0.1-001 WS3+WS4b:
- WS4b: task_status/task_read (read-only), task_close/task_recovery,
  pr_finalize (thin over core TaskPRReview); task output gains branch
  visibility, an 8k tail-biased bound with a task_read pointer, and an
  optional file_scope with non-blocking sibling overlap warnings;
  task_timeout now retains the worktree (resumable) instead of pruning
  it while telling the parent to resume.
- WS3: pack_search + domain_pack_load (per-turn cap 2, ~600-token
  bodies, session dedupe) make the 136 seeded domain packs reachable
  by the model; the inactive capability-load prototype is removed.
- Registration gates (builtins/inventory/readOnlyActions/tool-surface
  guard) updated for all seven new tools.
V2.0.1-001 WS3/WS4a content wave:
- New deepagent.dac-manual domain pack: 19 indexed documents
  (orchestration, code intel, context survival, permissions,
  reliability) plus 2 failure dossiers, each 339-410 tokens in the
  five-part manual format, discoverable via pack_search and loadable
  via domain_pack_load.
- L0 capability catalog grows 6 -> 15 manifests (task orchestration,
  oversight, worktree merge loop, manual chain, plan/question/skill,
  permission-denial and context-survival concept rows); approved L0
  budget raised 700 -> 1000 tokens, measured render 727.
- task.txt rewritten (45 lines): full roster, worktree/merge-loop
  semantics, file_scope, guardrails, timeout/recovery, result bound.
Tool failures from permission rejections now carry
metadata.failureCode (user_rejected_permission /
user_corrected_permission / permission_denied_rule) through the
registry settlement into the durable Tool.Failed event, whose
error.type narrows to permission_rejected / permission_corrected /
permission_denied. Model-visible text is unchanged; the five protocol
lowerings are untouched (V2.0.1-001 WS5 phase 2).
knowledge_propose gives the model a "remember this" write entry that
stages a human-review candidate (sensitivity scrub, 10/session cap);
im_send delivers a message to the session's bound IM group behind an
ask-by-default permission, AgentPushPolicy gates, and an audit row.
Both join the L0 catalog (827/1000 tokens) and the dac-manual pack
(V2.0.1-001 WS7 B6/C4).
Classify the nine V2.0.1 tools (task_status/task_read/pack_search/
domain_pack_load read-only; task_close/task_recovery/pr_finalize/
knowledge_propose/im_send mutating) so the plan gate covers the whole
builtin registry again, and re-pin the C7-10 manifest digest to the
current tree (the previous pin was already unreproducible on
origin/dev; the contract group's L0-budget literal drifted in
a7d8ed2).
…udget exhaustion

bug-V2.0-003: the V2 foreground task tool dropped the 1.0 degraded escape.
finalizeStructured hard-failed the parent turn when both bounded finalizer
attempts exhausted; the 1.0 finalizer settled degraded instead. Port the
degraded settlement: the exhausted loop returns a receipt-stamped
{_degraded,_reason,_attempts,_raw} payload (structured_output_missing /
structured_output_invalid) and seals a durable validation_failed row in
session_v2_structured_output_evidence for fresh durable launches. Attempt
budget, prompt shape, the validated success path, and the timeout hard
failure are unchanged.
…roducibility

K-05/B-11: the C7-10 manifest digest historically read the git-ignored
packages/core/.artifacts/ tree (caller-inventory report, perf baseline), so a
machine that had run those tools pinned a digest no clean checkout could
reproduce (O-TEST-3 / O-W12-3).

- manifest.ts: the input set is now closed over committed, git-tracked files
  (contract / migration registry / package versions / runtime flag-config),
  itemized in the exported ManifestInputRoots; the includeExternalEvidence
  option and the live .artifacts reads are gone. External evidence enters only
  as explicit content-addressed extraInputs, which is how the candidate-ledger
  generator already binds the freshly produced inventory report.
- head-pin.ts: the pinned HEAD digests move to a single machine-readable source
  of truth shared by the C7-10 test, the reproducibility gate and the
  docs-claims gate. Values are unchanged: the pin 001f5b1 recorded was
  already computed over the committed-tree-only set.
- assert-reproducible.ts: new gate asserting byte-stable double generation,
  pin match, no untracked/ignored .ts under the input roots, input-set closure
  from exactly the listed files, and immunity to a planted .artifacts decoy
  tree. Wired into CI as the manifest-digest-gate job in test.yml.
- script/assert-docs-claims.ts: new root gate (assert:docs-claims) that
  machine-checks release evidence products — deterministic-manifest internal
  consistency plus referenced-input existence and content hashes, evidence
  manifest decode and (at HEAD) file-backed digest recomputation, and the full
  authoritative-ledger digest chain.
The column stopped being a timestamp long ago: it carries the V2 execution
claim's random CAS fence token, and every production reader already uses the
claim alias (current_session_claim_token, executionClaimToken). Rename the
column and its partial index to make the semantics unambiguous.

- migration 20260922152631_execution_claim_token: ALTER TABLE session
  RENAME COLUMN, then DROP session_time_suspended_idx + CREATE
  session_execution_claim_token_idx (RENAME COLUMN does not rename indexes);
  partial-index semantics (WHERE ... is not null) preserved
- drizzle snapshot advances with the rename recorded; registry resealed
- mechanical rename across src and tests (138 references); the frozen
  20260803000000_time_suspended migration, its registry entry, and its
  isolation test keep the old name
- new static gate (session-claim-token-gate.test.ts): only
  session/store.ts and session/runner/recovery-durable-store.ts may write
  the column; any other SessionTable update or raw-SQL write of
  execution_claim_token fails the gate
…on compaction

POST /api/session/:sessionID/compact previously admitted no payload, so the
handler called SessionV2.compact without a model and always hit the typed
explicit-model refusal, surfacing a permanent 503. The contract now requires
providerID/modelID (mirroring the instance summarize payload), the handler
passes them through, and the 503 body carries the concrete refusal reason
(empty history, unsettled budget, terminal outcome). A missing model is a
typed 400 contract rejection; the server never defaults an identity.

Also retire stale comments that still referenced the removed host-side
CurrentManualCompaction seam; they now describe the durable RI-18
CompactionRequest chain.
…nment, gzip stream errors, fixture dedup

F-2: advisory-sourced reclaim candidates are re-validated against the live
scan's containment + residue-shape rules — a tampered or corrupted advisory
JSON can no longer widen the delete set beyond dataRoot (regression test
stages a real chain, tampers the advisory, asserts the outside path survives
confirm:true).

F-3: every stream in the gzip archive chain gets an error listener — a
mid-stream failure now surfaces as the typed archive_failed instead of an
unhandled 'error' event crashing the maintenance process (regression test
replaces a backup file with a directory).

Also dedupe the compaction-request CREATE lines the k01 merge doubled in the
two handcrafted inventory fixtures (both sides had added the same table).
F-3 regression asserts the corrupted source stays in place (a partially
created destination artifact races the error and is acceptable); the
provider-resolution facade joins the recovery route's owning paths (route
totality); the export help snapshot gains the --format md option the
V2.0.1-001 WS2 export wave added.
… env whitelists

The child-command whitelists in run-live-llm-all and pre-push-live-llm dropped
HOME: bun install children lost ~/.bunfig.toml AND the global bun cache, so a
warm no-op install became a full cold download through the default registry —
on networks where that registry is unreachable the setup gate hangs to its
20-minute timeout. HOME plus BUN_CONFIG_REGISTRY now flow through; the pinned
prePushEnvironment test covers both.
The V2 port rendered 'Command exited with code N.' — validation classifiers
regex on the canonical /exit code: N/ form (V1 shell.ts ground-truth trailer)
and read its absence as a missing success marker; the live shell-exit-contract
suite failed on every bash result. Align the trailer wording (single line, last,
null renders 'null (terminated)') and pin the unit expectations.
The journal-to-V1-wire projection filled part.state.metadata from
provider.resultMetadata alone, which is absent for every locally-executed
tool, so every projected V2 session serialized empty tool metadata. The
bash exit code disappeared from the CLI run output (scrollBashFinal reads
metadata.exit) and the live battery lost every truncation/exit assertion
(bash-truncation metadata {}, bash-repair exit 17, shell-exit-contract
Oracle 3).

Project the completed ToolState's durable structured output as the
metadata authority, alias exitCode to the legacy exit vocabulary every V1
consumer reads, surface ToolOutputStore truncation through the legacy
truncated/outputPath keys, and keep the provider channel (plan protocol
riders) as the override layer on explicit key collisions.
The headless suite spawned the from-source CLI without any V2 owner or
roster wiring, so after the 2.0 cutover (V2 owner gate default-on, V2
agent-roster admission) every run refused before the first provider turn:
the entry dev-mint skipped itself (InstallationVersion "local" is not a
0.0.0- dev stamp) leaving v2_owner_unavailable, and once armed the
AgentV2 roster was empty because DEEPAGENT_CODE_CONFIG_CONTENT only
reaches the V1 app config while the Core Location config reads config
files.

- pin DEEPAGENT_CODE_V2_OWNER_DEV_MINT=1 (same pin as
  test/lib/cli-process.ts) so the entry bootstrap mints the dev owner row
- write the workspace deepagent-code.json so the Location config (and the
  roster admission resolves through) sees the live-test agent, mirroring
  the in-process fixture
- accept the V2 read tool's relative `path` input alongside V1's absolute
  `filePath` in the canonical read assertion
The CoreTaskTool finalizer built its durable-evidence input with a
misnamed property — { outputMessageID } against the authority's
outputMessageId field. The conditional spread defeated excess-property
checking and the optional field dropped silently, so every 'validated'
record inserted output_message_id: NULL and the insert guard aborted
with 'invalid v2 structured output evidence' (previously misread as a
UNIQUE(run_id) race). live subagent-foreground hit that abort on every
deepseek run, and the tool's retry guidance re-ran into the same abort.

- tool/task.ts: pass the binding under the authority's field name; the
  parameter is renamed to match so the shorthand cannot drift again.
- session/task-run.ts: the evidence insert is now the UNIQUE(run_id) CAS
  in one statement (ON CONFLICT DO NOTHING plus a post-conflict
  re-read), so a writer landing between the pre-read and the insert
  converges or diverges typed instead of surfacing a raw constraint
  error. The recorded verdict is still never overwritten and divergence
  stays the typed StructuredEvidenceConflict.
- tests: a validated finalizer candidate now seals bound evidence
  (regression for the misnamed field), and concurrent double-writes of
  the same run converge on one row with divergence still typed.
Port the V1 #26514 subagent permission-narrowing semantics
(deepagent-code deriveSubagentSessionPermission) to the core V2 task
path, role-aware like the V1 original:

- the parent AGENT contributes only its edit rules (deny/ask forward as
  deny, allow forwards verbatim) — the parent agent's broader working
  posture (a blanket deny-all plus narrow task-tool allows) governs the
  parent's own tools and must not strip the delegated agent's built-in
  allow face;
- the parent SESSION forwards deny rules verbatim as hard runtime
  ceilings and ask rules as deny, while an ALLOW forwards only when it
  names a concrete non-wildcard action whose (action, resource) pair the
  delegated agent's own ruleset already permits independently, order
  preserved.

Previously every parent non-allow rule forwarded as a child deny, so the
recommended deny-all parent posture derived a bare deny-* child ruleset:
every tool definition was filtered out (isActionWhollyDenied) and the
child session ran with zero tools. Delegation still can never turn
ask/deny into allow: a forwarded allow cannot grant anything the
delegated agent's own ruleset does not already permit, and deny-wins
evaluation keeps the child's own denies final.
…eground

The task_read input schema is provider-tolerant (limit: number | numeric
string), so a model sending limit "100" is a valid complete-transcript
request; compare numerically instead of strictly.
… V2 contract

The V2-owner cutover (6f171ea61) deleted the legacy session-metadata
projection: deepagent.subagent state, the session-level child model
stamp, the structured_finalizer user metadata, and the StructuredOutput
synthesis for finalizer prompts are no longer written — the durable
task_run row is authoritative and the core finalizer rides the schema
in the prompt text.

Assert the same facts through the surviving durable surfaces: the task
call returns the schema-validated ResearchResult (parsed from the task
output), the child identity rides its assistant messages, and the
finalizer prompt is observed by its instruction text with the final
answer parsed as the ResearchResult JSON. Also drop the unused
nested-record helpers.
…onRuntimeHost seam

The keyed Location tree self-provided ContextQueryAuthorization.defaultLayer, but
Layer.fresh gives every tree a private memo map — so the runner's admitSelection
binds landed in a tree-local store while the host-captured CodeIntelFacade /
ContextQueryFacade instances resolved from the app-root store. In production the
split meant code_intel / context_query could never see a bound envelope and
always answered authorization_unavailable once the ContextToolRuntime seam was
available.

Move the store to the LocationRuntimeHost seam (like ProductionV2Sources and
ContextToolRuntime): the core default host keeps a per-tree store (bare-core
behavior unchanged), and the production runnerFrameHost captures the ONE
memoized ContextQueryAuthorization.defaultLayer build that also feeds the
facades' internal requirements, handing it to every keyed tree.
…ery authority

The live harness host provided ProductionV2Sources as a bare {} seam, so the
selection admission built authorization envelopes on the v2:local degradation
frame while the code index answers with the real instance identity — code_intel
returned scope_denied even with the authority store shared. Mirror production's
runnerFrameSeamFor: the empty base stays (the harness drives no production
context adapters) but the per-ref REAL instance identity is attached, and the
shared query-authority store is captured with the facades exactly like
runnerFrameHost does.
… the LocationRuntimeHost seam + harness production identity
…adds

Four late campaign worktrees swept their isolated live-test homes (.tmp/,
3020 knowledge-doc files) into their branches; the merges carried them into
the tree. Untrack and ignore .tmp/ at any level.
@github-actions

Copy link
Copy Markdown

Hey! Your PR title V2.0.1 doesn't follow conventional commit format.

Please update it to start with one of:

  • feat: or feat(scope): new feature
  • fix: or fix(scope): bug fix
  • docs: or docs(scope): documentation changes
  • chore: or chore(scope): maintenance tasks
  • refactor: or refactor(scope): code refactoring
  • test: or test(scope): adding or updating tests

Where scope is the package name (e.g., app, desktop, deepagent-code).

See CONTRIBUTING.md for details.

@github-actions

Copy link
Copy Markdown

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • No "Type of change" checkbox is checked. Please select at least one.
  • No issue referenced. Please add Closes #<number> linking to the relevant issue.
  • "How did you verify your code works?" section is empty. Please explain how you tested.
  • Not all checklist items are checked. Please confirm you have tested locally and have not included unrelated changes.

Please edit this PR description to address the above within 2 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

Keep only the newest snapshot (20260922182457) as the drizzle-kit
generation baseline; intermediate snapshots from this PR's earlier
migrations add ~37k review lines with no consumer (apply path reads
migration.sql via the registry; generation diffs against the newest
snapshot only). Follows the release-PR slimming precedent.
@deepagent-ai
deepagent-ai merged commit 8138b86 into dev Sep 23, 2026
3 of 16 checks passed
@deepagent-ai
deepagent-ai deleted the fix/project-switch-freeze branch September 23, 2026 08:15
deepagent-ai added a commit that referenced this pull request Sep 23, 2026
lxrzlyr pushed a commit that referenced this pull request Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant