Skip to content

Feat/v2.0.2 - #130

Merged
deepagent-ai merged 299 commits into
devfrom
feat/v2.0.2
Sep 26, 2026
Merged

deepagent-ai merged 299 commits into
devfrom
feat/v2.0.2

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.

…talling

GUI test D1. When the intelligence refinement model call fails (bad key,
weak model output, hung stream), the composer used to lock in its
preparing state with no error surface and the message was never sent —
session_input gained no row while the editor sat disabled.

- prepare SSE reads now race an idle deadline (90s default, injectable
  for tests) so a stream that never emits its terminal event degrades
  instead of hanging forever
- the W1-3 degrade path shows a toast (Intelligence unavailable — sent
  directly) carrying the underlying error
- stopDraftPrepare returns focus to the editor when no draft surface
  remains
- tests: degrade-toast assertion + stalled-stream timeout case (timeout
  shrunk to 200ms via the new setter); toast calls recorded in mocks
…g entries

GUI test D2. Upstream models.dev renamed the Kimi subscription faces
(kimi-for-coding -> kimi-code-plan-cn), leaving the fixed official id
with no catalog entry: a key-store credential for kimi-for-coding
merged into nothing — the connection silently did nothing, with no
error anywhere (the orphan warning skips official ids).

- OFFICIAL_PROVIDER_CATALOG_ALIASES maps drifted official ids to their
  catalog successors; officialProviderCatalogID resolves per id
- the provider loader re-homes each aliased catalog entry under its
  official id before key-store merges, so credentials, the picker, and
  auth writes keep the official identity
- tests: alias invariants (maps official -> non-official, pass-through
  for everything else) in core; verified live via refresh returning the
  full kimi model list under the official id
GUI test D3. Both config loaders read the same files, but providers
(the Core V2 catalog schema consumed by ConfigProviderPlugin) is not in
the deepagent-code V1 schema: any file carrying it failed the whole
instance config load with ConfigInvalidError (HTTP 500), while a pure
V1 file was invisible to the V2 catalog.

- normalizeLoadedConfig strips providers before V1 validation (the V2
  loader keeps consuming it from the same file); the strict legacy
  migration parser takes the same path so a valid V2-only file no
  longer blocks consolidation
- the raw schema still rejects providers — only the loader strips it,
  so genuinely unknown keys keep failing loudly
- test: loader strips the key while raw parse still throws
…ialog locks

GUI test D4. The settings dialog variant renders headerless (no title),
which also hid the only Close button — closing degraded to outside
clicks only. Escape also failed to close dialogs in practice: the
window-capture Escape handler races Kobalte's own modal handling and a
stale close lock swallowed every later press.

- the settings variant now always renders the header Close button
  (absolutely positioned; tab layout unchanged)
- dialog close lock self-heals when no close is actually pending, so a
  raced dispose can no longer swallow Escape forever

Remaining (tracked): Escape on the settings dialog still routes through
Kobalte's focus scope and does not close; the reliable path is a direct
onKeyDown on Dialog Content — follow-up with a DOM-level test
…e app

Found during GUI retest: providerResolutionList was a bare fetch inside
createResource. After switching servers, an in-flight request bound to
the dead server rejected with TypeError: Failed to fetch, which bubbled
straight through the top ErrorBoundary — the whole app rendered the
error page, and reloading the session route replayed the same crash in
a loop (the tab was effectively bricked).

- the list fetch is wrapped in retry (3 attempts, transient errors);
  network failures land in recoveries.error, rendered by the dock's own
  loadFailed banner with a Retry button instead of the global error page
- rename the imported retry to retryFetch: the component's local retry
  handler shadowed the module binding and broke inference
The D2 bridge moves from the dc provider service down to ModelsDev.mergeVendored,
the single choke point every catalog consumer reads: the V1 provider loader's
database, the V2 Catalog via ModelsDevPlugin, and the refresh handler. The V2
prompt path (SessionRunnerModel.resolve) previously still failed with
CatalogV2.ProviderNotFound for kimi-for-coding despite the dc-side fix.
The D1 toast never fired for server fail-soft degrades: the server returns a
normal route:general result (no exception), so the client catch path was dead
code. Both server degrade points now set degraded:true on the result, and the
client keys its degrade toast off the flag. The SSE read loop also gets a
per-chunk timer (the old code left one 90s timer per chunk) and cancels the
reader on error or a never-terminal stream.
A mixed file (V1 provider keys + the V2 catalog providers key) classifies as
V1, and decodeV1Info runs with onExcessProperty:error — the V2-only key killed
the whole location config load on the core loader too, not just the dc loader.
Strip it before the V1 decode; the post-migration decodeInfo re-admits it.
Live-browser verification traced the '⌘K/Settings Escape sometimes dead'
defect to the 100ms close window: the closing entry stays on the stack, so
command.tsx's dialog.active guard swallowed every reopen pressed in that
window (0/3 reproducible, felt as the keybind doing nothing). dialog.active
now skips entries already closing, and push/settle finish a pending close
(dispose + unstack) instead of cancelling its timer, so a dialog mounted
during the window can never share the stack with a dead one. The ineffective
pendingCloseID self-heal from the earlier D4 commit is removed.

Verified with real CDP Escape presses: reopen-at-30ms went 0/3 before the
fix to 3/3 after, for both the palette and the settings dialog.
…led banner

Verified live in a browser: with a dead default server the app rendered the
business UI anyway — infinite 'Loading' in the main area, every dialog
fetching into a dead connection, and a 'Maintenance status unknown' banner
that misnamed the incident (the maintenance endpoint being unreachable means
the SERVER is offline, not that maintenance protections are down).

The bootstrap gate now splits unreachable from maintenance:

- offline (cold boot, this server never reached ready): full offline screen
  naming the server, automatic 3s re-probing, and one-click switching to
  another configured server. No more hanging app.
- offline_after_ready (the app already booted against this server): keep the
  app mounted — tearing it down would discard drafts and scroll position —
  and show a 'Server connection lost, reconnecting' banner until the probe
  succeeds.
- recovery is automatic: the moment the server comes back the 3s probe flips
  the gate straight to the app (verified live: kill + restart, no user
  action, module state intact).

The maintenance/migration shells are untouched: typed 423/503 bootstrap
envelopes still route there exactly as before.

Note on live verification of offline_after_ready: this browser environment
force-reloads the page when the backend connection dies (evidence: navType=reload
with no JS-initiated reload, sessionStorage partition reset), so that branch is
covered by unit tests on the extracted offlineGateState decision instead.
…e dispatch

2e5c87d promoted steer/queue inputs before the system-context load so a
first prompt's model and agent resolve from the session row (the desktop
first-question fix), but that consumes the inputs at a point where the turn
can still fail before dispatch: with the system context unavailable, or a
concurrent Session move mid-load, the admitted input was already promoted
and the 'turn failed, input still pending' contract broke — the retry after
recovery never resent it.

Keep the first-question fix and restore the contract:

- resolve the model/agent against a NON-consuming peek of the next promotion
  batch (SessionInput.peekActiveSelection), mirroring the projector's
  last-wins agent/model activation so the resolution matches what promotion
  will activate (including the session-row variant normalization)
- run the promotion at its original safe boundary — after the epoch
  initialize survived, before the epoch prepare reads the baseline sequence
- re-validate the post-promotion row against what the attempt prepared with

session-runner suite 171/171 (both regressions green, queue-model chain and
baseline-reuse oracles intact).
The RI-94 inventory gate failed on the current tree: several reviewed
bindings' source anchors had drifted with 2.0.2 edits (permission/index.ts
line shifts, dc config/provider/provider.ts edits, core runner/llm.ts), so
the scanner derived new anchors while the reviewed table and the test's
reanchored list kept stale keys — candidates silently fell to
review_required and the pinned reanchored lookups came back undefined.

Repinned every dead anchor to its live counterpart (liveness-verified
against the scanner output before each rename): runner/llm.ts x2,
dc config x3, dc provider x4, plus the test's four stale entries.

Adjudicated the one genuinely new binding this exposed:
submit.ts:prepareStreamIdleTimeoutMs (the D1 SSE idle-timeout test-injection
seam — module binding, constant production default, test-setter-only
mutation). Backlog returns to the pinned three.
@github-actions

Copy link
Copy Markdown

Hey! Your PR title Feat/v2.0.2 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.

@deepagent-ai
deepagent-ai merged commit adbeda9 into dev Sep 26, 2026
10 of 34 checks passed
@deepagent-ai
deepagent-ai deleted the feat/v2.0.2 branch September 26, 2026 19:04
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