Compare commits

...
Author SHA1 Message Date
Turtle 46a7f68b09 Merge pull request #5073 from deepseek-harness/rel/dsh-0.1.7-rc.1
release(dsh): 0.1.7-rc.1
2026-09-23 21:03:33 +08:00
turtle1999 a60af51e80 release(dsh): 0.1.7-rc.1 2026-09-23 20:47:45 +08:00
Turtle 07ad70817f fix(plugins): deny incompatible bundles and clarify compatibility refusals (#5061)
* fix(plugins): deny incompatible bundles and clarify compatibility refusals

A bundle package is not a plugin row, so startup admission never read its
own DSH peers: a bundle installed outside DSH or before a DSH upgrade still
applied its patch layer. loadProfileDirectory now skips such a bundle like an
unreadable one unless the profile exempts that exact pair.

A post-install refusal in a profile without a lockfile reinstalls from the
restored manifest instead of failing a frozen install and leaving the
rejected packages in node_modules.

Compatibility warnings now name the `dsh plugin allow-version` command for
CLI-managed profiles, startup denials name the row id instead of a resolved
module URL, and the CLI no longer reports "pnpm failed" when pnpm never ran.

* feat(plugins): report incompatible versions as typed refusals

Compatibility checks now yield the refused packages as data instead of a
profile-specific sentence. runProfilePnpm returns them as
PackageResult.incompatible, and the plugin manager refuses with the
incompatible-version code carrying the same records. Each surface renders
them: the CLI prints the exact allow-version command for its profile, the
Web plugin manager words them through its locale dictionary, and app-boot
keeps one neutral English diagnostic for logs and startup stderr.

* fix(plugin-manager): drop the agent exemption hint from the incompatibility reason
2026-09-23 20:44:06 +08:00
imccyu c76217a356 Merge pull request #5053 from deepseek-harness/feat/tool-preparing-progress
feat(chat): add work detail modes and file preparation progress
2026-09-23 20:28:16 +08:00
imccyu 490f569044 Merge pull request #5067 from deepseek-harness/revert-5062-ihsiang/chat-flow-visual-polish
Revert "fix(web): 恢复蓝色深度求索动画并优化过程折叠展示"
2026-09-23 20:22:22 +08:00
imccyu 7c8a44ac31 Revert "feat(web): restore the process-row polish on the grouped work details"
This reverts commit 036d53fb612c785a17d9e6068f58b155d87120c1.
2026-09-23 20:21:46 +08:00
ihsiang b09dd94827 feat(web): restore the process-row polish on the grouped work details
* feat(web): restore the process-row polish on the grouped work details

The sweep highlight, shared row colors, expanded-row spacing, and the
12–24px font-size range were developed against the pre-revert work-details
implementation. Re-apply them to the grouped implementation that replaced it.

- TextShimmer keeps its active-driven span and now paints a masked sweep
  overlay in the theme's `--dsw-alias-label-shimmer` color, so the base text
  inherits the caller's color and hover changes.
- DisclosureRow owns the row color: tertiary at rest, secondary on hover,
  with glyphs, titles, and summaries inheriting it; tool, skill, command,
  reasoning, and workflow rows drop their own hover darkening.
- ChatView spaces process rows 8px apart and keeps 16px beside Assistant
  responses; the grouped header sits 8px above its first member.
- The Settings font-size stepper accepts 12–24px, and the grouped header's
  leading box and glyph scale with the content font-size delta.

* fix(web): restore blue running status and refine process disclosures

* fix(web): finalize running snapshots and whale animation

* fix(web): preserve disclosure layout and repair CI fixtures
2026-09-23 20:10:50 +08:00
turtle1999 f64c57dbb2 fix(plugin-manager): drop the agent exemption hint from the incompatibility reason 2026-09-23 20:04:31 +08:00
turtle1999 51d70c5f5c feat(plugins): report incompatible versions as typed refusals
Compatibility checks now yield the refused packages as data instead of a
profile-specific sentence. runProfilePnpm returns them as
PackageResult.incompatible, and the plugin manager refuses with the
incompatible-version code carrying the same records. Each surface renders
them: the CLI prints the exact allow-version command for its profile, the
Web plugin manager words them through its locale dictionary, and app-boot
keeps one neutral English diagnostic for logs and startup stderr.
2026-09-23 19:51:29 +08:00
imccyu 96a4607ba2 test(chat): refresh work detail snapshots and preparation progress 2026-09-23 19:48:45 +08:00
imccyu 6943f9fee6 fix(chat): satisfy hook documentation and lint contracts 2026-09-23 19:48:45 +08:00
imccyu 5a32aae571 docs(chat): align preparing hooks and work detail modes 2026-09-23 19:48:45 +08:00
imccyu 1b55f5bea3 fix(chat): retain preparing tools and ordering until step completion 2026-09-23 19:48:45 +08:00
imccyu 4987e90b32 fix: static 2026-09-23 19:48:45 +08:00
imccyu ac77cefba7 test(chat): cover work detail modes and preparation subscriptions 2026-09-23 19:48:45 +08:00
imccyu d6fb1351b3 fix(chat): refine verbose headers and file preparation rows 2026-09-23 19:48:45 +08:00
imccyu 015a9b202c feat(chat): add four work detail modes and legacy mappings 2026-09-23 19:48:45 +08:00
imccyu b660f72d4e feat(tool): show content preparation progress for file mutations 2026-09-23 19:48:45 +08:00
Yudong Han fb8bb16345 Merge pull request #5052 from deepseek-harness/fix/zoom-rerender
fix(preview): redraw PDF and Office pages after pinch zoom
2026-09-23 19:42:44 +08:00
yudshj 312341970c Merge master into fix/zoom-rerender 2026-09-23 19:31:59 +08:00
imccyu 9b510d89a5 Merge pull request #5029 from deepseek-harness/feat/chat-image-preview-4941
feat(chat): 图片链接悬停预览与内联图片放大
2026-09-23 19:27:02 +08:00
Yudong Han 896c81d796 Merge pull request #5047 from deepseek-harness/fix/excel-trackpad-dark-mode
fix(web): support free spreadsheet trackpad panning and readable controls
2026-09-23 19:26:26 +08:00
turtle1999 747c98b0db fix(plugins): deny incompatible bundles and clarify compatibility refusals
A bundle package is not a plugin row, so startup admission never read its
own DSH peers: a bundle installed outside DSH or before a DSH upgrade still
applied its patch layer. loadProfileDirectory now skips such a bundle like an
unreadable one unless the profile exempts that exact pair.

A post-install refusal in a profile without a lockfile reinstalls from the
restored manifest instead of failing a frozen install and leaving the
rejected packages in node_modules.

Compatibility warnings now name the `dsh plugin allow-version` command for
CLI-managed profiles, startup denials name the row id instead of a resolved
module URL, and the CLI no longer reports "pnpm failed" when pnpm never ran.
2026-09-23 19:14:19 +08:00
Turtle 5ea77078d6 feat(plugins): enforce DSH peer compatibility with exact exemptions (#4980) 2026-09-23 18:56:56 +08:00
turtle1999 2c67633990 feat(plugins): enforce DSH peer compatibility with exact exemptions 2026-09-23 18:46:08 +08:00
yudshj 19d6df2b2f fix(web): pan read-only spreadsheets on both trackpad axes 2026-09-23 18:13:19 +08:00
yudshj e0e8327992 fix(preview): redraw PDF pages after settled zoom 2026-09-23 18:12:21 +08:00
imccyu 7eb9629e0a Merge pull request #5041 from deepseek-harness/fix-gateway-mem
fix(gateway): bound stream cancellation retention
2026-09-23 18:10:58 +08:00
Turtle 4aa207c5fe feat(preset): add plugin practices reference to creator skill (#5025)
* feat(preset): add creator-mode plugin practices skill

Creator mode mounts packages/preset/agent-preset/skills. The new
cordis-plugin-practices skill states Harness-specific principles and
concrete rules for plugins that survive upgrades and avoid polling or
rescanning session logs: waterfall delegation, restrict/guard over
assemble, agent.ctx ownership, no new session event types from profile
plugins, session projections, and slot-based UI.

* refactor(preset): fold plugin practices into cordis-plugin-development

A separate skill competed with cordis-plugin-development for the same
trigger. The practices now live in references/practices.md, read from
procedure step 2 before choosing an extension point.

* feat(preset): require Harness components and tokens for plugin pages

A creator-built management page served as an iframe with its own
stylesheet could not match the Harness design. The practices reference
now requires slot-rendered React pages built from ui-primitives, theme
tokens from Theme inspection, and light/dark checks in the real slot.

* feat(preset): make host UI consistency a principle and a verification step

Adds principle 6: the rendering surface decides whether a plugin can
match the Harness UI, so it is chosen before any view. Step 4 of
cordis-plugin-development now verifies primitives, token-only styles,
and light/dark rendering beside a comparable host page.

* feat(preset): prefer theme tokens over pre-stable ui-primitives

ui-primitives is a pre-stable platform module; creator plugins are plain
JS without type checks, and a throwing component blanks the slot entry.
The practices reference now treats tokens as the default way to match
the host, limits primitives to behavior that is costly to reproduce, and
tells the agent how to detect a slot entry crash after an upgrade.

* feat(preset): forbid runtime imports of Harness Client packages in plugins

Creator plugins must assume ui-primitives and other Client packages break
without notice. The practices reference now has plugins copy markup, CSS,
and behavior from primitives, or inspect the rendered host control, and
keep --dsw-alias-* tokens as the only shared styling dependency.

* fix(preset): correct plugin practices against runtime behavior

- agent.ctx registrations need a plugin-owned disposer too; plugin
  unload does not remove them.
- Timers start work with followup(); inject() does not wake the agent.
- inject() logs agent/inbox/spliced at call time; agent/request cannot
  change messages.
- Guards are synchronous; awaited decisions return ask from
  tools/pre-execute.
- Chat node renderers are keyed by kind; slot inject reinstalls on
  declaration return.

skills.spec now keeps whole-file cordis-plugin-development references
below the pruner threshold as the read tool renders them.
2026-09-23 18:06:52 +08:00
yudshj e4234651ee fix(web): support spreadsheet tab gestures and readable name box 2026-09-23 17:52:13 +08:00
Xu Hanxiang c5dc0853d6 Merge pull request #4686 from deepseek-harness/fix/desktop-dev-runtime-target
fix(desktop): resolve the development runtime directory from the build target
2026-09-23 17:43:43 +08:00
Kaige-Gao bd2a49a2ca fix(chat): use elevated scrollbar colors for image previews 2026-09-23 17:22:55 +08:00
imccyu 45f5cd34b0 fix(gateway): pass explicit undefined in retention test 2026-09-23 17:20:38 +08:00
Xu Hanxiang 62aef4c583 Merge branch 'master' into fix/desktop-dev-runtime-target 2026-09-23 17:19:03 +08:00
Kaige-Gao 4839239954 fix(chat): address image preview review and coverage gaps 2026-09-23 17:12:03 +08:00
Ziya e68fb0bc8d Merge pull request #5022 from deepseek-harness/fix/subagent-zh-terminology
fix(i18n): 将 UI「子代理」改为「子智能体」
2026-09-23 17:08:26 +08:00
imccyu 4cfd292a7b fix(gateway): bound stream cancellation retention 2026-09-23 17:07:09 +08:00
ZiyaZhang dc0ae81905 Merge remote-tracking branch 'upstream/master' into fix/subagent-zh-terminology 2026-09-23 01:54:16 -07:00
mektpoy a8776afa87 Merge remote-tracking branch 'origin/master' into fix/desktop-dev-runtime-target 2026-09-23 16:43:00 +08:00
Yudong Han 219db5454a Merge pull request #5027 from deepseek-harness/fix/excel-preview-sheet-navigation
fix(web): separate spreadsheet tab navigation from zoom controls
2026-09-23 16:38:47 +08:00
ZiyaZhang 3675e91cf9 fix(i18n): update subagent labels added on master 2026-09-23 01:34:17 -07:00
Yudong Han 5e25475857 fix(office): use bundled LibreOffice Kit 0.1.0 CLI across deployments (#4902)
* chore(office): upgrade libreoffice kit to 0.0.1-2

* fix(office): guide bounded CLI rendering and preview reuse

* chore(office): consume published LibreOffice Kit 0.0.3

* test(office): mock the complete LibreOffice converter interface

* chore(office): upgrade LibreOffice Kit to 0.0.4

* fix(office): expose bundled LibreOffice Kit 0.1.0 CLI across deployments

* fix(office): inspect Excel visuals only for layout tasks

* fix(office): allow compatible LibreOffice Kit 0.1 updates

* fix(office): stop Excel QA on blank previews and run regressions in CI

* fix(office): end workbook visual QA after an empty preview
2026-09-23 08:32:15 +00:00
ZiyaZhang f4c6b012aa Merge remote-tracking branch 'upstream/master' into fix/subagent-zh-terminology 2026-09-23 01:32:15 -07:00
mektpoy fe4d18fda3 test(desktop): provide the required primary runtime directory in the welcome startup spec
`welcome-startup.spec.ts` imports the unpackaged main entry, which now
requires `DSH_DESKTOP_PRIMARY_RUNTIME_DIR`. Stub it beside the neighbouring
`DSH_DESKTOP_*` values so the suite reaches the welcome startup it covers.
2026-09-23 16:29:28 +08:00
Kaige-Gao 907a241b61 Merge remote-tracking branch 'origin/master' into feat/chat-image-preview-4941 2026-09-23 16:28:26 +08:00
imccyu e5e9b5b982 Merge pull request #4985 from deepseek-harness/feat/tool-call-preparing
feat(chat): show preparing tools during live generation
2026-09-23 16:24:46 +08:00
Kaige-Gao 7fe9c06fa6 feat(chat): preview image links on hover and expand inline images 2026-09-23 16:18:09 +08:00
yudshj c920174964 fix(web): separate spreadsheet tab navigation from zoom controls 2026-09-23 16:13:21 +08:00
imccyu 3776169373 test(client): align wait agent row title expectation 2026-09-23 16:11:44 +08:00
imccyu 19675fd061 fix(client): align subagent coordination and wait labels 2026-09-23 16:11:44 +08:00
imccyu 536764c8b4 test(web): align snapshots with write activity labels 2026-09-23 16:11:44 +08:00
imccyu b66b602c72 fix(chat): address preparing lifecycle review feedback 2026-09-23 16:11:44 +08:00
imccyu 913aa2289b fix(chat): unify tool lifecycle titles and activity labels 2026-09-23 16:11:44 +08:00
imccyu 701026b5bf test(chat): bind the preparation start spy 2026-09-23 16:11:44 +08:00
imccyu 5840898e07 perf(tool): retain unchanged subcall branches 2026-09-23 16:11:44 +08:00
imccyu b2817d77fb perf(chat): batch preparing tool updates 2026-09-23 16:11:44 +08:00
imccyu a60d4df027 test(desktop): await ASAR fixture output completion 2026-09-23 16:11:44 +08:00
imccyu 833835d95c test(chat): retire unknown casts from tool row fixtures 2026-09-23 16:11:44 +08:00
imccyu 74511b3dcb test(chat): cover live preparation and durable history replay 2026-09-23 16:11:43 +08:00
imccyu d3086e6571 feat(chat): present tools in preparing, start, and result stages 2026-09-23 16:11:43 +08:00
imccyu d550f1c4af feat(conversation): initialize contexts from transient start matches 2026-09-23 16:11:43 +08:00
Turtle ccaa0dc11c fix(plugin-manager): bound pnpm runs so a silent child cannot hold the profile lock (#4982)
* fix(plugin-manager): bound pnpm runs so a silent child cannot hold the profile lock

A pnpm child that prints its completion line and then never exits holds the
profile write lock until the host is restarted, so every other session and host
on that profile fails after its own lock timeout (issue #4981). execa resolves
its promise only once the piped stdio has ended, so a run now completes on the
process's own exit and drains the pipes under a bound instead of awaiting their
end.

A service run whose captured output stays silent for idleTimeoutMs is
terminated, reports timedOut alongside its exit status, and is not retried on
the next registry; a run with inherited descriptors captures nothing and is
left unbounded for its operator.

* test(plugin-manager): type the mocked child and await the drain descendants

The inherited-descriptor fixture no longer needs its legacy `as unknown` cast,
because the mocked child is typed for the fields the operations read; the
retired baseline entry is pruned (verify-no-unknown-casts).

The drain specs wait for the killed descendant to exit before removing its
directory: Windows refuses to delete a directory a live process still holds.

* test(plugin-manager): stop the drain descendant deterministically

The descendant that holds the pipes now ends on a stop file the teardown
writes, and on its own after a minute, so the fixture leaves no process behind
when a kill cannot be delivered. Windows stops it through taskkill, and the
spec waits for the exit before removing the directory it ran in.

* test(plugin-manager): run the drain descendant from its own script file

Windows cannot carry a nested `-e` script through another spawn argument: the
descendant failed to start there, so the fixture asserted on a process that was
already gone. The descendant now runs from a script file the fixture writes.

* test(plugin-manager): cover the bounded drain everywhere and stage the real descendant on POSIX

Windows does not stage a descendant that keeps the inherited pipes open past
the direct child's exit, so the real-process cases run on POSIX only. The
mocked child now models execa's own completion rule — its promise settles only
once both pipes end — which covers the bounded drain on every platform and
still fails without the raw-exit race.

* test(plugin-manager): cover the drain failure path without a child process

An output consumer failure that the bounded drain cuts short is staged with the
mocked child as well, so the per-file coverage gate also holds where the
real-process cases are skipped.

* fix(plugin-manager): terminate the whole tree of a stalled run

A lifecycle script is a child of pnpm, so signalling pnpm alone left an
approved postinstall writing into the profile after the operation reported a
timeout and released the lock. A stalled run now stops its process group (or
Windows process tree) and waits for it to disappear before the caller restores
the files. The cut tail's close is told apart from a reading failure by where it
happened, so a failure from before the cut still surfaces.

* docs(plugin-manager): regenerate the config catalog after the rebase

The English catalog gains `idleTimeoutMs` from the regenerator, and the Chinese
counterpart carries the same interface line so the pair stays consistent.

* fix(plugin-manager): terminate a run's tree through execa, keeping exit cleanup

Spawning a service run with `detached` made execa skip the handler that kills
the child when the manager exits, so a crashed manager left the pnpm tree
running in the profile. `killDescendants` spawns the run as its own process
group while keeping that cleanup, and it addresses the tree on every kill path,
including cancellation. The module now only decides the probe target and waits
for quiescence.
2026-09-23 16:11:30 +08:00
Jiawei Song 19844ea9ad Merge pull request #5001 from deepseek-harness/fix/diff-review-colors
fix(web): 优化文件 diff Review 的浅色和深色配色
2026-09-23 16:09:33 +08:00
ZiyaZhang a2278b460c fix(i18n): limit subagent terminology changes to requested UI copy 2026-09-23 01:09:27 -07:00
mektpoy 80e4da0e3d fix(desktop): take the development project target as an explicit input
`prepareDevelopmentProject()` resolved the build target from the host
platform and architecture, so a Linux host threw `unsupported target
linux-x64` while writing the runtime descriptor. Pass the target in
instead: the launcher supplies the target it prepared, and isolated tests
supply a fixed supported target.

Extract `desktopTargetPlatform()` into desktop-build-paths.mjs so the
target-to-payload mapping has one home, and use it for the qualification
runner, which previously derived both its target directory and its
descriptor from the host architecture.

Correct the README limitation to name the module that now performs the
architecture check.
2026-09-23 16:03:17 +08:00
ZiyaZhang f132684c81 fix(i18n): scope subagent terminology to UI and refresh snapshots 2026-09-23 00:57:07 -07:00
Turtle 3d6d46b8a6 Merge pull request #4896 from deepseek-harness/fix/preset-ui-20260921
fix(ui-agent-preset): restore the read-only configuration viewer
2026-09-23 15:50:34 +08:00
Yudong Han dd004b4221 fix(web): simplify read-only spreadsheet preview controls (#5006)
* fix(web): simplify read-only spreadsheet preview controls

* fix(web): soften read-only spreadsheet freeze dividers

* fix(web): hide read-only spreadsheet freeze dividers

* fix(web): keep spreadsheet formula bar on one line
2026-09-23 07:46:13 +00:00
Xu Hanxiang 38596d5c70 Merge pull request #4876 from deepseek-harness/fix/windows-open-through-explorer
fix(native-command): open Windows paths through explorer.exe
2026-09-23 15:46:04 +08:00
ZiyaZhang 8833ef9f63 Merge upstream/master into fix/subagent-zh-terminology 2026-09-23 00:38:59 -07:00
ZiyaZhang 995caa05ed fix(i18n): translate subagent as 子智能体 in Chinese UI 2026-09-23 00:33:41 -07:00
Yifffan 10292baeff Merge pull request #4818 from deepseek-harness/ui/agent-team-header-panel-refine
feat(web): refine the Team header panel interactions and adaptive header
2026-09-23 15:26:24 +08:00
Turtle bbeef5f250 Merge pull request #5020 from deepseek-harness/copy/plugin-experimental-labels
fix(web): label beta features as experimental
2026-09-23 15:24:43 +08:00
turtle1999 d52424042c Merge remote-tracking branch 'origin/master' into fix/preset-ui-20260921 2026-09-23 15:21:17 +08:00
Dudu-0223 2ea753960c fix(web): align Team card elevation and preset header expectations 2026-09-23 15:14:29 +08:00
Dudu-0223 2b44a4b657 fix(web): preserve Team hover pinning and responsive task expansion 2026-09-23 15:14:29 +08:00
Dudu-0223 4face54ea7 test(web): align adjacent navigation snapshots with header UI 2026-09-23 15:14:29 +08:00
Dudu-0223 0b62921f37 test(web): align Team panel snapshot with pinned projection UI 2026-09-23 15:14:29 +08:00
Yif 86e3e0e448 feat(web): refine the Team header panel interactions and adaptive header
The Team panel opens on hover with the subagent-catalog grace timings, pins
on click, polls the one-shot view RPC every 20s while open (replacing the
toolbar refresh/close controls), and eagerly loads on mount so the trigger
keeps a stable two-person icon. The lead card navigates from teammate
conversations, the current conversation carries a Current chat tag (selected
ring only among multiple members), task descriptions clamp to two lines
behind an expand toggle, an empty board shows a one-line description, and a
lone member narrows the panel to one column.

The header title row becomes an inline-size container: the preset label
hides first, then the Team trigger collapses to its icon and switches to
click-only opening. The subagent catalog moves to the front of the actions
band so lineage stays beside the title crumbs, and its pinned-menu behavior
matches the Team panel. Tooltip gains a gap prop for tight anchors.
2026-09-23 15:14:29 +08:00
Dudu-0223 61936aa113 Merge pull request #4810 from deepseek-harness/feat/agent-team-projection-panel
feat(agent-team): drive the Web Team panel from the agentTeam projection
2026-09-23 15:14:25 +08:00
Turtle 3b8be2928f Merge pull request #5017 from deepseek-harness/turtle/fix-tooltip-layering-e1cc527c20d0
fix(web): keep composer-stack tooltips above the input card
2026-09-23 15:14:07 +08:00
ZiyaZhang 3c20f401d2 fix(web): label beta features as experimental 2026-09-23 00:07:13 -07:00
turtle1999 a56e82e5dd fix(ui-agent-preset): share reader keyboard focus handling 2026-09-23 15:02:27 +08:00
Turtle a96d45af40 test(web): name the covering element in the tooltip hit test
Review follow-up on the tooltip layering fix. The helper probes
`bubble.contains(hit)` instead of identity, so a bubble that grows child
elements still passes, and each probe returns the hit element's tag and
class, so a regression reports what covered the bubble instead of a bare
`expected false to be true` — the pre-fix run now names `DIV.u82K-q_input`
for both probes.

The `portal` JSDoc says the ancestor stacking context confines the
bubble's z-index to that context rather than resolving it against
siblings.
2026-09-23 15:00:52 +08:00
Dudu-0223 63cc821e7e Merge master into feat/agent-team-projection-panel 2026-09-23 14:57:32 +08:00
Dudu-0223 df7fa8945c refactor(agent-team): narrow projection fields and task views 2026-09-23 14:54:21 +08:00
mektpoy edd0a438bf docs(native-command): state that the Explorer handoff acknowledgement proves no open
The open paragraph accepted Explorer's exit code 1 as a delegated handoff but
did not repeat the reveal paragraph's warning that the acknowledgement proves no
application opened the file, which the Agent Note records for the Remote answer
`{ opened: true }`.
2026-09-23 14:49:30 +08:00
turtle1999 02383832df Merge remote-tracking branch 'origin/master' into fix/preset-ui-20260921 2026-09-23 14:45:13 +08:00
Turtle 5082e05f71 fix(web): keep composer-stack tooltips above the input card
The goal strip and the queue dock ride directly above the input card, and
both create a stacking context (`isolation: isolate`) so their frosted
panel stays behind their own content. A tooltip bubble left inside that
context resolves its z-index there, where the card — painted later in the
same context — wins: the goal strip's bubble showed a two-pixel sliver
and the queue dock's stayed entirely hidden.

Portal those dock tooltips to the body, where the bubble's z-index
resolves above the whole card. The ui-primitives `portal` contract now
names the capping stacking context beside clipped containers, because
neither container clips its bubble.

The queue-actions aria goldens lose the tooltip line: the captured center
column no longer contains the portaled bubble. The scenario still covers
it by role and adds a hit test at the bubble's center and bottom edge, as
does goal-bar.
2026-09-23 14:43:23 +08:00
sjw1231 c4955b1900 test(web): strengthen diff review color assertions 2026-09-23 14:42:08 +08:00
Ziya 354da32a38 Merge pull request #4979 from deepseek-harness/fix/github-install-connectivity
fix(plugin-manager): fail fast on stalled GitHub connections
2026-09-23 14:38:25 +08:00
Dudu-0223 f546068553 fix(agent-team): align package metadata with merged release 2026-09-23 14:27:58 +08:00
Dudu-0223 3bf6d738f1 fix(agent-team): keep late activation on normal reconnect path 2026-09-23 14:16:57 +08:00
Yudong Han 5124a2a310 fix(web): show model IDs in monospace and names on hover (#5004)
* fix(web): show original model IDs on hover

* fix(web): display model IDs with names on hover
2026-09-23 06:10:30 +00:00
Dudu-0223 2aa3a469db fix(agent-team): consume shared projections without panel loads 2026-09-23 14:08:25 +08:00
Dudu-0223 3f07e34b5e Merge remote-tracking branch 'origin/master' into codex/pr-4810-review-fixes
# Conflicts:
#	packages/experimental/agent-team/package.json
#	packages/experimental/client-ui-agent-team/package.json
#	pnpm-lock.yaml
2026-09-23 13:58:31 +08:00
ZiyaZhang d7b45fb415 Merge remote-tracking branch 'origin/master' into fix/github-install-connectivity 2026-09-22 22:18:07 -07:00
ZiyaZhang 75d74a055e fix(plugin-manager): preserve authentication fallback during GitHub checks 2026-09-22 22:15:09 -07:00
turtle1999 f68d4b5aba Merge remote-tracking branch 'origin/master' into fix/preset-ui-20260921
# Conflicts:
#	packages/preset/agent-preset-registry/package.json
2026-09-23 13:08:30 +08:00
Yichen Jiang 752953d2b6 Merge pull request #4966 from deepseek-harness/worktree/voice-model-download-mirrors
feat(voice-input): support automatic and manual model download sources
2026-09-23 13:01:51 +08:00
mektpoy 2b9923fede fix(native-command): escape both Explorer field separators and keep non-ASCII literal
On Windows 11 ARM64 build 26200 Explorer splits its own command line at
commas and equals signs and rejects percent-encoded non-ASCII in a file
URI, falling back to the Documents folder in both cases without reporting
it. `explorerTarget()` now escapes `=` next to `,` and decodes Node's
non-ASCII escapes back to the literal characters the shell resolves, so
the two rules stay in one place. The Agent Note and the target-encoding
table record the measured behavior.
2026-09-23 12:33:31 +08:00
Ziya c3afd488aa Merge pull request #4962 from deepseek-harness/ux/plugin-install-20260922
feat(plugin-manager): offer mirror recovery for GitHub install failures
2026-09-23 12:03:47 +08:00
Yichen Jiang 3f0022a8fa Merge remote-tracking branch 'origin/master' into worktree/voice-model-download-mirrors 2026-09-23 12:03:11 +08:00
Turtle 040f47a822 Merge pull request #4987 from deepseek-harness/turtle/fix-title-overlap-b5d8f732773e
fix(ui-settings-account): clear the Windows caption for the Platform return bar
2026-09-23 12:02:28 +08:00
Ruilin Geng c68c6db9ca Merge pull request #4717 from deepseek-harness/fix/webkit-model-picker
fix(web): 修复 Safari 模型菜单鼠标选择失效
2026-09-23 11:59:53 +08:00
Yichen Jiang 0067efc8ae Merge pull request #4992 from deepseek-harness/worktree/pin-official-deepseek-first
fix(web): keep official DeepSeek first in model settings
2026-09-23 11:59:00 +08:00
Yichen Jiang d40dbef91b feat(voice-input): expose preparation download source selection 2026-09-23 11:58:54 +08:00
Yifffan 5bc57902d5 Merge pull request #4970 from deepseek-harness/docs/client-ui-ux-skill
docs(skills): add the dsh-client-ui-ux skill
2026-09-23 11:53:56 +08:00
Yichen Jiang dc374b8879 fix(web): keep official DeepSeek first in model settings 2026-09-23 11:18:01 +08:00
Turtle 14a062acb7 docs(ui-settings-account): record the Platform view caption limitation
Review follow-up: the caption menu is inert while the Platform view is open,
which the previous geometry hid; state it under Known Limitations, and drop
the duplicated 40px figure the Desktop owns. The style spec's rule lookup
anchors on the rule boundary and asserts with toContain.
2026-09-23 10:47:17 +08:00
Turtle ceb997cdef fix(ui-settings-account): clear the Windows caption for the Platform return bar
The embedded Platform view renders its 48px return bar at the top of the
window. On Windows the Desktop-owned caption menu occupies that strip (fixed
at top 0, 40px high, z-index 1100), so the "Back to DeepSeek Harness" label
and the Application/Edit menu text were painted over each other.

Offset the overlay by --dsh-windows-titlebar-height where the Desktop preload
publishes its caption marker, the same clearance AppFrame and the sidebar
caption controls use. The return bar and the native view bounds, which come
from the viewport element's rectangle, both move below the strip.
2026-09-23 10:33:17 +08:00
gengruilin 60b5800816 Merge origin/master into fix/webkit-model-picker 2026-09-23 10:26:50 +08:00
ZiyaZhang 0edfe83573 fix(plugin-manager): bound GitHub connection checks before installation 2026-09-22 08:33:49 -07:00
imccyu 00102833df Merge pull request #4978 from deepseek-harness/worktree/release-dsh-0.1.7-alpha.2
release: [email protected]
2026-09-22 23:25:38 +08:00
imccyu 10ea83bcc3 release(dsh): 0.1.7-alpha.2 2026-09-22 23:12:55 +08:00
imccyu 178ee3a9eb Merge pull request #4975 from deepseek-harness/worktree/release-vendor-4.0.4
release(vendor): cordis 4.0.4, cosmokit 1.8.5, group 1.0.4, hmr 1.0.1…
2026-09-22 23:12:01 +08:00
ZiyaZhang 4bf9d2e0c0 Merge master into plugin installation recovery 2026-09-22 08:03:27 -07:00
imccyu d0dca04e22 release(vendor): cordis 4.0.4, cosmokit 1.8.5, group 1.0.4, hmr 1.0.19, include 1.0.9, loader 1.0.5, logger-console 1.0.4, schemastery 3.18.4, timer 1.1.6 2026-09-22 23:02:28 +08:00
imccyu 3950647caa Merge pull request #4954 from deepseek-harness/fix/dsh-exact-workspace-dependencies
build: 锁定 DSH 版本并将 vendor/native 依赖限制为补丁升级
2026-09-22 22:59:56 +08:00
imccyu 3889e97de0 Merge pull request #4947 from deepseek-harness/fix/chat-groups-followup
fix(chat): 稳定滚动控件与嵌套自动跟随
2026-09-22 22:59:36 +08:00
imccyu 08c0e8e71b fix(chat): deduplicate admitted local steering 2026-09-22 22:47:12 +08:00
imccyu 43a8a22824 fix(chat): restore turn rail width threshold and clarify scroll gestures 2026-09-22 22:25:32 +08:00
Dudu-0223 9095678c8c fix(agent-team): address remaining projection review comments 2026-09-22 22:21:10 +08:00
Dudu-0223 9fcb76d9bc Merge remote-tracking branch 'origin/master' into codex/pr-4810-review-fixes 2026-09-22 22:17:37 +08:00
imccyu f6966f8fab test(client): align resize fixtures and restore branch coverage 2026-09-22 22:08:50 +08:00
imccyu fad9e27706 test(web): stabilize resize and scroll fixtures 2026-09-22 21:44:39 +08:00
imccyu 838eff14eb fix(chat): preserve reading intent around group scrolling 2026-09-22 21:44:00 +08:00
imccyu 4e6028a604 build: use tilde ranges for vendor and native workspaces 2026-09-22 21:14:06 +08:00
imccyu b9b5f7c58e fix: complete exact DSH ranges in application and runtime manifests 2026-09-22 21:12:57 +08:00
imccyu 7021420f29 fix: validate dependency ranges across every workspace 2026-09-22 21:12:57 +08:00
imccyu 37372101b5 build: pin internal DSH workspace dependencies 2026-09-22 21:12:56 +08:00
imccyu c92d5ebe18 perf(ui): fit tooltips from observed sizes 2026-09-22 21:07:56 +08:00
imccyu 820824edd7 fix(chat): stabilize input echo admission and ordering 2026-09-22 21:07:55 +08:00
imccyu 50ba2c8bb2 fix(session): align history pagination with turn boundaries 2026-09-22 21:07:55 +08:00
imccyu 2251b6189b fix(chat): preserve sticky headers with overflow clipping 2026-09-22 21:07:55 +08:00
imccyu 13dfae20c3 fix(chat): settle native scroll following before retargeting 2026-09-22 21:07:55 +08:00
imccyu fb02779a26 fix(chat): restore turn rail container queries 2026-09-22 21:07:55 +08:00
imccyu 1f029fc442 docs(chat): record scroll ownership and control geometry 2026-09-22 21:07:55 +08:00
imccyu 633cf7394e fix(chat): share independent scroll following with native group animation 2026-09-22 21:07:55 +08:00
imccyu 6b4055bf32 fix(chat): keep floating controls outside transcript clipping 2026-09-22 21:07:55 +08:00
Yif e8d5278c46 docs(skills): fix review findings in dsh-client-ui-ux and add scrollbar rule
Point selectable hover content at HoverCard (Tooltip has no interactive
prop), require a persistently mounted toast host, scope the font-weight
cap to new feature CSS, prefer theme typography tokens, link the styling
owners, date the design-reviewer roster, add the scrollbar containment
rule, and make the skill discoverable from packages/client/AGENTS.md.
2026-09-22 21:03:30 +08:00
imccyu 3dcfbf2094 Merge pull request #4964 from deepseek-harness/fix-reconnect
fix(web): gate reconnects on application readiness
2026-09-22 21:00:21 +08:00
Yif 6a07033145 docs(skills): require designer review for new icons and user-perceivable UI changes 2026-09-22 20:39:38 +08:00
mektpoy 5925925d20 Merge remote-tracking branch 'origin/master' into fix/desktop-dev-runtime-target
# Conflicts:
#	apps/desktop/src/main.ts
2026-09-22 20:30:25 +08:00
mektpoy a6b909c767 docs(native-command): correct the non-interactive Explorer claim
The recorded exit code 0 after about 30 seconds was not reproduced: an SSH
session 0 on Windows 11 ARM64 build 26200 measured exit code 1 after about a
second with no association invoked, the same exit code a delegated open
returns. The README, both Agent Note languages, and the `explorerTarget` JSDoc
now record that measurement, and the `Invoke-Item` contrast keeps its
qualitative part without the timing.
2026-09-22 20:29:24 +08:00
mektpoy b73031be9b test(native-command): pin the Explorer target for verbatim and device paths
A verbatim `\\?\` prefix and a `\\.\` device namespace take Node's UNC
resolution, and the comma encoding has to survive them. The drive-root case is
pinned separately because Node restores a trailing separator against the host's
`path.sep`, which differs on the Windows host this opener runs on.
2026-09-22 20:27:45 +08:00
sjw1231 fd784e4429 fix(web): refine file diff review colors 2026-09-22 20:23:45 +08:00
gengruilin 969c3df8c1 Merge origin/master into fix/webkit-model-picker 2026-09-22 20:10:46 +08:00
Yichen Jiang 1498b01923 feat(voice-input): select responsive model download mirrors 2026-09-22 20:06:00 +08:00
mektpoy bf3de68213 Merge remote-tracking branch 'origin/master' into tmp4876 2026-09-22 20:04:19 +08:00
Yichen Jiang 37e4dc2dee Merge pull request #4961 from deepseek-harness/worktree/model-discovery-display-name
fix(web): show discovered model names in picker
2026-09-22 19:56:28 +08:00
imccyu a44ced5b96 fix(web): gate Remote WebSockets on application readiness 2026-09-22 19:54:21 +08:00
Yichen Jiang d4b109f439 Merge pull request #4907 from deepseek-harness/worktree/plugin-install-country-registry
feat(plugin-manager): select the first responsive public registry
2026-09-22 19:51:49 +08:00
Yichen Jiang bef0869d14 test(web): select discovered models by display name 2026-09-22 19:44:57 +08:00
ZiyaZhang 82a7dd9c3f test(plugin-manager): strengthen GitHub recovery assertions 2026-09-22 04:04:33 -07:00
winewill 84394b818c Merge pull request #4919 from deepseek-harness/fix/fatal-diagnostics-and-crash-reports
fix: contain spill failures, recover failed client batches, and write Desktop crash reports
2026-09-22 19:01:39 +08:00
Yif 5b600047d2 docs(agents): add dsh-client-ui-ux skill for client UI/UX design and review
Collects the standing judgment rules for product-user-visible GUI work:
token and shared-treatment reuse, light/dark readability, font weight and
size scale, feedback-surface choice (toast vs in-place notice vs empty
state), loading treatments, overlay dismiss/fit/clip checks, macOS/Windows
window adaptation, and spacing review. dsh-code-review links it from its
sources of truth so UI PR reviews load it.
2026-09-22 18:52:15 +08:00
winewill ec852793b5 Merge remote-tracking branch 'origin/master' into fix/fatal-diagnostics-and-crash-reports 2026-09-22 18:51:57 +08:00
Yichen Jiang bdbf1fe339 Merge origin/master into registry response selection 2026-09-22 18:39:34 +08:00
Yichen Jiang 3c50bf6b6c feat(plugin-manager): choose the first responsive public registry 2026-09-22 18:38:43 +08:00
Ziya 20e7407ff1 Merge branch 'master' into ux/plugin-install-20260922 2026-09-22 18:36:24 +08:00
ZiyaZhang d6e5b7e41a feat(plugin-manager): offer mirror recovery for GitHub install failures 2026-09-22 03:24:45 -07:00
winewill c22b226b18 fix: address review threads on the fatal-diagnostics PR
- Agent Notes: cross-link the new decision with the native fatal recovery
  and fail-loud release notes both ways, rewrite the older note's
  console-retention and dialog-detail facts in place, and state the report's
  actual bounds.
- crash-report: cut the rendered error section at 256 KiB and prune only
  files matching the generated crash-<time>-<source>.log syntax.
- main: backendReady follows the backend state except during shutdown, so a
  failure while tearing down a ready backend still reads as running.
- subprocess-local: the temporary-file-cleaner hint is appended only for
  ENOENT; other codes rely on the appended error.
- client-modules: describeError is module-private; BootModuleRow.url's
  JSDoc names its fallback role.
2026-09-22 18:20:12 +08:00
winewill af28c551f3 Merge pull request #4913 from deepseek-harness/fix/windows-installer-extract-diagnostics
fix(desktop): report Windows extraction failures with 7-Zip evidence
2026-09-22 18:15:40 +08:00
Yichen Jiang b34b5d33a1 fix(web): show discovered model names in picker 2026-09-22 18:01:37 +08:00
winewill 603a8fc7d3 Merge remote-tracking branch 'origin/master' into fix/fatal-diagnostics-and-crash-reports 2026-09-22 17:58:58 +08:00
Turtle 505559b03b Merge pull request #4955 from deepseek-harness/turtle/settings-link-copy-064fa5feb8cd
fix(client): clarify chat link-opening settings copy
2026-09-22 17:42:50 +08:00
winewill 30a7f8f787 Merge branch 'master' into fix/windows-installer-extract-diagnostics 2026-09-22 17:37:28 +08:00
Dudu-0223 7c296b3f08 Merge pull request #4951 from deepseek-harness/fix/teammate-lead-guidance
fix(agent-team): explain teammate discovery and messaging
2026-09-22 17:36:31 +08:00
mektpoy fac0d8d877 docs(native-command): record the interactive-session requirement for Explorer opens 2026-09-22 17:35:54 +08:00
Turtle b2ec54159f docs(client): name the Sidebar Browser in the link-opening row prose
The ui-chat README described the visibility rule as "while the built-in browser
is available", a phrase that now reads like an option label. Both languages name
the Sidebar Browser, matching the sentence that follows.
2026-09-22 17:33:49 +08:00
Yichen Jiang 108f767a89 Merge pull request #4949 from deepseek-harness/worktree/fix-voice-language-settings
fix(voice-input): persist recognition language settings
2026-09-22 17:31:21 +08:00
winewill 7b2094d04d fix(desktop): keep the Host's original diagnostic in startup crash reports
A Host startup failure reached the crash report as a fresh Error built from
a message string three hops away from the original: the Host sent only
error.message over the fatal IPC event, DesktopHostProcess wrapped it in a
new Error, desktopErrorState flattened it to text, and main.ts wrapped that
text again. Stack, cause, code, and path were gone, and the Host's stderr
tail arriving with the later exit was blocked by the first-failure latch.

The Host now sends its complete inspected error alongside the message
(bounded to 64 KiB; carried over IPC rather than stderr because the two
race and the shell reports the first failure it sees). DesktopHostProcess
keeps it on DesktopHostFatalError.diagnostic, the backend error state
carries the original failure object, main.ts reports that object, and the
crash report prints the Host diagnostic verbatim in its own section.
2026-09-22 17:31:17 +08:00
mektpoy 03294cb653 test(native-command): pin every Explorer open target through one table 2026-09-22 17:31:12 +08:00
winewill d8924486ad fix(desktop): declare the cordis peer the packaged application needs
apps/desktop imports @deepseek-ai/dsh-api-gateway/stream-protocol, whose
import chain reaches @deepseek-ai/dsh-typert-protocol and then
@deepseek-ai/cordis. Both intermediate packages declare cordis as a
peerDependency and nothing satisfied it for the application closure, so
electron-builder omitted cordis from app.asar and the packaged main process
died before opening a window with ERR_MODULE_NOT_FOUND. A workspace checkout
resolves the peer through pnpm's own links, so development runs and the unit
lanes stayed green while every packaged build since efbc8e26b6 (2026-09-20)
failed to launch.

The lockfile additionally drops a stale duplicate [email protected] in favour of the
7.2.3 it already carries; any real pnpm install removes it.

Verified with a signed win-x64 installer built from this tree: app.asar now
ships cordis and @standard-schema/spec, and the installed application opens
its main window.
2026-09-22 17:21:06 +08:00
Dudu-0223 a79e3a2a5f fix(agent-team): explain teammate discovery and messaging 2026-09-22 17:18:08 +08:00
Turtle c81adc2569 Merge pull request #4948 from deepseek-harness/turtle/agent-experience-skill
docs(skills): add the agent-experience skill
2026-09-22 17:16:36 +08:00
Turtle a294fc55d0 Merge pull request #4930 from deepseek-harness/fix/641-unbounded-completion-wakes
fix(tool-jobs): wake an idle owner for every completion by default
2026-09-22 17:14:13 +08:00
Turtle aba3472b1d fix(client): clarify chat link-opening settings copy
The Settings → General row for Chat link destinations now reads
网页链接默认打开方式 with the description 对话中网页链接的打开位置 and the option
labels 应用内侧边栏 and 默认浏览器. English keeps the title "Open chat links in" and
now reads "Choose where to open web links", "In-App Sidebar", and "Default
Browser".

The `ui-chat.linkOpening` setting, its values, persistence, visibility rule, and
open behavior are unchanged. The row spec, the clickable-link gallery scenario,
and the two package READMEs name the new copy; the bilingual pairing records were
re-confirmed for the README pairs.
2026-09-22 17:12:43 +08:00
Dudu-0223 d257fa4af7 fix(agent-team): limit the reminder to the lead identity 2026-09-22 17:08:45 +08:00
imccyu b980fdf5df Merge pull request #4950 from deepseek-harness/worktree/release-dsh-0.1.5-rc.3-history-to-master
chore(release): record dsh 0.1.5-rc.3 history on master
2026-09-22 17:07:52 +08:00
ihsiang e420c44d60 Merge pull request #4857 from deepseek-harness/ihsiang/unify-code-block-ui
feat(web): 统一代码块样式与复制、换行控件
2026-09-22 17:06:07 +08:00
turtle1999 bada62b50b docs(skills): add the agent-experience skill
Carry the user-scoped agent-experience guidance into the repository unchanged so
project sessions discover it beside the other repository skills. The file,
including its frontmatter, is byte-identical to the personal copy.
2026-09-22 17:03:36 +08:00
Dudu-0223 6ec97fa124 fix(agent-team): tell teammates how to message the lead 2026-09-22 17:02:25 +08:00
yixiangihsiang 9c6c407da2 Merge master into ihsiang/unify-code-block-ui 2026-09-22 16:57:18 +08:00
Yichen Jiang 7c84d9d790 fix(voice-input): persist recognition language settings 2026-09-22 16:55:33 +08:00
imccyu 3ae2f687f1 merge: record dsh 0.1.5-rc.3 history on master
Keep the master tree unchanged while recording the dsh-v0.1.5-rc.3 release ancestry.
2026-09-22 16:50:58 +08:00
Yudong Han d3c5f959aa Merge pull request #4921 from deepseek-harness/fix/4918-excel-preview-resize
fix(web): resize Excel previews with their pane
2026-09-22 16:46:26 +08:00
HYD@OVERTON 58651b8359 fix(web): resize Excel previews with their pane 2026-09-22 16:33:30 +08:00
turtle1999 2d653be7b0 fix(tool-jobs): address review on the unbounded wake default
Skip the per-owner wake ledger entirely when no cap is configured.
Rewrite the 2026-08-11 Agent Note in place for the opt-in cap and add
a note recording why the default was removed. Keep `subagent`
untranslated in Chinese prose and drop change narration from the test.

Refs #4944
2026-09-22 16:33:24 +08:00
yixiangihsiang 5929af046c Merge master into ihsiang/unify-code-block-ui 2026-09-22 16:32:42 +08:00
yixiangihsiang 780e07b8ed test(web): align built boot assertions with diff layout 2026-09-22 16:28:46 +08:00
lsdsjy 32ca127565 Merge pull request #4898 from deepseek-harness/fix/contact-form-url
fix(client): update Feedback form and omit account UID
2026-09-22 16:27:23 +08:00
turtle1999 536b444f36 Merge remote-tracking branch 'origin/master' into fix/641-unbounded-completion-wakes 2026-09-22 16:26:50 +08:00
Huanqi Cao f5d7de2a01 Merge pull request #4807 from deepseek-harness/fix/pwsh-prompt-readiness
fix(pty): keep the controlled prompt so persistent pwsh settles fast
2026-09-22 16:25:59 +08:00
lsdsjy 4777947b6c fix(client): keep account UID fields out of questionnaire URLs
The Contact us action already stopped prefilling the signed-in UID, but a
configured contact form URL could still carry its own prefill_uid or hide_uid.
contactUrl now drops both query parameters unconditionally, with regression
coverage that starts from a form URL containing them.

The contact URL test also pins the shipped default form path, and the injected
contactUs documentation and the AccountProfile summary no longer describe a
support UID that is no longer sent.
2026-09-22 16:16:34 +08:00
lsdsjy 930d187e04 Merge remote-tracking branch 'origin/master' into fix/contact-form-url
# Conflicts:
#	packages/client/ui-settings-account/README.i18n.yaml
#	packages/client/ui-settings-account/README.md
#	packages/client/ui-settings-account/README.zh.md
2026-09-22 16:13:19 +08:00
Dudu-0223 ee0048558d test(web): capture terminal recovery readiness with its snapshot 2026-09-22 16:13:15 +08:00
Yudong Han 34a0a8e81d fix(web): preserve Excel previews across OPC paths and XML encodings (#4872)
* fix(web): resolve XLSX part relationships and namespace aliases

* fix(web): keep Excel browser scenario in the host test program

* fix(excel): preserve XML encodings and relocated workbook parts

* fix(excel): exclude installed launchers from dependency patch

* fix(excel): restore Strict and case-equivalent workbook previews
2026-09-22 08:04:13 +00:00
Dudu-0223 700b636508 fix(agent-team): limit baseline loading fix to the panel 2026-09-22 16:00:57 +08:00
lsdsjy e76a5eba21 Merge pull request #4903 from deepseek-harness/fix/platform-login-theme
fix(desktop): match Platform login theme
2026-09-22 16:00:44 +08:00
lsdsjy 785a92ab03 fix(client): rename the contact menu entry to Feedback
The account menu entry that opens the questionnaire is now labeled Feedback in
English and 意见反馈 in Chinese. The menu action, contact form URL and its
configuration options keep their existing names.

Expected menu snapshots and the bilingual README record the new copy.
2026-09-22 15:49:30 +08:00
lsdsjy 3b6a5399e8 docs(desktop): clarify login theme ownership 2026-09-22 15:49:20 +08:00
yixiangihsiang fd8bed1ad0 Merge master into ihsiang/unify-code-block-ui 2026-09-22 15:45:41 +08:00
yixiangihsiang 62f816f3a5 feat(web): refine code card spacing and thinking blocks 2026-09-22 15:44:57 +08:00
Dudu-0223 bfc772bb47 Merge remote-tracking branch 'origin/master' into codex/pr-4810-review-fixes 2026-09-22 15:40:58 +08:00
Dudu-0223 749f05e9fa fix(agent-team): refresh projections when opening the panel 2026-09-22 15:38:32 +08:00
Yichen Jiang ab7cdbee75 Merge remote-tracking branch 'origin/master' into worktree/plugin-install-country-registry 2026-09-22 15:38:14 +08:00
Huanqi Cao 41fd0baf91 Merge remote-tracking branch 'origin/master' into fix/pwsh-prompt-readiness
# Conflicts:
#	docs/config-catalog.i18n.yaml
2026-09-22 15:36:21 +08:00
lsdsjy f50059e425 fix(desktop): theme workspace sign-in links 2026-09-22 15:34:58 +08:00
Turtle 5ca6d34ac6 Merge pull request #4924 from deepseek-harness/turtle/fix-queued-edit-newline-54bfaf4e59d7
fix(web): keep line breaks when re-editing a queued message
2026-09-22 15:33:31 +08:00
Yichen Jiang 8cf07ea76c fix(plugin-manager): preserve remembered registries during initial lookup 2026-09-22 15:32:16 +08:00
turtle1999 b6775f6d4f fix(tool-jobs): wake an idle owner for every completion by default
`maxConsecutiveWakes` no longer defaults to 3. Unset, every background
job completion wakes its idle owner; the cap is opt-in.

With the default cap, a session chaining background commands and
one-shot subagents exhausted the budget after three wakes. The fourth
notice was injected into the next-step inbox without waking the owner,
the client showed nothing, and the session stalled until the user
typed something (dsh-external/issues#641).

Closes dsh-external/issues#641
2026-09-22 15:29:23 +08:00
lsdsjy 2c098434b9 Merge remote-tracking branch 'origin/master' into fix/platform-login-theme 2026-09-22 15:27:06 +08:00
lsdsjy 13fe312e15 test(desktop): fix login theme CI fixtures 2026-09-22 15:26:39 +08:00
Turtle 753df93b85 test(web): pin the multiline queued edit in the browser lane
The recorded-session scenario edited with a single-line value, so the ARIA
golden could not catch a return to a newline-stripping editor. Edit with a
two-line value and assert the model-bound user message keeps the line break;
with the previous input element the scenario fails on that assertion. Also
refresh the stale input cast and pin that Shift+Enter is not consumed.
2026-09-22 15:12:35 +08:00
Yichen Jiang 8ae7631861 Merge pull request #4916 from deepseek-harness/worktree/settings-provider-docs
docs(settings): correct provider configuration paths and examples
2026-09-22 15:08:34 +08:00
lsdsjy b1c084e769 Merge pull request #4904 from deepseek-harness/fix/web-account-client-platform
fix(credentials): send web client platform header for account requests
2026-09-22 15:02:35 +08:00
Yichen Jiang 13d9606675 test(voice-input): synchronize idle cleanup failure observation 2026-09-22 14:59:47 +08:00
Turtle c74e36acfc fix(web): keep line breaks when re-editing a queued message
The inline queue editor was a single-line input, and HTML strips newlines
from such a value: opening and saving a multi-line queued message silently
collapsed it into one line before delivery. Edit in a textarea that grows
with its content, capped at six lines; Enter saves, Shift+Enter breaks the
line, and Escape still cancels.
2026-09-22 14:55:34 +08:00
Jiawei Song e950c8bea4 Merge pull request #4838 from deepseek-harness/fix/4823-message-overflow-review-menu
fix(web): 修复消息长文件名溢出和 Review 浮窗菜单错位
2026-09-22 14:47:21 +08:00
winewill a19a48edea Merge pull request #4837 from deepseek-harness/feat/desktop-update-ui-2
fix(desktop): polish the Windows update copy, app icon and About dialog
2026-09-22 14:42:44 +08:00
winewill cafb9b93b4 fix: address adversarial review of the fatal-diagnostics branch
client-modules: a batch that already executed is never requested again,
even when its first importer was a row it did register — every executed
batch URL is remembered and a still-missing row goes straight to its
one-resource URL. The in-process module system test in test-support is
updated to the aggregated arrival message. describeError replaces three
inline non-Error ternaries and is covered.

subprocess-local: the spill path is published only after the exclusive
open succeeds, so a failed open (EEXIST on a planted entry included) can no
longer unlink a path this process did not create; a reporter that throws is
contained and written to stderr; logSpillFailure builds the owner reporter
for both the runtime and the SSH helper so the logging path is exercised
once instead of duplicated. Tests cover the runtime logger, the bare-caller
stderr default, EEXIST, and a throwing reporter.

desktop: a fatal failure during shutdown still writes its report; the
report phase follows the backend state so a failure during a restart is
'startup' again; writeReport is a required recovery operation and the
dialog tests await the write; the shell pid is labelled; the withheld
response-header list states why set-cookie is dropped.

app-boot, READMEs, Agent Note: "never resumes" is restated as control never
returning to the failed operation while the loop runs until release
settles; inspect output is bounded; incident inferences (who removed the
spill directory, which batch failure occurred) are written as inferences.
2026-09-22 14:41:10 +08:00
lsdsjy 5f1a524f61 docs(credentials): clarify private platform request headers 2026-09-22 14:39:19 +08:00
Dudu-0223 db4bfb4c96 docs(agent-team): refresh generated module dependencies 2026-09-22 14:37:05 +08:00
lsdsjy 6a133a02b8 Merge origin/master into fix/web-account-client-platform 2026-09-22 14:36:24 +08:00
Yichen Jiang 43bd7010d0 test(llm): isolate idle timeout from HTTP setup timing 2026-09-22 14:36:17 +08:00
mektpoy 1a21e958df Merge remote-tracking branch 'origin/master' into tmp4876 2026-09-22 14:34:28 +08:00
Dudu-0223 7befda1147 test(agent-team): release fixtures and respect snapshot types 2026-09-22 14:32:01 +08:00
winewill bed4a2b565 fix(desktop): address review of the extraction failure report
- Headline skips the archive path 7-Zip echoes before an open failure
  (also when the path contains spaces) and prefers the operation line.
- Raw process statuses such as 0xC0000005 are kept and printed in hex
  instead of being clamped to INT_MAX.
- The expanded panel excerpts the result and 7-Zip's own lines rather
  than the report header.
- The saved report keeps up to 16 MiB of output, records any cut, and
  trims a partial UTF-8 sequence before decoding.
- The details view no longer reads the UTF-8 log as ANSI; the test seam
  define and the zero-padded GetTime order are documented; the smoke
  asserts a year-first report name and normalizes -FrameLibrary.
- README names the real updater cache directory.
2026-09-22 14:30:56 +08:00
Huanqi Cao dbd34bcc0d Merge remote-tracking branch 'origin/master' into fix/pwsh-prompt-readiness
# Conflicts:
#	docs/config-catalog.i18n.yaml
2026-09-22 14:30:34 +08:00
Yichen Jiang c9591d9445 Merge remote-tracking branch 'origin/master' into worktree/plugin-install-country-registry 2026-09-22 14:29:40 +08:00
Yichen Jiang d459183f32 docs(settings): correct provider configuration paths and examples 2026-09-22 14:28:46 +08:00
Dudu-0223 b7d9c0f6a4 Merge remote-tracking branch 'origin/master' into feat/agent-team-projection-panel 2026-09-22 14:27:15 +08:00
Dudu-0223 843e1b97af fix(agent-team): scope projection reads and address review feedback 2026-09-22 14:27:00 +08:00
yixiangihsiang ce2ba9b216 Merge master into ihsiang/unify-code-block-ui 2026-09-22 14:23:25 +08:00
CreatixChu ee4458f6d0 Merge pull request #4802 from deepseek-harness/fix/4775-multimodal-tool-retention
fix: 按统一 token 预算保留工具结果中的图文内容
2026-09-22 14:22:45 +08:00
Yichen Jiang d407a6ce90 test(credentials): allow either concurrent account request order 2026-09-22 14:22:13 +08:00
winewill 4544874d62 Merge branch 'master' into fix/windows-installer-extract-diagnostics 2026-09-22 13:46:04 +08:00
imccyu a4c74a91e0 Merge pull request #4909 from deepseek-harness/worktree/release-dsh-0.1.5-rc.3
release: [email protected]
2026-09-22 13:26:23 +08:00
winewill 6530130ffa docs(agent-notes): record fatal diagnostics and crash report decisions 2026-09-22 13:25:38 +08:00
winewill e38cca76fd feat(desktop): write a crash report before the fatal recovery dialog
The fatal recovery dialog showed the last eight lines of the error and
promised that the full diagnostic was in the Electron console, which a
packaged installation never surfaces. Two field failures were diagnosed
from screenshots of those eight lines: a Host killed by an uncaught
exception whose path happened to be in the tail, and a renderer boot
failure whose real import error lived only in the renderer console.

Every reportFatal caller now names its source (host, web-boot, renderer,
main). Before the first dialog opens, the shell writes
crash-<time>-<source>.log under app.getPath('logs') with the source, the
startup or running phase, application and runtime versions, the complete
inspected error with its properties and cause chain, and the primary
window's recent error-level console output (a 64 KiB tail captured from
webContents 'console-message'). The dialog names the file on its own line,
independent of whether the error was shortened and in the listener-conflict
variant too; the wait is bounded to one second and a failed write shows the
dialog without a path. Startup keeps the ten newest reports. Buttons and
recovery actions are unchanged.
2026-09-22 13:22:27 +08:00
imccyu 8e7cca22ca release(dsh): 0.1.5-rc.3 2026-09-22 13:08:07 +08:00
imccyu 943af81a18 fix(release): pin vendor dependencies for dsh 0.1.5 2026-09-22 13:07:34 +08:00
winewill 5864a6f6f1 fix(desktop): drop connection-level headers and bundle cache headers when forwarding to the Host
forwardWebRequest relayed transfer-encoding, connection, keep-alive, and
the other hop-by-hop headers from the Host's response into the dsh-app://
response, describing a Node fetch connection that Chromium never saw. It
also relayed the Host's `cache-control: public, max-age=31536000,
immutable` on plugin bundles whose revision changes on every launch, so
each start wrote another unreusable copy into the renderer's disk cache.

The forwarder now strips the connection-level header set alongside the
encoding headers it already removed and marks /plugins/ responses
no-store. Whether either header contributed to the observed batch load
failure is unverified; both are corrections in their own right.
2026-09-22 13:03:13 +08:00
winewill afde35880f feat(client-modules): recover from a failed batch script and record import errors
One application batch is a single classic script that registers every
package in sequence, requested exactly once during boot. A transient
failure of that one <script> request failed every row in the batch and,
through arriveGraphRow's dependency wait, every row that injects or
requests them; the boot audit then listed each as "import failed (see
console for the import error)" with the real error only in the renderer
console.

Arrival now distinguishes the two failure kinds. A transport failure
(script error event, nothing executed) is retried once on the same URL and
the retry is shared by every row waiting on that batch. A script that
loaded without registering a row is never re-executed, because register()
rejects duplicates and a replay would stop at the first package the
original run did register. Either way each still-missing row then loads its
own one-resource combo URL, which the Host serves for every package. A batch
URL that failed is remembered so later rows skip straight to their
fallback; a one-resource URL is not, so it stays retryable on the next
import as before. Dependency failures are wrapped with the consumer and
dependency names so a cascade reads as a chain.

The module system records the last import() or prefetch() failure per
graph row at the outer operation, so factory execution errors are captured
as well as arrival errors, and clears it on success or invalidation.
assertEntriesActive takes the module system and reports that text per
fiberless entry.
2026-09-22 13:00:13 +08:00
Yichen Jiang 9f52c6401e docs(ip-geolocation): refresh the module dependency graph 2026-09-22 12:57:05 +08:00
Yichen Jiang 22be805026 fix(ip-geolocation): align the package with the current release 2026-09-22 12:50:28 +08:00
winewill 9f9a50e553 feat(app-boot): treat uncaught exceptions as fatal through installFailLoud
The Desktop Host and every other profile launcher installed a fail-loud
handler for unhandled rejections only. A synchronous throw from a stream
'data' listener fell through to Node's default uncaught-exception path: it
printed and exited without the surface's release hook, and the label and
format differed from every other fatal diagnostic.

installFailLoud now registers the same report for 'uncaughtException' with
the label `fatal uncaught exception`, shares the exit latch and release
timeout with rejections, and never resumes the process. The diagnostic is
util.inspect(err, { depth: 4 }) instead of err.stack so enumerable
properties (a node:fs error's code, syscall, and path) and the cause chain
reach stderr; the `fatal load failure` label for rejections is unchanged
because the Web profile expected-output e2e tests match it.
2026-09-22 12:46:37 +08:00
winewill cfa84ed4e3 fix(subprocess-local): contain spill file failures instead of killing the host
OutputCollector.spillAll ran openSync/writeSync inside the stream 'data'
listener with no containment. When the private per-process spill directory
had been removed by a temporary-file cleaner, the first overflowing chunk
threw ENOENT from Pipe.onStreamRead, became an uncaught exception in the
Desktop Host, and the Electron shell reported it as a fatal failure.

Spilling is best-effort by contract (spillPath is optional; a failed final
close already withholds it). Every filesystem failure while opening or
appending the spill file now discards the spill, reports once through the
owner's logger, and leaves in-memory collection untouched. Spill inputs move
into one SpillOptions object with the reporter, so tail-only collectors no
longer pass an empty directory; prepareManagedProcessBinding is the explicit
resolve step for the directory and reporter defaults, and the runtime and
SSH helper supply their plugin loggers.

Tests cover ENOENT on open (removed directory), ENOTDIR on open, and ENOSPC
on append after the file exists, asserting the tail stays byte-exact, the
spill file is withdrawn, and the reporter fires once.
2026-09-22 12:40:07 +08:00
winewill ab62f09c22 fix(desktop): report Windows extraction failures with 7-Zip evidence
The installer showed "Failed to decompress files" and discarded 7-Zip's
error output, so a failure such as security software quarantining an
extracted file could not be told apart from a full disk or a broken
download.

- InstallerExtract returns negated Win32 errors for launch failures so
  they no longer collide with 7-Zip exit codes; 7-Zip writes UTF-8 errors.
- window-frame.dll composes a report (result, archive, destination,
  Windows version, full 7-Zip output), saves it under the updater cache
  directory, and presents a task dialog with the first error line, a
  collapsible bounded excerpt, and a Copy error details button. Silent
  installs keep only the report; a missing task dialog falls back to a
  message box.
- Unsigned Windows builds name their installer *-unsigned.exe so a
  shared file cannot pass for a release artifact; version discovery
  still counts them.
- The directory smoke asserts the saved report on a broken archive and
  smoke-windows.ps1 accepts the prepared DLL.

Refs #4893
2026-09-22 12:38:05 +08:00
Yichen Jiang dccf989cd8 feat(plugin-manager): prefer the mainland mirror for CN network exits 2026-09-22 12:37:32 +08:00
creatixchu c5ca36387b Merge remote-tracking branch 'origin/master' into fix/4775-multimodal-tool-retention
# Conflicts:
#	packages/spill/spill-policy/package.json
2026-09-22 12:35:56 +08:00
mektpoy 95611fbc63 fix(native-command): encode the Explorer target so a comma stays in the path 2026-09-22 12:24:53 +08:00
Turtle c36a83ff6b Merge pull request #4901 from deepseek-harness/rel/dsh-0.1.7-alpha.1
release(dsh): 0.1.7-alpha.1
2026-09-22 12:12:33 +08:00
lsdsjy f5c96b6340 fix(credentials): identify web account requests with client platform header 2026-09-22 12:09:40 +08:00
creatixchu 670a903237 test: clarify parallel recovery fixture cleanup 2026-09-22 12:08:55 +08:00
creatixchu b74bef5d40 test: exercise image recovery and real PTC retention 2026-09-22 12:03:54 +08:00
lsdsjy 817623750a fix(desktop): match Platform login theme
Opened and copied authorization links carried no palette, so the Platform
login page rendered its own stored preference. Append the effective Desktop
palette as theme=light or theme=dark, resolving system through the theme
source the preload publishes.

The theme source now reaches nativeTheme on Windows as well: the preload
drops its macOS-only guard, since shouldUseDarkColors is read for every
platform, and gates the DOMContentLoaded listener with once.
2026-09-22 12:02:54 +08:00
turtle1999 112ce776ac release(dsh): 0.1.7-alpha.1 2026-09-22 11:59:16 +08:00
Huanqi Cao 8aff71863b Merge remote-tracking branch 'origin/master' into fix/pwsh-prompt-readiness
# Conflicts:
#	docs/config-catalog.i18n.yaml
2026-09-22 11:52:42 +08:00
Turtle 6942625430 Merge pull request #4897 from deepseek-harness/fix/web-ui-hide-account-sign-in
fix(web): hide DeepSeek account sign-in outside desktop
2026-09-22 11:50:02 +08:00
winewill 9d91bb01c9 fix(desktop): read the About copy from the live locale
master replaced the startup-bound messages constant with a locale that
follows the settings preference; the About dialog now reads the same
locale as the update prompts so the two never mix languages.
2026-09-22 11:48:33 +08:00
winewill 3305bb1938 Merge remote-tracking branch 'origin/master' into feat/desktop-update-ui-2 2026-09-22 11:44:41 +08:00
sjw1231 5ac920e50c chore: merge latest master into message and review fixes 2026-09-22 11:39:33 +08:00
lsdsjy adbb0e0dc4 fix(client): update the contact form URL and drop its account UID prefill
The Contact us entry now opens the current Feishu questionnaire, and the URL
no longer carries the signed-in account UID: contactUs stops reading the
account profile, contactUrl stops writing prefill_uid and hide_uid, and the
remaining build and browser context fields keep their existing behavior.

The url, package tests and the bilingual README and account-login note state
the same current facts.
2026-09-22 11:39:14 +08:00
lsdsjy 75546dc8e9 fix(web): hide DeepSeek account sign-in outside desktop 2026-09-22 11:38:43 +08:00
ZiyaZhang 2426d398b5 fix(ui-agent-preset): restore the read-only configuration viewer
The declarative-preset refactor (#4569) removed the registry's read Remote
together with the YAML editor, and with it the 查看配置 card action that
opened a preset's composition read-only. #4850 brought the Creator entry
back and had to leave 查看配置 out because `agentPresets/read` was gone.

Give the registry a read-only `read` Remote that renders a declaration's
child plugin list from the in-memory definition in the Loader's own
entry-list dialect, so `!!js` conditions read as declared, and put the card
action back at the right end of each card footer: an icon that opens the
YAML in a read-only dialog titled after the preset. Nothing accepts YAML
back, and a broken preset stays readable because its diagnostic points into
that YAML.

Fixes #4895
2026-09-21 20:38:30 -07:00
Turtle 1c96299173 Merge pull request #4831 from deepseek-harness/worktree/voice-input-icon
fix(voice-input): improve plugin icon and setup diagnostics
2026-09-22 11:35:32 +08:00
creatixchu de45804084 test: refresh tool projection API snapshot 2026-09-22 11:32:07 +08:00
imccyu 1e9c7b410a Merge pull request #4891 from deepseek-harness/worktree/release-vendor-4.0.2
release(vendor): cordis 4.0.3, cosmokit 1.8.4, group 1.0.3, hmr 1.0.1…
2026-09-22 11:31:17 +08:00
Huanqi Cao 091db6c8c3 Merge remote-tracking branch 'origin/master' into fix/pwsh-prompt-readiness
# Conflicts:
#	docs/config-catalog.i18n.yaml
#	docs/config-catalog.zh.md
2026-09-22 11:30:34 +08:00
creatixchu 94728ebd23 fix: repair multimodal retention CI resolution and coverage 2026-09-22 11:28:51 +08:00
creatixchu 06923b8868 Merge remote-tracking branch 'origin/master' into fix/4775-multimodal-tool-retention 2026-09-22 11:25:47 +08:00
imccyu efa63a7fc4 release(vendor): cordis 4.0.3, cosmokit 1.8.4, group 1.0.3, hmr 1.0.18, include 1.0.8, loader 1.0.4, logger-console 1.0.3, schemastery 3.18.3, timer 1.1.5 2026-09-22 11:21:12 +08:00
Yichen Jiang d20c90a091 Merge remote-tracking branch 'origin/master' into worktree/voice-input-icon 2026-09-22 11:19:32 +08:00
Turtle 1d1b033f77 Revert "feat(shortcuts): add configurable Web and desktop shortcuts" (#4886) 2026-09-22 11:17:24 +08:00
_Kerman ad83cce36a fix(session): repair missing turn ends during V3 migration (#4689)
* fix(session): repair interrupted turns during V3 migration

* docs(session-format-v3-to-v4): state reference remapping in the summary and code

The frontmatter summary still promised preserved references after the edge
began remapping audited same-artifact references around an inserted
interrupted turn/end. Two local comments record why the opaque branch moves
only `seq` and why the open-step flag needs no reset at a turn boundary.

* docs(session-format-catalog): describe the V3-to-V4 edge as a converting link

The catalog listed the edge as an identity conversion, which stopped being
true once it inserted an evidenced interrupted turn/end and remapped the
audited local references that follow.
2026-09-22 11:13:54 +08:00
Yichen Jiang 2fb7c881db Merge remote-tracking branch 'origin/master' into worktree/voice-input-icon 2026-09-22 11:13:49 +08:00
Turtle 2c95550e87 Revert "feat(shortcuts): add configurable Web and desktop shortcuts" 2026-09-22 11:04:01 +08:00
Yichen Jiang 577ccabb96 fix(voice-input): match microphone support and capsule stroke widths 2026-09-22 11:03:25 +08:00
creatixchu 6b227a81a7 docs: sync plugin inventory after master merge 2026-09-22 11:00:31 +08:00
Yichen Jiang 9799132631 fix(voice-input): outline the capsule without changing microphone proportions 2026-09-22 10:59:08 +08:00
Yichen Jiang dc1e6e967b fix(voice-input): use the outline microphone icon 2026-09-22 10:53:06 +08:00
creatixchu 4137d1d628 Merge remote-tracking branch 'origin/master' into fix/4775-multimodal-tool-retention
# Conflicts:
#	docs/config-catalog.i18n.yaml
#	docs/config-catalog.zh.md
#	docs/module-graph.i18n.yaml
#	docs/module-graph.md
#	docs/module-graph.zh.md
2026-09-22 10:52:21 +08:00
Dudu-0223 d08aac9db2 fix(agent-team): read current projections through standard hooks 2026-09-22 10:46:06 +08:00
Dudu-0223 5ca4fea9b5 Merge master into feat/agent-team-projection-panel 2026-09-22 10:45:10 +08:00
Yichen Jiang 8c022b8013 fix(voice-input): use waveform artwork and filled microphone 2026-09-22 10:42:59 +08:00
流光 08b73684d0 feat(shortcuts): add configurable Web and desktop shortcuts (#4847)
- Add shortcut discovery, search, recording, and customization.
- Persist device-local bindings with conflict detection and reset support.
- Integrate shortcuts with desktop menus, workspace actions, and sidebar focus.
- Add unit coverage and browser regression snapshots.
2026-09-22 03:46:10 +08:00
Xinyu Yang a0d453d3c6 fix(shortcuts): complete CI coverage and browser regressions 2026-09-22 03:27:55 +08:00
imccyu a68c1f6539 Merge pull request #4852 from deepseek-harness/feat/chat-process-groups-product
feat(chat): grouped work details and scoped rendering optimizations
2026-09-22 02:57:17 +08:00
imccyu c7b9c91497 fix(chat): preserve steering order and limit expanded mode to live turns 2026-09-22 02:34:16 +08:00
Huanqi Cao 44900be005 docs(native-command): name explorer.exe in the Windows open note
The Agent Note title said 'the shell's own application resolution', which names a layer instead of the command the opener runs.
2026-09-22 02:07:32 +08:00
Huanqi Cao 68e43b173e fix(native-command): open Windows paths through the shell's resolution
The settings sheet's Open configuration file action opened nothing on Windows when the user's default lived only in the Explorer per-user record (FileExts\\<ext>\\UserChoiceLatest), which Explorer honors and the association resolver inside the host process does not. Invoke-Item also exits 0 when it finds no application, so the Remote answered { opened: true } and the sheet rendered no error.

Windows opens now hand the path to explorer.exe as one argv element - the resolution a double-click performs - and revealNativePath shares that invocation rule. The shared helper accepts Explorer's delegate exit 1 for an open and a reveal alike; every other failure and every cancellation still rejects. The PowerShell command string and powershellLiteral are gone: the path crosses the process boundary as argv, with no escaping step.

Fixes #4428
2026-09-22 01:57:21 +08:00
imccyu 504013def1 refactor(chat): simplify process titles and clarify grouping notes 2026-09-22 01:32:38 +08:00
imccyu 5d62f82bc0 fix(chat): preserve cross-client pending input order 2026-09-22 01:08:15 +08:00
imccyu 809e0942b9 fix(session): retire confirmed steering echoes on resync 2026-09-22 01:08:15 +08:00
imccyu 12d9dd6aa4 test(web): refresh chat process group snapshots 2026-09-22 00:45:46 +08:00
imccyu 3bf26d0954 docs(chat): consolidate grouping rationale and review status 2026-09-22 00:45:46 +08:00
imccyu 9ad25a6ab6 fix(chat): cover turn triggers and preserve reading anchors 2026-09-22 00:45:46 +08:00
imccyu 3b4ecc9d20 fix(chat): retain assistant nodes and steering echoes 2026-09-22 00:45:46 +08:00
imccyu 92046a8abc fix: snapshots 2026-09-22 00:45:46 +08:00
imccyu 275008d324 fix: static 2026-09-22 00:45:46 +08:00
imccyu b12a72867a fix(chat): keep early turn controls after opening input 2026-09-22 00:45:46 +08:00
imccyu 4d4af31d16 fix(chat): retain typed source receivers and paging results 2026-09-22 00:45:46 +08:00
imccyu 22cd2cd387 test(chat): align turn presentation and deterministic clock fixtures 2026-09-22 00:45:46 +08:00
imccyu 99c0e8dcf6 test(plan): cover scoped card binding and availability failures 2026-09-22 00:45:46 +08:00
imccyu ee3526059e docs(chat): complete grouping and viewport API comments 2026-09-22 00:45:46 +08:00
imccyu a0bf09f629 docs(chat): remove the obsolete work-details migration tracker 2026-09-22 00:45:46 +08:00
imccyu 19e5af7ded perf(chat): locate visible turns with outer-row binary search 2026-09-22 00:45:46 +08:00
imccyu 97daa6a799 perf(conversation): ignore idle width-handle pointer movement 2026-09-22 00:45:46 +08:00
imccyu 5ff9ab8757 perf(plan): subscribe cards to turn-local plan data 2026-09-22 00:45:45 +08:00
imccyu a7fc7d4f56 perf(chat): restore preference focus before publishing updates 2026-09-22 00:45:45 +08:00
imccyu 3a046413de perf(locale): avoid unchanged document language writes 2026-09-22 00:45:45 +08:00
imccyu 376f68199e perf(chat): narrow group title updates and defer edge measurements 2026-09-22 00:45:45 +08:00
imccyu 1bcb633c3f feat(chat): add grouped work details and stable history paging 2026-09-22 00:45:45 +08:00
imccyu e17e1ac801 feat(client): present turn triggers and completed-turn footers 2026-09-22 00:45:45 +08:00
imccyu 4b78b6577c feat(client): inject reset-aware disclosure hooks 2026-09-22 00:45:45 +08:00
imccyu b47cef687e Merge pull request #4863 from deepseek-harness/fix-excel
fix(web): 修复含绘图的 XLSX 文件预览
2026-09-22 00:41:02 +08:00
Xinyu Yang 76ab2a32d1 fix(shortcuts): align settings launcher and browser regressions with master 2026-09-22 00:20:29 +08:00
imccyu d49f2b1bf8 test(client): make rebuild generations independent of clock precision 2026-09-22 00:18:59 +08:00
imccyu 5270b7ffa1 test(hmr): wait for filesystem ctime before polling assertions 2026-09-22 00:01:10 +08:00
Xinyu Yang 7b564f61e7 fix(shortcuts): integrate current desktop guests and client APIs 2026-09-21 23:57:50 +08:00
Xinyu Yang 069c370987 fix(shortcuts): address review findings and modal focus 2026-09-21 23:57:49 +08:00
Xinyu Yang 49fc4f0e5b refactor(shortcuts): remove unused state and tighten command context 2026-09-21 23:57:49 +08:00
Xinyu Yang f815d6c733 fix(shortcuts): stabilize recording, focus, and desktop dispatch 2026-09-21 23:57:49 +08:00
Xinyu Yang 365ef73c7c feat(client): prioritize action tooltips over workspace previews
Hide an enclosing hover preview while an action tooltip is visible, and restore the preview when the tooltip closes.
2026-09-21 23:57:49 +08:00
Xinyu Yangand神河一号 - 烈焰流光 b7ac0ade10 feat(shortcuts): add fixed stop and approval actions
Add scoped double-Escape cancellation and fixed approval actions. Reserve their input combinations and preserve composition, modal, and input-owner arbitration.

Co-authored-by: 神河一号 - 烈焰流光 <[email protected]>
2026-09-21 23:57:49 +08:00
Xinyu Yangand神河一号 - 烈焰流光 f54744e35b feat(shortcuts): add focused panel and page commands
Route file, terminal, browser, split, fullscreen, close, and refresh commands to the focused panel. Connect native Desktop page and window actions to the same command owners.

Co-authored-by: 神河一号 - 烈焰流光 <[email protected]>
2026-09-21 23:57:49 +08:00
Xinyu Yangand神河一号 - 烈焰流光 a532a687a6 feat(shortcuts): add workspace and session commands
Connect saved bindings to workspace search, creation, session naming, forking, archiving, and external opening. Publish the loaded conversation history needed to select fork targets.

Co-authored-by: 神河一号 - 烈焰流光 <[email protected]>
2026-09-21 23:57:49 +08:00
Xinyu Yangand神河一号 - 烈焰流光 efdf8e5b6d feat(shortcuts): add configurable Web and desktop shortcuts
Add the keyboard registry, device-local preferences, and searchable shortcut editor. Support Web routing and native Desktop single-key and two-key bindings.

Co-authored-by: 神河一号 - 烈焰流光 <[email protected]>
2026-09-21 23:57:28 +08:00
imccyu 2d236df56f test(client): wait for filesystem ctime before revision checks 2026-09-21 23:49:42 +08:00
imccyu 6ba92b3f05 fix(web): preview XLSX workbooks with unsupported drawings 2026-09-21 23:26:50 +08:00
imccyu d7c238a98d Merge pull request #4855 from deepseek-harness/fix/client-revisions-server-restart
fix(web): preserve sessions across server restarts
2026-09-21 23:24:51 +08:00
lsdsjy 099eacf108 Merge pull request #4475 from deepseek-harness/feat/desktop-welcome-and-window-controls
feat(desktop,credentials): add Welcome and DeepSeek account sign-in
2026-09-21 23:04:14 +08:00
lsdsjy 5b9a33fa61 test(account): align browser flows and diagnose stalled transport 2026-09-21 22:48:12 +08:00
imccyu d1fb051e0f test(web): stamp bootstrap artifacts before rebuild checks 2026-09-21 22:41:17 +08:00
yixiangihsiang f114233545 fix(web): cover code-card overflow and refresh browser expectations 2026-09-21 22:40:55 +08:00
lsdsjy 2a1eb24285 fix(account): cover lifecycle failures and restore CI compatibility 2026-09-21 22:32:30 +08:00
Turtle d1255ab1a4 docs(agent-preset): enumerate and verify skill files with Host file tools only (#4856)
* docs(agent-preset): enumerate and verify skill files with Host file tools only

* docs(agent-preset): list template files instead of enumerating the skill directory

The glob and search tools spawn a native ripgrep process, which cannot read
inside app.asar any more than a shell can; the skill now carries the complete
template file list and forbids enumeration.
2026-09-21 22:30:57 +08:00
lsdsjy 0d985d55a2 merge: sync master into desktop welcome branch 2026-09-21 22:11:39 +08:00
lsdsjy 43ff526d1e fix(ui-settings-account): retry failed embedded Platform pages 2026-09-21 22:06:05 +08:00
lsdsjy f8175d5989 fix(desktop): synchronize embedded Platform locale 2026-09-21 21:59:10 +08:00
yixiangihsiang 8d2cd0cf72 feat(web): unify code block styling and icon controls 2026-09-21 21:57:35 +08:00
lsdsjy 9d0b8da486 fix(desktop): use medium weight for welcome brand 2026-09-21 21:53:05 +08:00
imccyu 092ae937b7 test(web): cover session recovery across server restarts 2026-09-21 21:47:52 +08:00
imccyu b1921cf56d fix(client): keep artifact revisions stable across server restarts 2026-09-21 21:47:52 +08:00
Turtle b13bbc027c feat(agent-preset): progressive creator skills and profile shell facts (#4836)
* feat(tool-cordis): inspect live plugin Config schemas

Replace the Host `Builtin` inspect provider, which described a dynamic Host
half the model can no longer define, with a `Config` provider that lists every
live Loader entry with its Config status and projects one entry's native
Schemastery Config into a self-contained JSON Schema document. app-boot exports
its projector, native-schema check, and the shared `loaderExpression`
definition for the live walk; the CLI dump keeps its profile-composition path.

* fix(tool-cordis): refresh config catalog pointer and presets e2e provider list

* feat(agent-preset): progressive creator skills and profile shell facts

Split `cordis-plugin-development` into a short procedure with `references/`
read on demand and copyable `templates/`, replace the source-reading ban in
both creator skills with an ordered list of knowledge sources, and add the
`cordis-composition-reference` skill with the Loader patch dialect and a
generated, doc-sync-gated list of loadable plugin packages. `shell-env`
exposes `DSH_PROFILE` and `DSH_PROFILE_DIR` so skills can address the installed
profile.

* chore(agent-preset): regenerate plugin package list after merging master

* fix(tool-cordis): resolve the Loader at Config query time

A Host composed programmatically without a Loader keeps registering the
inspect providers; `Config.listConfigs` reports the missing Loader when
queried. Refresh the advanced-toolchain recordings whose provider list now
carries `Config` in place of `Builtin`.

* chore(agent-preset): regenerate plugin package list after merging master

* docs: regenerate module graph for the tool-cordis app-boot dependency

* docs: regenerate module graph for the shell-env app-boot dependency

* docs: regenerate event graph pointer for app-boot exports

* fix(agent-preset): address review on creator skills

`Config.listConfigs` reports each entry's resolved `packageDir` through the
profile package lookup, and the skills read READMEs and built declarations
there instead of guessing `$DSH_PROFILE_DIR/node_modules`, which holds only
profile-installed packages. Skills name the PowerShell variable syntax and the
no-profile omission; the composition reference no longer attributes
`overridden` to `list_plugins`; references point at the templates instead of
pasting them. The skills spec renders every skill and bounds it by the pruner
threshold in code points, checks referenced files and the source-reading ban
across all skill files.

* fix(tool-cordis): address review on the Config inspect provider

Report the Loader entry id (unique across include trees) beside the tree-local
patch id, classify group and include carriers as `tree`, treat disabled and
disposed fibers as `inactive`, and drop the unreachable boolean-schema branch
by narrowing the projector's root type. The presets e2e now queries
`Config.listConfigs` over the booted Web profile and projects the shipped
`tools` row; the advanced-toolchain recordings carry the updated manifest.

* fix(tool-cordis): page the Config directory and filter it by plugin name

A full directory of a shipped Web profile exceeds the 50000-byte spill cap
once rows carry more fields, and the spilled preview is no longer JSON.
`Config.listConfigs` now returns pages (`offset`, `limit` up to 100, default
25) with `total` and `nextOffset`, and an exact plugin `name` filter; the
presets e2e reads the shipped rows through the filter.

* chore(agent-preset): refresh skill recordings after the paged Config query

* docs(agent-preset): teach the composition skill to migrate legacy presets

A preset authored before declaration rows lived in
`$DSH_HOME/.agent-presets/<id>/` as `preset.yml` plus `agent.cordis.yml`;
the skill now maps those files onto a declaration bundle and re-checks plugin
names against the package list.

* docs(agent-preset): read skill files through the file-read tool only

Desktop ships the skill directory inside app.asar, which the Host process can
open but shell commands, node, and pnpm cannot. The skills now say to read
references and templates with the file-read tool and copy templates into the
workspace before installing or checking them; the spec rejects shell reads of
skill files.

* docs(agent-preset): document bundle display metadata and package.json icon in the host-plugin reference
2026-09-21 21:28:35 +08:00
07akioni 8b993f43e0 Merge pull request #4845 from deepseek-harness/dev/desktop-devtools
feat(desktop): enable hidden native DevTools shortcuts
2026-09-21 21:07:26 +08:00
Turtle 1b87a2a889 fix(ui-agent-preset): restore the Creator entry on the preset settings page (#4850)
The declarative-preset refactor replaced the settings section's visible
"让 Agent 帮我创建预设模式" entry with a question-mark icon beside the title
that opened an explanatory modal, and rewrote the section's markup and CSS
along with it.

Restore the section as Ziya's commit 1ed59986f7 had it, minus the copy
(复制预设) and view (查看配置) card actions the refactor removed for good:
the roster keeps its cards, mode help and picker row, the custom group stays
on screen with the Creator entry as its tail, and the question-mark modal is
gone. The creator button stages the cordis preset and starts a task in it,
disabled with the picker's own reason while mode selection is off.

Delete and open-location stay out: the registry exposes only `list` and
`select`, and the roster row no longer carries a trust or a document.

The section spec covers the entry's four paths, and the Web scenario
replaces its help-dialog step with one that drives the entry to a host
session composed from cordis.
2026-09-21 21:05:20 +08:00
07akioni e47e85cce6 test(desktop): remove obsolete overlay DevTools assertion 2026-09-21 20:58:15 +08:00
07akioni fc7c9c2e2f fix(desktop): retain update overlay DevTools availability 2026-09-21 20:57:01 +08:00
07akioni 54f6433357 test(desktop): avoid untyped nested overlay matcher 2026-09-21 20:47:26 +08:00
07akioni 355e43c1f3 refactor(desktop): use hidden native DevTools menu shortcuts 2026-09-21 20:43:38 +08:00
Turtle 8c146d978f feat(tool-cordis): inspect live plugin Config schemas (#4834)
* feat(tool-cordis): inspect live plugin Config schemas

Replace the Host `Builtin` inspect provider, which described a dynamic Host
half the model can no longer define, with a `Config` provider that lists every
live Loader entry with its Config status and projects one entry's native
Schemastery Config into a self-contained JSON Schema document. app-boot exports
its projector, native-schema check, and the shared `loaderExpression`
definition for the live walk; the CLI dump keeps its profile-composition path.

* fix(tool-cordis): refresh config catalog pointer and presets e2e provider list

* fix(tool-cordis): resolve the Loader at Config query time

A Host composed programmatically without a Loader keeps registering the
inspect providers; `Config.listConfigs` reports the missing Loader when
queried. Refresh the advanced-toolchain recordings whose provider list now
carries `Config` in place of `Builtin`.

* docs: regenerate module graph for the tool-cordis app-boot dependency

* docs: regenerate event graph pointer for app-boot exports

* fix(tool-cordis): address review on the Config inspect provider

Report the Loader entry id (unique across include trees) beside the tree-local
patch id, classify group and include carriers as `tree`, treat disabled and
disposed fibers as `inactive`, and drop the unreachable boolean-schema branch
by narrowing the projector's root type. The presets e2e now queries
`Config.listConfigs` over the booted Web profile and projects the shipped
`tools` row; the advanced-toolchain recordings carry the updated manifest.

* fix(tool-cordis): page the Config directory and filter it by plugin name

A full directory of a shipped Web profile exceeds the 50000-byte spill cap
once rows carry more fields, and the spilled preview is no longer JSON.
`Config.listConfigs` now returns pages (`offset`, `limit` up to 100, default
25) with `total` and `nextOffset`, and an exact plugin `name` filter; the
presets e2e reads the shipped rows through the filter.
2026-09-21 20:39:29 +08:00
turtle1999 d52227c135 fix(ui-agent-preset): restore the Creator entry on the preset settings page
The declarative-preset refactor replaced the settings section's visible
"让 Agent 帮我创建预设模式" entry with a question-mark icon beside the title
that opened an explanatory modal, and rewrote the section's markup and CSS
along with it.

Restore the section as Ziya's commit 1ed59986f7 had it, minus the copy
(复制预设) and view (查看配置) card actions the refactor removed for good:
the roster keeps its cards, mode help and picker row, the custom group stays
on screen with the Creator entry as its tail, and the question-mark modal is
gone. The creator button stages the cordis preset and starts a task in it,
disabled with the picker's own reason while mode selection is off.

Delete and open-location stay out: the registry exposes only `list` and
`select`, and the roster row no longer carries a trust or a document.

The section spec covers the entry's four paths, and the Web scenario
replaces its help-dialog step with one that drives the entry to a host
session composed from cordis.
2026-09-21 20:19:06 +08:00
07akioni ff16cf535a fix(desktop): suppress DevTools shortcuts behind update overlays 2026-09-21 20:18:40 +08:00
winewill 53abf11bdd fix(desktop): keep the Windows About dialog on the startup locale
Review pointed out that About read its copy from the renderer-reported
Windows language while the shared update dialog stamps its lang and
close label from the startup locale, so an in-app language switch mixed
languages inside one dialog. About now uses the same startup messages
as Check for Updates. The menu click also logs a dialog that fails to
open instead of leaving an unhandled rejection, and the return type no
longer hides the message-box result behind unknown.
2026-09-21 20:11:05 +08:00
07akioni edae410733 feat(desktop): support Command Option I for macOS DevTools 2026-09-21 20:06:43 +08:00
lsdsjy e8778d413d Merge commit 'e3c84634b4' into HEAD 2026-09-21 20:02:27 +08:00
lsdsjy 3306fac787 merge: include latest master in desktop welcome branch 2026-09-21 20:01:07 +08:00
lsdsjy e682ad3e5b fix(ui-settings-account): keep sign-in errors in the login dialog 2026-09-21 19:56:34 +08:00
lsdsjy a2bc1e412c test(account): type browser popup proxy values 2026-09-21 19:54:43 +08:00
lsdsjy d5db0ff0c4 merge: sync master into desktop welcome branch 2026-09-21 19:53:28 +08:00
Yichen Jiang fcd89f691a fix(voice-input): explain model preparation download failures 2026-09-21 19:50:20 +08:00
07akioni 982fb17754 feat(desktop): open renderer DevTools with F12 in packaged builds 2026-09-21 19:49:04 +08:00
Jiawei Song f384ee7418 Merge branch 'master' into fix/4823-message-overflow-review-menu 2026-09-21 19:48:47 +08:00
Yichen Jiang cbae324bfa feat(workspace): stop a Session's running work before archiving it (#4765)
* feat(workspace): stop a Session's running work before archiving it

Archiving was a pure visibility write, so a Session archived mid-turn kept
running out of sight. The Workspace registry now owns archive admission
through two Host events: `workspace/session-activity` reports what still
runs and refuses the plain archive as `workspace/session-active`;
`workspace/session-stop`, requested with `stopActivity`, stops that work the
way the user's own stop actions do before the archive write. The API Session
Controller reports and stops turns, subagent descendants, and owned jobs and
rejects any model step for an archived Session; the Schedule plugin reports
and deletes active reminders. The sidebar turns the refusal into a
stop-and-archive confirmation listing the Host's answer.

* test(workspace): pin the widened archive signature and prove child liveness by heartbeat

The Client inspect catalog spec pins archiveSession's new options
parameter, and the archive e2e reads the Bash child's liveness from a
heartbeat file instead of a pid: the Linux sandbox runs the tool in its own
PID namespace, so the pid it reports is not comparable from the test.

* test(workspace): cover the archive-admission providers and dialog to the per-file gate

Drop the provider's unused disposer and the dialog's unreachable confirm
guard, read reminders without a catch that no composition can reach, and
add the cases the coverage lane found missing: a stop without a jobs
registry, a looping lineage, a descendant whose projections reject, the
dialog's Escape guard and non-Error reason, and a family merged by another
provider.

* fix(workspace): write the archive before stopping, gate subagent lineage, and delete reminders under the schedule barriers

Review round on #4765. The registry now writes the archive set before it
dispatches `workspace/session-stop`, so the durable set already gates
every wake the stops induce; the controller's `agent/pre-step` gate also
blocks subagent descendants of an archived Session and contains each
cancel or kill on its own. The Schedule plugin's stop listener runs as a
management delete in the agent's transaction queue between the same two
persistence barriers the `schedule_delete` tool uses. The confirm dialog
selects singular or plural English lines and keys list rows by index.
Docs: archive-admission section with type-equiv blocks on the workspace
subsystem page, write-first order and residual windows in the READMEs
and the Agent Note, and the missing zh reminders sentence.

* test(web): scope the question composer's New Session wait to the Sessions tree

The wide sidebar's New Session button carries the same visible label as
a fresh session row, so the unscoped `getByText` either settled on the
button before the row existed or hit a strict-mode violation once the
row rendered first, which is how the snapshots lane failed on
ece68c2ab5. Waiting inside the Sessions tree names the row alone.

* fix(schedule): answer archive admission from the owned runtime's fold, not the projection registry

The `sessionProjections.snapshot` API is the Client projection view and
is not a Host read path (review on #4765). `ScheduleRuntime.activeRecords()`
folds the live log the way every drive does, and the archive-admission
listeners ask the owned runtime for that fold: a session without a live
agent or without an owned runtime has nothing armed to fire, so it reports
nothing and has nothing to stop. Covers the runtime-less agent, a fold that
turns unreadable at the persistence barrier, and the stopping/faulted
runtime answering nothing.

* refactor(subagent): report and stop subagent descendants from the Subagent runtime

The `subagent` family of archive admission moves out of the API Session
Controller into `dsh-subagent`, which records the lineage at delegation
and owns the descriptor (review on #4765): `SubagentRuntime` answers
`workspace/session-activity` with the running descendants, labelled from
each child's descriptor through a live Session observation, and
`workspace/session-stop` by cancelling each of them with the parent
cause. The controller keeps turns, owned jobs, and the lineage step gate,
and composes its provider through `ctx.plugin(SessionActivityProvider)`
with declared injections instead of a bare installer call. `dsh-workspace`
becomes an optional type-only peer of `dsh-subagent`.

* refactor(jobs): answer archive admission for owned jobs from the job registry seam

The `job` family moves out of the API Session Controller into `dsh-jobs`
(review on #4765): the seam's constructor installs the
`workspace/session-activity` and `workspace/session-stop` listeners for
every implementation through the abstract `list` and `kill` alone, so the
controller no longer knows the jobs vocabulary and keeps only the turn
family and the archived-lineage step gate. `dsh-workspace` becomes an
optional type-only peer of `dsh-jobs`.

* refactor(workspace): leave SessionActivityKindMap empty and let each provider merge its family

The workspace package no longer names any activity family (review on
#4765): `SessionActivityKindMap` is empty, and each provider merges its own
key from a module both its faces import — `turn` in the API Session
Controller's types, `job` in the job registry seam's view, `subagent` in
the Subagent runtime's control types, `schedule` in the Schedule plugin's
types. The confirm dialog imports those modules for its cases and keeps
the generic line for any other key; the registry and controller suites
merge test-owned families instead of borrowing the providers'.

* refactor(agent): report and stop the running turn from the Agent registry

The `turn` family moves out of the API Session Controller into `dsh-agent`
(review on #4765): the registry's constructor installs the
`workspace/session-activity` and `workspace/session-stop` listeners for
every Agent it publishes, answering with its own lookup and cancelling the
turn with the user cause, and merges the `turn` key from its client-safe
types. The controller keeps only the archived-lineage step gate, renamed
`ArchivedSessionGate`. `dsh-workspace` becomes an optional type-only peer
of `dsh-agent`; the workspace package depends on none of the providers,
so the edge stays acyclic.

Since #3127 a foreground Bash command is a registered job from its start,
so the archive dialog lists it beside the turn; the e2e and its ARIA golden
expect that row, and the capture keeps the running job by design.
2026-09-21 11:46:13 +00:00
lsdsjy 44b15a3377 fix(desktop,credentials): address sign-in review regressions 2026-09-21 19:39:42 +08:00
winewill 89251dde83 Merge remote-tracking branch 'origin/master' into feat/desktop-update-ui-2 2026-09-21 19:39:37 +08:00
sjw1231 7ebbd370cd test(web): preserve quoted file reference coverage 2026-09-21 19:34:51 +08:00
sjw1231 719cc56276 Merge branch 'master' into fix/4823-message-overflow-review-menu 2026-09-21 19:25:06 +08:00
lsdsjy 1137e86aef fix(desktop,ui-theme): align bundled Montserrat fonts with onboarding 2026-09-21 19:19:00 +08:00
winewill 894f25b91e fix(desktop): source the About product name from the locale
The client UI i18n gate rejects literal message text in main.ts; the
Windows About dialog now reads the product name from the dictionaries
like every other shell string.
2026-09-21 19:03:39 +08:00
Yudong Han f65c15a5cf feat(preview): unify document zoom controls (#4785) 2026-09-21 10:49:45 +00:00
Yudong Han d0ddf939f0 refactor(office): transfer PDFs as multipart bytes (#4830)
* refactor(office): transfer PDFs as multipart bytes

* fix(document-preview): address Office review feedback
2026-09-21 10:30:10 +00:00
Turtle 601d6761e4 feat(settings): project volatile Config through profile-backed forms (#4587) 2026-09-21 18:26:03 +08:00
Yichen Jiang da7ca4265a fix(voice-input): explain model download network requirements 2026-09-21 18:09:31 +08:00
Jiawei Song c63e174b7b Merge pull request #4798 from deepseek-harness/fix/4670-review-tab-scroll-and-filename
fix(ui-deliverables): prevent edge rebound and preserve filename endings
2026-09-21 18:08:50 +08:00
Jiawei Song 08653de38a Merge branch 'master' into fix/4670-review-tab-scroll-and-filename 2026-09-21 17:59:51 +08:00
07akioni c82e40d498 Merge pull request #4809 from deepseek-harness/otel/product-telemetry
feat(telemetry): add explicit product event OTLP exporter
2026-09-21 17:59:21 +08:00
winewill bc4e8bafe6 Merge remote-tracking branch 'origin/master' into feat/desktop-update-ui 2026-09-21 17:58:40 +08:00
sjw1231 5847ff8721 chore: merge master into review tab fixes 2026-09-21 17:58:14 +08:00
Yichen Jiang 645e4faab2 fix(voice-input): add a dedicated plugin icon 2026-09-21 17:54:37 +08:00
07akioni 6d13819fbc docs(telemetry): regenerate module graph after package rename 2026-09-21 17:50:32 +08:00
07akioni 3a3aaaf796 Merge pull request #4804 from deepseek-harness/fix/desktop-ripgrep-unpack
fix(desktop): unpack platform ripgrep executables
2026-09-21 17:50:05 +08:00
sjw1231 5aa21749b9 fix(web): wrap long file references and track floating menus
Fixes #4823
2026-09-21 17:48:27 +08:00
winewill c1edda8a37 fix(desktop): show the Windows About command in the update dialog
Electron's About role on Windows falls back to a bare system message
box, which looks nothing like the shell's dimmed update prompts. Design
asked for the version check to match Check for Updates, so the Windows
menu item now opens the shared update dialog with the application name,
localized version line and a single OK button; macOS keeps the native
panel. The About command follows the renderer's chosen language like
the rest of the Windows caption menu.
2026-09-21 17:46:26 +08:00
07akioni 535cb4aae3 fix(telemetry): isolate collector transport and address review 2026-09-21 17:45:19 +08:00
winewill 1566f3f5ad fix(desktop): round the Windows app icon corners further
The Windows badge used a 44px radius on its 1024px plate, which reads as
nearly square on the desktop next to other Windows 11 icons. Raise the
radius to 192px (3/16 of the plate, matching Tencent Meeting's corner)
and re-export the transparent 1024×1024 PNG from the SVG; the ICO for
the app, installer and uninstaller and the About panel image all derive
from that PNG.
2026-09-21 17:40:49 +08:00
winewill 481b6b57e5 fix(desktop): simplify the update-available restart notice
The manual check dialog explained that the release bundles its matching
dsh runtime before mentioning the restart. Users only need to know the
app restarts to finish updating; the bundling detail belongs in the
README, not the confirmation. Both locales carry the shorter sentence.
2026-09-21 17:39:50 +08:00
07akioni b2ad3bf020 Merge remote-tracking branch 'origin/master' into fix/desktop-ripgrep-unpack 2026-09-21 17:37:59 +08:00
creatixchu 754733d5d1 test: update shell spill snapshot for the shared token budget 2026-09-21 17:34:29 +08:00
07akioni 8b89200324 Merge remote-tracking branch 'origin/master' into otel/product-telemetry 2026-09-21 17:33:03 +08:00
winewill dcac4c6eac Merge pull request #4792 from deepseek-harness/build/windows-release-scripts
fix(desktop): 修复 Windows 发布脚本阻断缺陷并建立构建版本权威来源
2026-09-21 17:31:45 +08:00
Yichen Jiang b2db503913 Merge pull request #4458 from deepseek-harness/feat/plugin-detail-slots
feat(web): open plugin detail-page slots and move official settings pages to companions
2026-09-21 17:30:26 +08:00
creatixchu f3c6d0c0df test: refresh multimodal tool catalog after master update 2026-09-21 17:28:00 +08:00
Huanqi Cao 68b44d78ba Merge remote-tracking branch 'origin/master' into fix/pwsh-prompt-readiness
# Conflicts:
#	docs/config-catalog.i18n.yaml
2026-09-21 17:21:55 +08:00
winewill 5fdad8d838 Merge branch 'master' into build/windows-release-scripts 2026-09-21 17:21:48 +08:00
creatixchu 575dc9add0 Merge remote-tracking branch 'origin/master' into fix/4775-multimodal-tool-retention 2026-09-21 17:21:38 +08:00
Turtle 4eb26f0e71 feat(cli): export JSON Schema for Cordis configuration (#4705)
* feat(cli): dump native Cordis config schemas

* test(cli): include schema dump in launcher help snapshot

* feat(cli): export JSON Schema for Cordis configuration

* fix(cli): preserve config schema discovery and annotations

* fix(cli): conservatively project effectful config validation

* refactor(boot): encapsulate profile config schema generation

* fix(cli): follow Loader disabling and native writeback in config schema

- Dormant group/include rows (disabled without group: true) may omit
  config and are recorded as childless trees; the entry schema skips
  their child declarations, matching the Loader, which never creates them.
- Lazy builders run once per document, and a wrapped null-only projection
  keeps a deduplicated type list so the document stays compilable.
- Union widening applies only when an earlier branch can write adapted
  values back through a container; +Infinity max and -Infinity min
  without a step are treated as the native no-ops they are.
- Projection limitations repeat at every entry sharing a Config, and the
  entry schema documents the boolean/expression domain of disabled.
- Record the schema-dump validation policy as an Agent Note.
2026-09-21 09:19:24 +00:00
creatixchu b0be6e79c2 fix: preserve failed PTC image result forwarding semantics 2026-09-21 17:17:03 +08:00
Yichen Jiang aba8b3a29f Merge remote-tracking branch 'origin/master' into feat/plugin-detail-slots 2026-09-21 17:15:10 +08:00
Yichen Jiang 5882efd7ce test(web): re-record the bundle golden for plugin-owned identity
The bundle page now titles the fixture by its package name, prints the
row id as code under the row's title, and drops the missing-description
line, as master's row and official goldens already record.
2026-09-21 17:13:41 +08:00
Yichen Jiang 4b54e59a52 Merge pull request #3127 from deepseek-harness/worktree/job-kill-delivery
feat(jobs): human job kill, timeout promotion, and workflow run_in_background
2026-09-21 17:13:28 +08:00
Yichen Jiang 873140cff3 test(web): follow master's slot fixture and cast rules after the merge
The Plugins page spec's configuration bodies take the view positionally
as master's fixture does, the companion and section specs read a slot
entry's injected face through a Pick of the members they assert instead
of a double assertion, and the no-unknown-casts baseline drops the
entries retired with the moved settings tests.
2026-09-21 17:13:17 +08:00
winewill 735700c4b5 Merge pull request #4789 from deepseek-harness/feat/windows-clean-uninstall
feat(desktop): Windows 卸载随应用清除 Electron 本机数据,保留 .dsh
2026-09-21 17:12:00 +08:00
creatixchu 14c1fec958 test: record mixed-content retention through the headless profile 2026-09-21 17:11:56 +08:00
creatixchu 79468e26dd test: stabilize multimodal spill recovery locators 2026-09-21 17:08:01 +08:00
winewill d54b000bdb fix(desktop): keep the new specs off the built tree and off unknown casts
The afterPack spec mocked the built runtime tree through a path relative
to the spec, which only resolves once `lib/` exists; on a clean checkout
the hook imported the real module and every case failed. It now mocks
the path the hook actually resolves, as the asar-unpack spec does, and
passes with `lib/` absent.

The toolchain probe assertion reached its matcher through an assertion
to unknown, which `verify-no-unknown-casts` forbids. It compares the
reported tools and detail directly instead.
2026-09-21 17:05:33 +08:00
Yifffan f33b604ca2 Merge pull request #4786 from deepseek-harness/fix/4781-dedupe-open-with-apps
fix(native-command): macOS「从应用打开」菜单去重
2026-09-21 17:05:06 +08:00
winewill 0981cd5924 fix(desktop): harden the Windows uninstall data removal after review
Pinned directories are now opened with listing access so the handle takes
part in share checks and a rename or replacement of the directory under the
helper fails; a metadata-only handle did not participate. A DSH_HOME published
as an environment variable protects overlapping targets, and /KEEP_APP_DATA
retains data alongside --updated, matching the flags electron-builder passes
to an older uninstaller. The remover clears read-only attributes, keeps
deleting siblings after a locked file, and removes only ordinary empty scope
parents below %APPDATA% instead of an NSIS RMDir that would also unlink a
junction. The redundant un.DataInit extraction is gone.

The native regression now runs from the installer test driver and covers the
protected root, rename blocking, read-only and locked files and parent
removal; the uninstaller smoke uses a per-run scope, polls for the temp-copy
handoff, asserts InstallLocation after each silent install, and adds
/KEEP_APP_DATA and DSH_HOME-inside cases. The Agent Note keeps the topic's
2026-09-08 filename, states the amendment in the decision, and defers macOS.
2026-09-21 17:01:54 +08:00
Yudong Han 5b4ca4a29e Merge pull request #4808 from deepseek-harness/fix/pr4346-spreadsheet-review
fix(preview): harden spreadsheet conversion
2026-09-21 17:01:28 +08:00
Yichen Jiang 36a29b424d test(web): settle the transient running-job control before conversation goldens
A foreground shell command is a job while it runs, and its removal reaches
the browser one coalesced roster frame after the tool result, so a
conversation golden captured right after the turn could catch the header's
running-job control; the replay round-trip golden did on CI. captureStableAria
now waits for that control to leave unless a scenario keeps it, which the
two job scenarios that capture a running row do.
2026-09-21 17:01:17 +08:00
creatixchu 4694540e2a Merge remote-tracking branch 'origin/master' into fix/4775-multimodal-tool-retention
# Conflicts:
#	docs/module-graph.i18n.yaml
#	docs/module-graph.md
#	docs/module-graph.zh.md
2026-09-21 16:59:45 +08:00
Yichen Jiang ecb66a3497 Merge remote-tracking branch 'origin/master' into feat/plugin-detail-slots
# Conflicts:
#	.agents/notes/implemented/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.i18n.yaml
#	.agents/notes/implemented/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.md
#	.agents/notes/implemented/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.zh.md
#	apps/web/tests/expected/plugin-config/row.expected.md
#	apps/web/tests/fixtures/plugins/fixture-live-client/client.js
#	apps/web/tests/plugin-config.e2e.ts
#	docs/config-catalog.i18n.yaml
#	docs/config-catalog.md
#	docs/config-catalog.zh.md
#	docs/module-graph.i18n.yaml
#	docs/module-graph.md
#	docs/module-graph.zh.md
#	packages/bundle/web-app/package.json
#	packages/client/ui-plugin-manager/README.i18n.yaml
#	packages/client/ui-plugin-manager/README.md
#	packages/client/ui-plugin-manager/README.zh.md
#	packages/client/ui-plugin-manager/src/client/PluginManagerPage.tsx
#	packages/client/ui-plugin-manager/src/client/index.ts
#	packages/client/ui-plugin-manager/src/client/slot-contract.ts
#	packages/client/ui-plugin-manager/tests/components.client.spec.tsx
#	packages/client/ui-settings-plugins/README.i18n.yaml
#	packages/client/ui-settings-plugins/README.md
#	packages/client/ui-settings-plugins/README.zh.md
#	packages/extensions/cordis-client-runner/src/client/slot-catalog.ts
#	pnpm-lock.yaml
2026-09-21 16:59:17 +08:00
creatixchu e52209da40 test: share multimodal snapshot composition 2026-09-21 16:57:59 +08:00
creatixchu 5c1d966c3f test: cover image recovery failures and snapshot registration 2026-09-21 16:55:13 +08:00
Huanqi Cao 74f4c438de test(pty): pin the pwsh fast path by settle reason
The composition suite's idleSilenceMs: 30000 was below the suite's own send
bound (dsh-terminal-bash timeoutMs: 300000), so the comment claiming silence
could settle nothing was wrong: a regressed send still settled as inferred_idle
after ~30 s and only the accumulated case timeout caught it.

The case now records every send's waitReason and requires at least six
stdin_read settlements with no inferred_idle. Reinstating the removed prompt
override fails it in 30 s with 'expected 0 to be greater than or equal to 6'
while the output assertions still pass. The silence tier returns to its product
default.

Also renames the two stale test titles ('private shell prompt', and the
cancellation phase that no longer covers an initialization send), pins the
stdin_read fallback's session retention, and records the interactive-child
consequence of that retention in the package README pair and the Agent Note.
2026-09-21 16:53:15 +08:00
Dudu-0223 552235aab6 docs: align the Chinese config catalog with the regenerated source line 2026-09-21 16:52:05 +08:00
creatixchu c4c18ef0d4 test: align retention checks and catalogs with token budgets 2026-09-21 16:50:35 +08:00
Dudu-0223 38dbb56237 test(agent-team): type Team panel fixtures without unknown casts and regenerate catalogs
The static CI gate rejected four new assertions to unknown in the
rewritten Team panel specs. Build the Session list, Session snapshot,
and injected-callback fixtures as typed values, prune the retired
baseline fingerprints, and regenerate the module graph, config, and
persistence catalogs for the removed Remote dependency edges and the
shifted source lines.
2026-09-21 16:45:40 +08:00
creatixchu ab102138c8 fix: retain ordered tool text and images within a token budget 2026-09-21 16:45:21 +08:00
lsdsjy 4ae923fe6f fix(desktop): refine welcome typography and copy feedback 2026-09-21 16:44:47 +08:00
lsdsjy 4e5e1f9c7a fix(ui-settings-account): allow retry after active sign-in errors 2026-09-21 16:39:31 +08:00
Yichen Jiang 131fb841f2 Merge remote-tracking branch 'origin/master' into worktree/job-kill-delivery
# Conflicts:
#	docs/module-graph.i18n.yaml
#	docs/module-graph.md
#	docs/module-graph.zh.md
2026-09-21 16:39:19 +08:00
winewill b8050f0251 fix(desktop): finish the review items the first pass missed
Three comments in the same review round went unread and still applied.

A failed tag push left the local tag in place, so the printed recovery
`git tag … && git push …` short-circuited on "already exists" and never
pushed. The result now carries the commands still outstanding, and the
tests cover a failed push and a failed tag separately.

Automatic numbering caught every error from resolving the upload
destination, which turned a misconfigured destination into silent local
numbering that can reuse a published index. It now checks whether a
destination is configured at all and lets an invalid one fail, and the
remaining catch names the error it reports.

`pnpmCommand` fell back to a bare `pnpm`, which on Windows is the batch
shim spawnSync cannot start — the failure this branch exists to fix. It
says so instead, and keeps the fallback where a shim is executable.

The afterPack regression this branch introduced earlier reached review
because nothing exercised that hook: one spec calls it directly for an
ordinary build, a prepared qualification runtime, and a build version,
and it fails when the expected version collapses back to one value.
2026-09-21 16:37:50 +08:00
Dudu-0223 1dc518f217 feat(agent-team): drive the Web Team panel from the agentTeam projection
The Team panel read a Remote snapshot on open and behind an explicit
refresh button, while the subagent catalog panel updates from Host
projection frames. The Team projection was Host-only and its `apply`
mutated state in place, so the projection registry could never publish
a client view for it.

Publish a durable-only `agentTeam` wire view: roster rows with their
durable phase, non-deleted task views through the same readiness,
owner-name, and write-overlap derivation the task board returns to
agents, and `failure` beside the last valid state when a persisted Team
record is rejected. `apply` now returns a new state per applied Team
event and replaces only the touched collection, so mailbox-only events
reuse the previous view reference and publish nothing.

The Web panel derives every row from the shared Session store: the Lead
from the current Session's subagent address, running/inactive from
Session status, and the model from the member Session's durable
`modelSelection` projection. Opening requests the Lead's projection
baseline when the store has none, a failed read shows a retry control,
and the refresh button and Remote mount are gone. The `agentTeams/view`
Remote method remains for callers that need live availability and the
configured model.

The Team panel web e2e now asserts that Host-side task creation and
claiming reach the open panel without any user action.
2026-09-21 16:37:10 +08:00
07akioni 3a3422f517 Merge remote-tracking branch 'origin/master' into otel/product-telemetry 2026-09-21 16:34:37 +08:00
Yichen Jiang 0659ded55b refactor(job-controller): drop the subagent ownership fence from job.kill
The registry's owner fence is the only access rule for a human kill: a job
is killable by the session whose list carries it, and a child session's own
jobs are no exception. The fence had pulled two implementation helpers out
of the Session Controller's package entry (plus a safe-host-export exception
for them) so the job controller could import them; both go, and the job
controller no longer depends on the agent registry or the Session Controller.
2026-09-21 16:34:15 +08:00
Turtle 949e875f7c Merge pull request #4732 from deepseek-harness/turtle/favicon-dark-fix-c387d950b8d9
fix(web): select static favicons with media-qualified links
2026-09-21 16:28:26 +08:00
07akioni 910d52f717 feat(telemetry): add explicit product event OTLP exporter 2026-09-21 16:28:18 +08:00
Jiawei Song 9e87907849 Merge branch 'master' into fix/4670-review-tab-scroll-and-filename 2026-09-21 16:27:08 +08:00
Huanqi Cao b900840784 Merge remote-tracking branch 'origin/master' into fix/pwsh-prompt-readiness
# Conflicts:
#	docs/config-catalog.i18n.yaml
2026-09-21 16:26:08 +08:00
Yif b631804d2b Merge remote-tracking branch 'origin/master' into fix/4781-dedupe-open-with-apps 2026-09-21 16:25:47 +08:00
07akioni c1ad3bb493 test(desktop): align ripgrep packaging checks and fixtures 2026-09-21 16:25:03 +08:00
Huanqi Cao 4f74f956ec Merge remote-tracking branch 'origin/master' into fix/pwsh-prompt-readiness
# Conflicts:
#	docs/config-catalog.i18n.yaml
2026-09-21 16:24:49 +08:00
Turtle d1e22a7e24 feat(preset): declare Agent compositions in profile YAML (#4569)
* feat(preset): declare Agent compositions in profile YAML

* fix(preset): share scope binding cleanup and refresh module graph

* fix(preset): use host syntax colors in the YAML editor

* fix(preset): write new profile declarations as block YAML

* fix(preset): align eager activation with browser and worker surfaces

* fix(ui-agent-preset): retain the existing preset card layout

* fix(preset): keep rows waiting for a Host service mounted and re-audit after settlement

A preset row whose Host provider was still activating at declaration time
was reported as waiting for a missing service, and the registry recorded the
definition as broken for the rest of the process. Split the mount audit into
failed and pending rows: failures and root-realm leaks still reject the
mount, while pending rows stay mounted and every read and binding re-audits
them after the Host Loader tree settles.

Remove the accidental self-dependency of @deepseek-ai/dsh-web-app and its
lockfile self-link. Archive the copy-only preset authoring note, which the
declarative-preset note fully supersedes, and update the ralph note's
restore recipe to the profile-patch mechanism.

* chore(lint): re-key and prune the unknown-cast baseline after the master merge

* refactor(web-app): ship each Agent preset as its own bundle patch file

`dsh.bundle.patch` accepts an ordered file list, so the four shipped preset
declarations move out of the 1000-line web patch into
`presets/<id>.patch.yml`, one insert per file, applied after the host patch.
The host patch keeps the preset registry row. Gates and test scaffolds that
read the web layer resolve every patch file through `bundlePatchPaths`
instead of one hardcoded path.

* refactor(preset): drop the save notice injected into live Agents

The notice duplicated the save result the Web editor already shows, and
after the producer-owned message sources it would have needed its own
persisted source kind. Saving now only persists and reports the activation
diagnostic; the persistence catalog is unchanged from master.

* docs: regenerate the module graph after dropping the registry's llm dependency

* test(ui-agent-preset): cover the guide dialog's keyboard handling on the declarative section

* fix(preset): address the remaining review comments

Apply the lowercase id rule only when creating a declaration, so presets
declared with other ids stay editable. Export `isBuiltInPreset` from the
display fold and key the client card groups on it. Remove the dead `error`
and `retry` locale keys, the JSDoc orphaned by the `trust` removal, the
`trust` fields in the plugin-inventory test fixtures, and the stale
`dsh-agent-presets` package name in comments and READMEs.

* refactor(preset): make the Web preset page view-and-select only

Remove the child-plugin YAML editor, the `agent_preset` tool and the
registry's read/save Remote methods. Presets are declared and changed only by
bundle patches; the settings page keeps the roster, the new-task default and
the mode help, and its question icon explains that and offers to start a
Creator-mode task. The `editing-cordis-compositions` skill and the Creator
persona describe the bundle-based path, and the authored skill-load Session
and Web snapshots are refreshed for it.

* docs(skill): make the composition skill self-sufficient for preset authoring

State where the shipped declarations live, list every declaration field,
inline a complete minimal bundle and an override example, and name the
verification calls, so Creator mode no longer reads DSH package sources to
infer the format. Refresh the authored skill-load Session for the new text.

* test(tools): drop the removed agent_preset tool from the catalog expectation
2026-09-21 08:24:18 +00:00
winewill 685ed95f17 Merge remote-tracking branch 'origin/master' into feat/windows-clean-uninstall 2026-09-21 16:20:49 +08:00
Turtle 933d3bf39e Merge pull request #4784 from deepseek-harness/turtle/chip-model-truncate-4889eecc8358
fix(web): collapse model text when composer controls cannot fit
2026-09-21 16:20:18 +08:00
sjw1231 6340df605d refactor(client): share file tree path labels and improve review tests 2026-09-21 16:19:56 +08:00
winewill 0b91ce2126 refactor(desktop): drop the uninstall data-removal log line
Nobody reads the uninstaller details list and NSIS keeps no log file, so the
failure message added no diagnostic value while costing a bilingual string.
Removal failures now leave residue silently; the uninstaller carries no custom
copy.
2026-09-21 16:15:57 +08:00
07akioni b20a383a7a fix(desktop): unpack platform ripgrep executables 2026-09-21 16:15:47 +08:00
winewill 0181baae5b Merge branch 'master' into build/windows-release-scripts
Adopt the `unsignedArtifacts` build path master introduced, in place of
the directory this branch was joining by hand for automatic numbering.
2026-09-21 16:14:26 +08:00
Yichen Jiang 2bba702a6e Merge remote-tracking branch 'origin/master' into worktree/job-kill-delivery 2026-09-21 16:14:21 +08:00
Turtle a9840f055f fix(web): select static favicons with media-qualified links 2026-09-21 16:13:38 +08:00
Yichen Jiang 7a29f57199 chore(session-controller): drop the leftover dsh-jobs type import
The import came with the session.killJob Remote that later moved to the
job controller; nothing in this package reads ctx.jobs any more, and the
package declares no dependency on the seam.
2026-09-21 16:12:14 +08:00
Yif 3cab7723e4 fix(native-command): validate macOS grouping fields without an unsafe any 2026-09-21 16:12:09 +08:00
Turtle 4a2b8486da Merge master to resolve PR #4784 conflicts 2026-09-21 16:11:41 +08:00
Yudong Han 22da693201 Merge pull request #4346 from deepseek-harness/web/excel-frontend-preview
feat(preview): open spreadsheets in the browser
2026-09-21 16:06:37 +08:00
winewill 1b6451a647 fix(desktop): follow the documented release version rules
Review found that `apps/desktop/README.md#release-versions` and the
2026-09-16 version-derivation note already specify this convention, and
that three of their requirements were reversed without saying so. The
rules themselves are right; what was wrong was leaving them undocumented
and rejecting one of the forms they prescribe.

A stable base takes `-test.YYYYMMDD.index`, which validation refused
because such a version ranks below its base. That ordering is the point:
the form belongs to a test feed that never serves the production
release. Both READMEs, the older note, and a new note now record which
requirements this change reverses and why.

Three defects the review found, each reproducible:

- `afterPack` compared the bundled runtime against the product version,
  which broke installed-update qualification: that flow passes a runtime
  whose version was deliberately rewritten. The expected version is now
  a parameter the preparer supplies, and `verifyDesktopRuntime` says
  "bundled X is not the expected Y" rather than naming Electron.
- Upload could only learn the published version from a shell variable,
  conflicting with "release fields come only from the target file". It
  reads the completion record packaging wrote, and a mismatch now prints
  the version that record holds.
- Automatic numbering read one listing page and one fixed directory. It
  now follows every page, discards a listing it could not finish rather
  than reusing an index that would overwrite a published installer,
  bounds the request through the client's own timeout, and reads the
  directory an unsigned build actually writes.

Also: `npm run` and `yarn run` set `npm_execpath` too, so the pnpm
resolution checks the path names pnpm; the argument separator can arrive
after a run script's preset arguments, not only first; and the toolchain
probe, parser, build commit, and tarball readers now have specs.
2026-09-21 16:04:08 +08:00
creatixchu 4e47ba6c1d chore: start multimodal tool result retention work 2026-09-21 16:03:43 +08:00
sjw1231 02a0c70662 fix(ui-deliverables): preserve review fixes in shared file diff 2026-09-21 16:01:02 +08:00
Huanqi Cao 97545d9c8f fix(pty): keep the controlled prompt so persistent pwsh settles fast
tool-pwsh-persistent installs its own `prompt` function after spawn, so
terminal-bash prompt readiness never matched and every send degraded to the
silence tier (idleSilenceMs + handoffGraceMs) under production defaults. This
is the defect the persistent bash tool fixed for the bash stack.

The tool now initializes without a prompt send and detects the no-end-marker
fallback through the seam's stdin_read wait reason instead of matching its own
prompt text. The private prompt constant and its stripping are deleted, so
partial output can carry the backend's own trailing prompt.

Tool calls drop from 8493/3722/3832/3759 ms to 1340/255/251/241 ms (spawn and
initialization plus the first command, then three warm commands; Windows,
production defaults, real Loader composition). The loader composition suite
pushes idleSilenceMs beyond the send bound, so every case fails if prompt
readiness regresses.
2026-09-21 15:57:08 +08:00
yudshj 57bcfac402 Merge remote-tracking branch 'origin/master' into web/excel-frontend-preview 2026-09-21 15:54:49 +08:00
Yichen Jiang 2cbf8df484 Merge pull request #4412 from deepseek-harness/worktree/experimental-voice-input
feat(voice-input): add optional local SenseVoice dictation
2026-09-21 15:53:42 +08:00
winewill ec52049610 docs(desktop): drop the Start menu claim for InstallLocation
On Windows 11 the Start menu context-menu Uninstall opens the installed-apps
list for every Win32 application regardless of the uninstall entry; only MSIX
packages uninstall from there. Keep InstallLocation as standard uninstall-entry
metadata and stop attributing Start menu behavior to it.
2026-09-21 15:48:14 +08:00
yudshj e7287e499e Merge remote-tracking branch 'origin/master' into web/excel-frontend-preview 2026-09-21 15:47:19 +08:00
Yif 1a14166d2b fix(native-command): validate the full handler list and dedupe only for display 2026-09-21 15:46:03 +08:00
Dudu-0223 5e0934ebbb Merge pull request #4698 from deepseek-harness/fix/team-task-board-read-only
fix(web): make Team tasks read-only and contain the panel
2026-09-21 15:45:52 +08:00
Yichen Jiang 080bea0b85 Merge remote-tracking branch 'origin/master' into worktree/experimental-voice-input
# Conflicts:
#	scripts/gen-cordis-catalog.ts
2026-09-21 15:39:45 +08:00
Dudu-0223 a93aa38876 Merge branch 'master' into fix/team-task-board-read-only 2026-09-21 15:38:03 +08:00
yudshj cbe0d84fc1 Merge remote-tracking branch 'origin/master' into web/excel-frontend-preview 2026-09-21 15:37:28 +08:00
CreatixChu 683625b793 Merge pull request #4780 from deepseek-harness/ihsiang/changed-files-hover-preview
feat(web): preview changed-file diffs on hover
2026-09-21 15:36:12 +08:00
yudshj 61a0acc4da fix(preview): satisfy strict spreadsheet lint 2026-09-21 15:35:57 +08:00
Dudu-0223 3324157b7f fix(web): address Team panel review feedback 2026-09-21 15:30:18 +08:00
imccyu 85f16860f5 Merge pull request #4758 from deepseek-harness/feat/chat-step-groups
feat(web): add definition-owned conversation groups
2026-09-21 15:29:30 +08:00
creatixchu e17f505504 Merge master into ihsiang/changed-files-hover-preview 2026-09-21 15:27:19 +08:00
winewill 4ab8fbfacc fix(desktop): name the commit a build records instead of its provenance
The concrete-terms gate forbids "provenance": the repository asks for the
exact source, field, or identity a value holds. The module records one
commit and whether its tree was dirty, so it and its callers say that.

The toolchain step also has to stay out of the way of the packaging
entry's error-recording test, which fakes the host platform and would
otherwise report the absent Windows compilers rather than the failure it
exercises. The test mocks the probe, as it already does for the release
environment and the signing keychain, and expects the new stage.
2026-09-21 15:26:29 +08:00
Yichen Jiang 4c7c7a552f Merge remote-tracking branch 'origin/master' into worktree/job-kill-delivery 2026-09-21 15:26:10 +08:00
Yichen Jiang bb20149360 refactor(shell): register foreground commands as jobs at start and drop the promotion protocol
With a job registry composed, tool-bash and tool-pwsh now register every
command with ctx.jobs as it starts and wait on the job with the registry's
own bounded wait: a command that finishes in time returns the ordinary
foreground result and its record is removed, one that outlives the timeout
returns the same job id it already had, and a kill from outside the call is
reported as `[stopped: <reason>]`. Nothing changes hands at the deadline, so
the shell seam loses `onExpiry: 'offer'`, `ShellExecution.promotion`, and
`ShellPromotionOffer`, and both local executors drop their offer arm.

The registry stays optional: the tools register a foreground-only definition
while no registry is composed and swap to the job-backed one inside a
`ctx.inject(['jobs'])` fork, so a composition without jobs keeps a plain bash
with no job tools in the model's schema. A refused registration at the start
(admission, no controller) runs under the executor's deadline kill, and a wait
that expires before the process spawned ends as the preparation timeout.

JobRegistry gains `remove(id, caller?)` and the `settled` event reports
`awaited`, so dsh-tool-jobs no longer keeps its own wait ledger; it withholds
notices for awaited settlements and for kills the model requested. The tool
catalog harvest composes the registry so the shipped schema stays documented,
and the two compaction fixtures move their context window from 10000 to 10300
tokens because the branch's larger tool schemas pushed their condensed history
over the old trigger, which requested a fourth scripted model call.
2026-09-21 15:23:08 +08:00
Yichen Jiang 262ee1bc23 fix(voice-input): address lifecycle and provider review feedback 2026-09-21 15:21:16 +08:00
sjw1231 c6e38b364f fix(client): resolve review tab conflicts and path label coverage 2026-09-21 15:19:38 +08:00
Huanqi Cao eb7d3af91c Merge pull request #4582 from deepseek-harness/fix/windows-acl-delete-escape
fix(sandbox): confine Windows deletes to the granted root
2026-09-21 15:18:36 +08:00
winewill 93a65a9a94 feat(desktop): remove Electron data on Windows uninstall without a custom page
Product dropped Harness-home cleanup from the uninstaller, so the opt-in page,
its bilingual dialog, the uninstall.ini home record and the overlap check go
away. The uninstaller now always removes the Electron user-data directory,
the %APPDATA% product directory and the updater cache through the native
helper; the Harness home is never touched. Silent uninstalls remove the same
data, upgrades retain it, and a locked file is logged instead of stopping the
uninstall.

Electron derives user data from the scoped package name, so the removal
targets %APPDATA%\@deepseek-ai\dsh-desktop and removes the empty scope
directory afterwards. The previous fixture used an unscoped name and never
reached that directory; the fixture now uses a scoped package name.

The native uninstall smoke runs end to end on the development host after two
test fixes: the interactive uninstaller was launched hidden, which made every
control invisible to the text search, and the Chinese finish text matched
"已经从" instead of MUI's "已从你". The native remover regression is renamed
data-cleanup-test.exe because UAC installer detection demanded elevation from
the unmanifested uninstall-data-test.exe.
2026-09-21 15:14:44 +08:00
Dudu-0223 35489c0f5c fix(web): manage Team panel keyboard focus 2026-09-21 15:09:08 +08:00
Dudu-0223 59b5a4ac0f chore: prune removed Team test assertion baselines 2026-09-21 15:09:07 +08:00
Dudu-0223 253fb5e66d fix(web): make Team tasks read-only and contain the panel 2026-09-21 15:09:07 +08:00
07akioni 7717eb39c0 Merge pull request #4553 from deepseek-harness/07akioni/fix-desktop-office-asar
fix(desktop): unpack native Office engine and verify packaged conversion
2026-09-21 15:08:18 +08:00
sjw1231 795e5024bc fix(ui-deliverables): fill diff row backgrounds across scroll width 2026-09-21 15:05:43 +08:00
Yichen Jiang d44d899c71 Merge pull request #4783 from deepseek-harness/feat/docs-image-viewer
为文档截图复用 Mermaid 弹层与缩放逻辑
2026-09-21 15:04:42 +08:00
Turtle cf323fdaac docs(web): clarify model collapse contract and geometry ownership 2026-09-21 15:01:04 +08:00
imccyu a67ad91237 feat(client): expose indexed positions to group definitions 2026-09-21 14:56:14 +08:00
Yif 64094e8991 test(web): match the 36px unpreviewable open control height 2026-09-21 14:56:01 +08:00
lsdsjy 7079376f28 fix(ui-settings): restore settings panel dimensions and radius 2026-09-21 14:53:25 +08:00
Huanqi Cao 11d36dd102 Merge remote-tracking branch 'origin/master' into fix/windows-acl-delete-escape
# Conflicts:
#	docs/config-catalog.i18n.yaml
2026-09-21 14:51:40 +08:00
winewill ee0fc82267 fix(desktop): check the bundled runtime against the product version
Injecting a build version renamed the Electron shell but not the dsh
runtime packed into it, and afterPack required the two to be equal, so
every build carrying a build version failed after packaging finished.
The bundled runtime is the dsh package at the product version, which is
what the check now compares against; the app already reports the shell
version and the bundled runtime version separately to the update policy.

Rename the flag to --build-version. The repository already has
DOWNLOAD_TEST_RELEASE_ID for the opaque test feed directory, so calling
a version a release id invited a reader to confuse the two. Accept a
forwarded `--` as well, because `pnpm run <script> -- --build-version x`
passes the separator through as an argument and every following option
would otherwise parse as a second target.
2026-09-21 14:45:35 +08:00
sjw1231 60cdd547ae fix(ui-deliverables): preserve filename endings in review headers 2026-09-21 14:45:18 +08:00
imccyu 7f382a1e2c Merge pull request #4757 from deepseek-harness/worktree-npmlink
feat(boot): resolve linked plugin peers from the running installation
2026-09-21 14:38:32 +08:00
gengruilin b1d878b33d fix(website): address shared viewer review feedback 2026-09-21 14:31:37 +08:00
yudshj ad9f77db04 fix(preview): compact spreadsheet formula warning 2026-09-21 14:28:15 +08:00
winewill 950ed3f6df feat(desktop): publish a build version without rewriting the manifests
Packaging a build for a test feed or a colleague required bumping the
version in 295 manifests plus the lockfile first, because the version a
build publishes was only ever read from the checkout. Those edits cannot
be committed, so every such build left the working tree dirty, and the
`<version>.<date>.<sequence>` convention behind them lived only in the
operator's memory.

The version a build publishes is now an input. `--release-id` names it,
`--release-id auto` numbers it after the builds already published to the
destination bucket (falling back to local artifacts, since a fresh CI
checkout has none), and it reaches electron-builder through
extraMetadata, the update feed, and the upload validation as one value.
The manifests keep the product version, aligned with the dsh npm
package, and a release still publishes exactly that.

Validation goes through `semver` itself, because `electron-updater`
compares feed versions with `semver.gt` against the installed
`app.getVersion()`: a build version must outrank the product version it
extends, which holds for `0.1.6-alpha.2.20260921.1` over
`0.1.6-alpha.2` and cannot hold on a stable version, where a fourth
release number is not a version at all.

Two things a build could not previously answer for itself:

- Artifacts now carry the commit they were packaged from and whether
  that checkout was dirty, so a build handed to a colleague is traceable
  without a tag.
- A production upload tags that commit as `desktop-v<version>` once the
  artifacts are public, and reports what to run by hand rather than
  failing an upload that already completed. Test and local builds are
  deliberately left untagged.

A toolchain step now probes tar and the Windows installer compiler
before the first build step, so a host that cannot finish the sequence
says so in seconds rather than minutes.
2026-09-21 14:23:48 +08:00
winewill dbbecd2461 feat(desktop): opt-in Windows uninstall data removal
The Windows uninstaller keeps every user file by default and adds a custom
page with unchecked options for desktop browser data and update caches, the
desktop plugin profile, and the complete Harness home. Selecting the whole
home requires a native warning that names the actual folder and the CLI
impact, defaults to Cancel, and forces the desktop child selection. Silent
and upgrade uninstalls never remove data, including through the legacy
--delete-app-data flag, which the adapted upstream template no longer honors.

Packaged Desktop records its resolved home in uninstall.ini under the
Electron user-data directory so a custom DSH_HOME is offered instead of the
default; a length field detects truncated NSIS reads and an invalid record
disables whole-home removal. The native remover in window-frame.dll rejects
protected roots and overlap with retained data, holds directory handles
during traversal, and unlinks reparse points without entering their targets.

Installation now also writes InstallLocation on the Windows uninstall entry,
which Explorer needs to associate the Start menu shortcut with this entry so
that the context-menu Uninstall launches the uninstaller directly instead of
opening the installed-apps list. The upstream template only records the
value under its private key.

Closes #3993
2026-09-21 14:22:39 +08:00
Ziya d7d099c7eb Merge pull request #4772 from deepseek-harness/copy/ptc-description-20260920
fix(web): describe PTC mode by the tasks it suits
2026-09-21 14:19:44 +08:00
Turtle 69fb366034 test(web): provide font events in built-bundle jsdom fixtures 2026-09-21 14:19:02 +08:00
Yifffan f91f461dc6 Merge branch 'master' into fix/4781-dedupe-open-with-apps 2026-09-21 14:16:43 +08:00
Yif 9a485724f6 fix(open-in-app): scale the empty-state opener chevron up and trim its height to 36px 2026-09-21 14:15:11 +08:00
Ziya a95dfea48d Merge branch 'master' into copy/ptc-description-20260920 2026-09-21 14:11:33 +08:00
Yichen Jiang b3d54fcef7 Merge pull request #4605 from deepseek-harness/worktree/model-provider-entry-ux-dde717
feat(models-ui): one add-provider entry with a segmented mode switch
2026-09-21 14:10:47 +08:00
imccyu 7252e5823b fix(boot): keep linked-root removal independent of relinking 2026-09-21 14:00:52 +08:00
imccyu 73e9efbbdf docs(boot): define linked-root removal across generations 2026-09-21 14:00:52 +08:00
imccyu cf41c158ba fix(boot): keep host lookups native and fix source smoke fixtures 2026-09-21 14:00:52 +08:00
imccyu c9d4b7561d fix(boot): apply linked peers at each native lookup position 2026-09-21 14:00:52 +08:00
imccyu 3e7af9cfbb feat(boot): resolve linked plugin peers from the running installation 2026-09-21 14:00:51 +08:00
imccyu a946008275 docs(boot): document linked plugin peer resolution 2026-09-21 14:00:51 +08:00
Yif 6cb1cd921f fix(native-command): collapse duplicate macOS handler copies to one menu entry 2026-09-21 13:58:18 +08:00
Yichen Jiang ed0700ffb5 Merge remote-tracking branch 'origin/master' into worktree/model-provider-entry-ux-dde717 2026-09-21 13:57:38 +08:00
07akioni 818c51f969 Merge remote-tracking branch 'origin/master' into 07akioni/fix-desktop-office-asar 2026-09-21 13:57:30 +08:00
Turtle c136d821dd fix(web): collapse model text when composer controls cannot fit 2026-09-21 13:56:47 +08:00
Turtle 4aba48ec03 fix(cordis): register Host inspect providers once per process (#4742)
`tool-cordis` registered the four first-party Host inspect providers from
every preset row that mounted it. The Host registry rejects a duplicate
provider id, so a second preset carrying `tool-cordis` — or a user preset
copied from a shipped one — failed to mount in the same process.

- Add `@deepseek-ai/dsh-tool-cordis/host` (`cordis-inspect-providers`),
  the host-plane row that registers the providers; the Web host
  composition mounts it beside `cordis-host-runner`. The per-session
  `tool-cordis` row now only registers the tools and its prompt section.
- Gate the `cordis` preset's `tool-plugin-manager` row on `profileContext`,
  the same condition `dsh-base` applies to the host `plugin-manager`
  service, so a profile-less embedding still mounts the preset.
2026-09-21 05:55:09 +00:00
yixiangihsiang 440070e4f8 fix(web): address diff preview review findings 2026-09-21 13:51:53 +08:00
07akioni 4e5aec6a74 Merge remote-tracking branch 'origin/master' into codex/office-package-master 2026-09-21 13:46:31 +08:00
Yifffan b6cb6be3da Merge pull request #4767 from deepseek-harness/fix/desktop-minimize-vibrancy
fix(desktop): 修复 macOS 最小化恢复后侧边栏毛玻璃短暂透明,同时修改 session 跑马灯效果
2026-09-21 13:42:11 +08:00
gengruilin cb718ce9da feat(website): enlarge screenshots with the shared media viewer 2026-09-21 13:40:37 +08:00
07akioni e0b5ab5ebd Merge master into PR #4553 and resolve runtime helper import 2026-09-21 13:39:09 +08:00
imccyu 7dd6d1fc31 docs(client): mark the grouping foundation implemented 2026-09-21 13:30:25 +08:00
imccyu b9252673e9 fix(client): report only changed turn locations 2026-09-21 13:30:25 +08:00
imccyu b3549ee455 fix(client): preserve grouped chat navigation and view lifecycle 2026-09-21 13:30:25 +08:00
imccyu c1c3410cf0 fix(benchmarks): declare grouping runtime dependency 2026-09-21 13:30:25 +08:00
imccyu 0076de86fc test(client): cover group injection and conversation fixtures 2026-09-21 13:30:25 +08:00
imccyu a7d457349f docs(client): record the conversation grouping foundation 2026-09-21 13:30:25 +08:00
imccyu 1b3b56c8f1 feat(client): render stable conversation group references 2026-09-21 13:30:25 +08:00
imccyu c890269a19 feat(client): add definition-owned conversation groups 2026-09-21 13:30:25 +08:00
winewill 766eee63eb fix(release): run pnpm and tar portably on Windows
spawnSync cannot start the pnpm .cmd shim without a shell, so the bump
step's lockfile refresh failed with ENOENT on Windows after rewriting
every manifest. Resolve pnpm to a JavaScript entry executed by the
current Node: the run-script's npm_execpath when present, otherwise the
workspace's own pnpm dependency (located on disk because pnpm's manifest
does not export its bin entry).

GNU tar parses the colon in an absolute Windows path as a remote-host
separator and aborts, so the tarball readers now run tar in the
tarball's directory and pass only its file name.
2026-09-21 13:30:12 +08:00
yudshj 767cb38567 fix(locales): update titles for Office document in Chinese and English 2026-09-21 13:20:45 +08:00
yudshj 15415600d1 fix(preview): render spreadsheet values as literal text 2026-09-21 13:20:45 +08:00
yudshj 9f6434d24b refactor(web): move Office loading into its injected face 2026-09-21 13:20:45 +08:00
yudshj f51092015f refactor(preview): align spreadsheets with shared file preview structure 2026-09-21 13:20:45 +08:00
yudshj e70fa89400 feat(preview): support xls csv and tsv 2026-09-21 13:20:45 +08:00
yudshj 30b6abad5c fix(preview): retain Excel worksheet selections 2026-09-21 13:20:44 +08:00
yudshj 087dc65e89 feat(preview): render Excel workbooks in the browser 2026-09-21 13:20:44 +08:00
yixiangihsiang 90a1b23e4a feat(web): preview changed-file diffs on hover 2026-09-21 13:19:52 +08:00
Yudong Han eaf0f68bb2 Merge pull request #4401 from deepseek-harness/perf/workspace-file-binary-transfer
feat(api): support binary fields in Remote results
2026-09-21 05:18:16 +00:00
Yifffan d7318cb7b5 Merge branch 'master' into fix/desktop-minimize-vibrancy 2026-09-21 13:17:27 +08:00
_Kerman e4fbb92570 Merge pull request #4770 from deepseek-harness/xtr/normalize-agent-cancel-cause
fix(agent-loop): copy the cancel cause before logging turn/end
2026-09-21 13:13:36 +08:00
Yif 8dafa0829b Merge remote-tracking branch 'origin/fix/desktop-minimize-vibrancy' into fix/desktop-minimize-vibrancy 2026-09-21 13:04:29 +08:00
Yif b4886c7e5b feat(workspace): marquee hovered session titles with fades on both cut edges 2026-09-21 13:02:48 +08:00
Yif 44fe86eaf8 test(desktop): cover the minimize backdrop swap and extract the chrome fallback fill 2026-09-21 13:02:45 +08:00
Ziya 63b7a0f7c1 Merge branch 'master' into copy/ptc-description-20260920 2026-09-21 12:53:07 +08:00
Yudong Han e98b5703c7 refactor(api): move Remote result projection to gateway 2026-09-21 12:50:42 +08:00
Yudong 2aac7c8ef5 fix(connection): keep narrowed attachment path type 2026-09-21 12:50:41 +08:00
Yudong 285f0a7005 fix(connection): narrow binary transport values 2026-09-21 12:50:41 +08:00
Yudong 983b89f9a5 refactor(fs): default unsupported file watching 2026-09-21 12:50:41 +08:00
Yudong ecf6acfb15 feat(api): support binary fields in Remote results 2026-09-21 12:50:40 +08:00
_Kerman 2e99e6021d Merge remote-tracking branch 'origin/master' into xtr/normalize-agent-cancel-cause 2026-09-21 12:45:05 +08:00
CreatixChu 6fba9d3bfb Merge pull request #4544 from deepseek-harness/worktree/issue-3881-folder-drop
feat(web): 桌面端支持将文件夹拖入输入框
2026-09-21 12:44:44 +08:00
Turtle 7520fe94e1 refactor(preset): 精简创造模式的 system prompt (#4745)
* refactor(preset): trim the creation-mode system prompt

The `cordis` preset persona repeated what the plugin_manager and
cordis_inspect_* tool descriptions and the two shipped skills already
carry. Keep only identity, the shipped-preset constraint, the visual
request routing rule, and which skill to load for what.

Drop the tool-cordis prompt section, which restated its own tool
descriptions, and the cordis_inspect_query navigation hints that the
provider manifests returned by cordis_inspect_list already state.
Shorten both skill descriptions so the first-turn catalog stays small.

First-turn request for the cordis preset, measured through the DeepSeek
API with deepseek-v4-flash: 9572 -> 8975 prompt tokens.

* docs: regenerate config catalog after section order removal

* docs: regenerate module graph after tool-cordis dependency removal

* refactor(preset): give the cordis preset the standard persona

Creation mode's system prompt now equals standard mode's; the skill
catalog and the shipped skills carry every routing and off-limits rule.
The plugin skill description states the visual-request destination.

First-turn request for the cordis preset: 9572 -> 8815 prompt tokens.

* fix(review): document current tool-cordis state and pin the shared persona

The tool-cordis README pair now states that only the two tool schemas
enter requests, that disposal removes tools and providers, and that
the first-turn skill catalog carries the routing. The Web skill row
test resolves the owner's selected fixture generation instead of a
fixed v3 path. The preset e2e asserts the cordis persona sections
equal standard's.
2026-09-21 04:43:38 +00:00
_Kerman 3773c4f85b docs(agent-loop): correct the owning cancellation note
The 2026-07-16 explicit-turn-cancellation note still described a frozen,
detached signal reason and a two-variant cause union. The README paragraph
added here points readers at that note as the owner of the signal
lifecycle, so the stale mechanism was load-bearing.

It now records why freezing the caller's cause is not an alternative:
undici assigns `stack` from strict-mode code, so a frozen reason makes
fetch reject with `TypeError: Cannot add property stack` in place of the
abort reason.

Also names `turn/end`'s declared `TurnEndCancelCause` in the README and
takes the reviewer's helper-doc wording.
2026-09-21 12:42:36 +08:00
lsdsjy a43da8740d fix(desktop,ui-settings-account): resolve branch hygiene violations 2026-09-21 12:42:03 +08:00
Yichen Jiang b42a93d62f Merge remote-tracking branch 'origin/master' into worktree/job-kill-delivery
# Conflicts:
#	docs/config-catalog.i18n.yaml
#	docs/event-producer-consumer.i18n.yaml
#	docs/module-graph.i18n.yaml
#	docs/tool-catalog.i18n.yaml
#	packages/api/session-controller/README.i18n.yaml
#	packages/api/session-controller/src/commands.ts
#	packages/client/ui-jobs/tests/browser-plugin.client.spec.ts
#	packages/client/ui-jobs/tests/job-list-action.client.spec.tsx
#	packages/shell/tool-bash/README.i18n.yaml
#	packages/shell/tool-bash/src/background.ts
#	packages/shell/tool-pwsh/src/background.ts
2026-09-21 12:40:44 +08:00
Ziya f206efb628 Merge branch 'master' into copy/ptc-description-20260920 2026-09-21 12:38:17 +08:00
creatixchu 6ac2d741a6 test(llm): retain pending mock listeners through teardown 2026-09-21 12:36:54 +08:00
creatixchu 6f07deab31 test(llm): preserve coverage timeout budget and drain stalled fixtures 2026-09-21 12:33:59 +08:00
creatixchu 49ff1f815d Merge origin/master into worktree/issue-3881-folder-drop 2026-09-21 12:33:50 +08:00
Yichen Jiang 35a02e1658 Merge pull request #3052 from deepseek-harness/worktree/process-plugin-streaming-72f9b3
feat(jobs): consolidate the job seam around one output ring and stream it to the session-header job list
2026-09-21 12:27:29 +08:00
Yichen Jiang dc1f87e890 Merge branch 'master' of github.com:deepseek-harness/deepseek-harness into worktree/process-plugin-streaming-72f9b3 2026-09-21 12:03:46 +08:00
Yichen Jiang 21d2c8b948 Merge pull request #4768 from deepseek-harness/worktree/input-plugin-extension-hooks
feat(web): expose input plugin extension hooks
2026-09-21 12:03:10 +08:00
winewill 4df3de2075 fix(desktop): sharpen installer brand art and progress-page scaling
Re-render the four installer brand banners from the vector sources:
the badge follows the Figma spec (three-stop gradients, layered drop
shadows) and the whale no longer inherits shape-rendering=crispEdges,
which disabled anti-aliasing and left jagged edges. The wordmark now
reaches its design color instead of a soft #353638.

Draw the progress-page brand with high-quality bicubic interpolation
and half-pixel offset so the always-2x bitmap stays crisp when scaled
down at 96 DPI.
2026-09-21 12:02:49 +08:00
lsdsjy f0b896aac8 refactor(desktop): render welcome with React and shared assets 2026-09-21 11:58:18 +08:00
creatixchu e30f0c7774 test(terminal): check descendant quiescence instead of PID removal 2026-09-21 11:58:04 +08:00
Turtle a0f59aac40 fix(llm-pi-ai): stop re-parsing streamed tool-call arguments on every delta (#4740)
pi-ai 0.85.1 runs parseStreamingJson over the whole accumulated argument
JSON on every tool-call delta, so one large tool call costs O(n²) CPU on
the event loop and a multi-megabyte argument stream stalls every session
in the process. Patch the six stream adapters to parse once at
toolcall_end, document the patch, and cover both OpenAI protocols.

Fixes #4739
2026-09-21 03:53:37 +00:00
mektpoy a2487e34b7 fix(desktop): lock the development runtime target and complete the resolution
- main-startup: assert primaryRuntime for the unpackaged Host and cover a
  missing DSH_DESKTOP_PRIMARY_RUNTIME_DIR as a fatal startup dialog with no
  Host started.
- test-workspace-updates: pass the prepared primary-runtime directory instead
  of an application/runtime path the runner never creates.
- development-project: derive the descriptor platform/arch from
  resolveDesktopBuildTarget so it matches the prepared payload.
- main: resolve the unpackaged primary runtime once at startup instead of
  inside the Host factory.
- dev: keep the environment override symmetric with its neighbours.
- README: document the variable as required for unpackaged launches and record
  the remaining win32-arm64 payload architecture limitation.
2026-09-21 11:53:32 +08:00
Yichen Jiang bb7e92c999 Merge remote-tracking branch 'origin/master' into worktree/process-plugin-streaming-72f9b3
# Conflicts:
#	docs/event-producer-consumer.i18n.yaml
#	docs/event-producer-consumer.md
#	docs/persistence-catalog.i18n.yaml
#	docs/persistence-catalog.md
#	docs/persistence-catalog.zh.md
#	docs/persistence-schema.json
2026-09-21 11:51:54 +08:00
ZiyaZhang e649981497 fix(web): describe PTC mode by the tasks it suits
The PTC card in Settings explained the mechanism (the agent chains tools
through code) rather than which tasks to pick it for, so readers could
not tell it apart from Standard mode the way the Minimal and Creator
cards allow. Reword the Chinese and English card and new-task menu copy
to name the tasks: batch tool calls whose results are then filtered,
organized, deduplicated, counted, or summarized. Update the four ARIA
snapshots that embed this text.
2026-09-20 20:49:56 -07:00
Yichen Jiang 3f17ab99d4 refactor(chat): restore ui-chat import paths to master
Six ui-chat modules carried import-path and line-wrap differences unrelated
to the jobs seam; they now match master byte for byte.
2026-09-21 11:46:55 +08:00
_Kerman 35f3abf1fa fix(agent-loop): copy the cancel cause before logging turn/end
Node's fetch assigns an enumerable `stack` string onto the abort reason
object it receives, and the loop logged that same object into `turn/end`.
The undici trace became durable session data under a field no reader
declares. A transport adding a property JSON discards instead made
`Session.append` reject the ending, leaving a turn with no close.

`abortedCancelCause()` narrows the untyped `AbortSignal.reason` once and
copies the declared fields, replacing the three `as AgentCancelCause`
assertions at the two read sites and the log write.
2026-09-21 11:45:03 +08:00
creatixchu 20be0ff297 Merge origin/master into worktree/issue-3881-folder-drop 2026-09-21 11:41:36 +08:00
Yichen Jiang 4625cd376f Merge branch 'worktree/input-plugin-extension-hooks' into worktree/experimental-voice-input 2026-09-21 11:40:10 +08:00
Yichen Jiang 2ab948a11b Merge remote-tracking branch 'origin/master' into worktree/input-plugin-extension-hooks 2026-09-21 11:39:00 +08:00
creatixchu d0ac0afbe2 fix(composer): recognize pasted directories through entry metadata 2026-09-21 11:38:44 +08:00
CreatixChu c240582109 Merge pull request #4646 from deepseek-harness/feat/4562-file-open-apps
feat: 统一文件和目录的跨平台打开控件
2026-09-21 11:38:41 +08:00
Yichen Jiang 9746e97260 Merge branch 'worktree/input-plugin-extension-hooks' into worktree/experimental-voice-input 2026-09-21 11:36:00 +08:00
Yichen Jiang 9df1b6033a fix(web): keep the empty composer dock collapsed 2026-09-21 11:35:35 +08:00
Yichen Jiang 61719629fc Merge remote-tracking branch 'origin/master' into worktree/process-plugin-streaming-72f9b3
# Conflicts:
#	apps/web/tests/expected/agent-preset-selection/header.expected.md
#	docs/architecture.i18n.yaml
#	docs/config-catalog.i18n.yaml
#	docs/config-catalog.md
#	docs/event-producer-consumer.i18n.yaml
#	docs/event-producer-consumer.md
#	docs/event-producer-consumer.zh.md
#	docs/module-graph.i18n.yaml
#	docs/module-graph.md
#	docs/module-graph.zh.md
#	docs/persistence-catalog.i18n.yaml
#	docs/persistence-catalog.md
#	docs/persistence-catalog.zh.md
#	docs/persistence-schema.json
#	docs/subsystems/web-client.i18n.yaml
#	packages/api/README.i18n.yaml
#	packages/api/README.md
#	packages/api/README.zh.md
#	packages/api/session-controller/README.i18n.yaml
#	packages/api/session-controller/README.md
#	packages/api/session-controller/README.zh.md
#	packages/api/session-controller/src/client/sessions/manager.ts
#	packages/api/session-controller/src/client/sessions/service.ts
#	packages/api/session-controller/tests/manager.client.spec.ts
#	packages/client/locale/tests/language-row.client.spec.tsx
#	packages/client/ui-chat/tests/chat-view.client.spec.tsx
#	packages/client/ui-chat/tests/transcript-view-row.client.spec.tsx
#	packages/client/ui-conversation/tests/conversation-registry.client.spec.ts
#	packages/client/ui-conversation/tests/enter-behavior-row.client.spec.tsx
#	packages/client/ui-conversation/tests/input-bar.client.spec.tsx
#	packages/client/ui-conversation/tests/input-matrix.client.spec.tsx
#	packages/client/ui-conversation/tests/input-scenarios.client.spec.tsx
#	packages/client/ui-conversation/tests/skeleton.client.spec.tsx
#	packages/client/ui-deliverables/tests/deliverables.client.spec.tsx
#	packages/client/ui-jobs/tests/job-list-action.client.spec.tsx
#	packages/client/ui-layout/tests/app-frame.client.spec.tsx
#	packages/client/ui-layout/tests/document-title.client.spec.tsx
#	packages/client/ui-open-in-app/tests/open-in-app-action.client.spec.tsx
#	packages/client/ui-primitives/README.i18n.yaml
#	packages/client/ui-session/tests/ui-session.client.spec.ts
#	packages/client/ui-settings-general/tests/settings-root.client.spec.tsx
#	packages/client/ui-settings-unarchive-sessions/tests/components.client.spec.tsx
#	packages/client/ui-subagent/README.i18n.yaml
#	packages/client/ui-subagent/README.md
#	packages/client/ui-subagent/README.zh.md
#	packages/client/ui-subagent/src/client/SubagentHeaderLineage.tsx
#	packages/client/ui-subagent/tests/conversation-ui.client.spec.tsx
#	packages/client/ui-theme/tests/appearance-row.client.spec.tsx
#	packages/client/ui-theme/tests/font-size-row.client.spec.tsx
#	packages/client/ui-tool/tests/coverage-tails.client.spec.tsx
#	packages/client/ui-tool/tests/diff-card.client.spec.tsx
#	packages/client/ui-tool/tests/image-card.client.spec.tsx
#	packages/client/ui-tool/tests/read-card.client.spec.tsx
#	packages/client/ui-tool/tests/terminal-card.client.spec.tsx
#	packages/client/ui-trajectory/tests/views.client.spec.tsx
#	packages/client/ui-user-questions/tests/plan-review-panel.client.spec.tsx
#	packages/client/ui-user-questions/tests/user-questions-composer.client.spec.tsx
#	packages/client/ui-workflow-run/tests/workflow-run.client.spec.tsx
#	packages/client/ui-workspace/tests/apply.client.spec.ts
#	packages/client/ui-workspace/tests/tree.client.spec.ts
#	packages/client/ui-workspace/tests/workspace-browser.client.spec.tsx
#	packages/client/ui-workspace/tests/workspace-picker.client.spec.tsx
#	packages/client/ui-workspace/tests/workspaces-service.client.spec.ts
#	packages/extensions/cordis-client-runner/src/client/slot-catalog.ts
#	packages/jobs/tool-jobs/src/index.ts
#	packages/shell/tool-bash/README.i18n.yaml
#	packages/test-support/client-runtime/src/sessions.ts
2026-09-21 11:34:20 +08:00
lsdsjy b6594005f7 fix(ui-settings-account): report clipboard failures without failing sign in 2026-09-21 11:32:33 +08:00
sjw1231 c84c908656 fix(ui-deliverables): prevent split diff edge rebound 2026-09-21 11:29:02 +08:00
imccyu 3834da8602 Merge pull request #4754 from deepseek-harness/desktop-browser
feat(desktop): retain sidebar webviews and restore saved pages
2026-09-21 11:24:48 +08:00
Yichen Jiang 103aee7396 Merge branch 'worktree/input-plugin-extension-hooks' into worktree/experimental-voice-input
# Conflicts:
#	apps/desktop/README.i18n.yaml
#	apps/desktop/README.md
#	apps/desktop/README.zh.md
#	packages/client/ui-plugin-manager/src/client/presentation.ts
#	packages/client/ui-plugin-manager/tests/components.client.spec.tsx
#	packages/extensions/cordis-client-runner/src/client/slot-catalog.ts
2026-09-21 11:15:10 +08:00
creatixchu 62891ca4fc Merge origin/master into worktree/issue-3881-folder-drop 2026-09-21 11:11:36 +08:00
Yichen Jiang 0aa6f94749 feat(web): expose input plugin extension hooks 2026-09-21 11:11:21 +08:00
imccyu 640e26806b docs(sidebar): describe the final retained browser architecture 2026-09-21 11:10:31 +08:00
sjw1231 2c5d49645b docs(ui-deliverables): plan review tab fixes for #4670 2026-09-21 11:09:13 +08:00
lsdsjy 31cce49fff fix(desktop): unify welcome material and action placement 2026-09-21 11:06:59 +08:00
Yichen Jiang 511476782b test(web): complete input action fixture after master merge 2026-09-21 11:02:05 +08:00
Yichen Jiang b3ff0f5dc7 Merge remote-tracking branch 'origin/master' into worktree/experimental-voice-input
# Conflicts:
#	packages/client/ui-plugin-manager/tests/components.client.spec.tsx
#	packages/client/ui-primitives/README.i18n.yaml
#	packages/client/ui-primitives/README.md
#	packages/client/ui-primitives/README.zh.md
#	packages/client/ui-primitives/tests/icons.client.spec.tsx
#	packages/client/ui-primitives/tests/state-dot.client.spec.tsx
2026-09-21 10:56:23 +08:00
Yifffan 2ced4d130b Merge branch 'master' into fix/desktop-minimize-vibrancy 2026-09-21 10:54:01 +08:00
creatixchu 82692090c8 Merge remote-tracking branch 'origin/master' into feat/4562-file-open-apps 2026-09-21 09:56:00 +08:00
imccyu 8e441f7da2 test(browser): complete sidebar retention and navigation coverage 2026-09-21 04:12:54 +08:00
imccyu 599f556deb test(sidebar): follow retained DOM layout in browser checks 2026-09-21 04:12:54 +08:00
imccyu 7be9188d20 fix(browser): complete preload fixtures and scrollbar bindings 2026-09-21 04:12:54 +08:00
imccyu 346b27b037 fix(webworker-packer): skip declaration-only export roots 2026-09-21 03:36:52 +08:00
imccyu d8438ecf06 refactor(browser): isolate Electron code and defer native coverage 2026-09-21 03:25:30 +08:00
imccyu 94bc5b25b8 test(desktop): cover webview and sidebar retention lifetimes 2026-09-21 03:13:15 +08:00
imccyu 0e652514ea fix(browser): declare workspace client injection 2026-09-21 02:33:35 +08:00
imccyu 60ef7b1261 fix(ci): refresh sidebar catalog and complete API docs 2026-09-21 02:29:00 +08:00
imccyu e9a0d5ac77 fix(desktop): address sidebar retention and webview review findings 2026-09-21 02:17:37 +08:00
imccyu 5614184902 chore(browser): prune retired unknown-cast baseline 2026-09-21 01:50:16 +08:00
imccyu 1489526281 fix(desktop): import updater declarations through package types 2026-09-21 01:47:28 +08:00
imccyu 37df1ea375 fix(browser): separate desktop types from client compilation 2026-09-21 01:47:20 +08:00
imccyu c402892c1a feat(browser): add manual page recovery and cwd storage grouping 2026-09-21 00:58:23 +08:00
imccyu ab4bd4c630 feat(desktop): enable lease-scoped sidebar webview guests 2026-09-21 00:58:23 +08:00
imccyu cbd866fa72 refactor(browser): separate navigation and presentation providers 2026-09-21 00:58:23 +08:00
imccyu 96b2aca346 feat(sidebar): retain session views across tab presentation changes 2026-09-21 00:58:23 +08:00
imccyu bf712e1404 feat(dockkit): add stable retained tab layout 2026-09-21 00:58:22 +08:00
imccyu 37535cf44a Merge pull request #4753 from deepseek-harness/fix/pr-4749-review
fix(chat): address presentation review findings
2026-09-21 00:46:02 +08:00
imccyu 4533be9531 fix(chat): address presentation review findings 2026-09-21 00:32:43 +08:00
imccyu b173fe1c19 Merge pull request #4749 from deepseek-harness/feat/chat-presentation-channel
feat(ui-chat): restore independent work-detail presentation from #4565
2026-09-21 00:10:18 +08:00
Turtle 3f7016a422 feat(config): add volatile schemas and watcher-driven updates (#4579)
* feat(cordis): add volatile config references without remounting

* refactor(cordis): use internal prefix for volatile updates

* fix(schemastery): preserve metadata-free serialized schemas

* refactor(config): move volatile updates into an opt-in Loader plugin

* refactor(config): reuse Loader context patch for volatile updates

* refactor(config): let the volatile watcher commit equal candidates

* refactor(config): separate comparison from volatile reconciliation

* refactor(loader): extract pure entry option diff

* refactor(loader): compare volatile config through schema metadata

* refactor(loader): make volatile comparison policy explicit

* refactor: name config reload and volatile HMR events

* refactor(loader): compare volatile config in one schema walk; declare hmr/volatile-update in volatile-hmr

Replace the mask/project pair in vendor/loader/src/config/diff.ts with one recursive schema-guided comparison. Absent objects now compare as their declared schema default at every object node, not only under volatile ancestors; both inputs resolve to the same validated config, so the one changed assertion (undefined vs {} under a non-volatile object schema) becomes unchanged. The isJsExpr special case is dropped: expression keys fall through to raw comparison.

Move the hmr/volatile-update Events declaration from vendored Cordis core to its producer, packages/boot/volatile-hmr, matching how dsh-hmr declares hmr/change and hmr/reload. Vendored cordis events.ts returns to upstream content; the inherited Cordis API catalog and the event matrix are regenerated, and the tutorial example imports the package types.

* fix(cosmokit): compare sparse array slots in deepEqual; extend volatile diff coverage

Array.prototype.every skipped holes, so deepEqual(Array(1), ['new']) was true in one direction. Compare every index with holes read as undefined.

config/diff.ts treats expression objects as opaque values even when a schema field shares the marker name, and looks up field schemas with Object.hasOwn so unknown keys named after Object prototype members compare raw. Tests cover absent-object defaults, sparse arrays in both directions, prototype-named keys, metadata-free serialized schema nodes, and a runtime remount check in volatile-hmr. READMEs and the architecture note describe the object-default rule.

* test(schemastery): build serialized schema fixtures without unknown casts

verify-no-unknown-casts rejects the two as-unknown assertions in scripts/volatile-config.spec.ts. Parse the serialized nodes from JSON text instead, matching scripts/loader-config-diff.spec.ts.

* docs(volatile-hmr): drop the scope-scan tag from hmr/volatile-update

verify-scoped-events rejects @dshScopeScan on an event that carries no scoped keys. Catalogs and the event matrix are regenerated.

* docs: regenerate module graph for the volatile-hmr dependency edge

verify-module-graph reports the merged docs stale: volatile-hmr now lists its app-boot dependency.

* test(volatile-hmr): watch the real YAML through an exact config watch

The Windows coverage lane lost the whole spec: its vitest worker exited without a report once this test started a chokidar directory watch (root: ['.']), the only real directory watch in the test suite. Register the include file through hmr.watchConfig instead, the pattern the HMR package's own real-file tests run on Windows, and emit loader/config-reload from the refresh callback as the root-watch path does. packages/boot/hmr/tests/coordination.spec.ts already covers that root-watch emission with a mocked watcher; volatile-hmr and hmr sources stay at 100% coverage.

* test(volatile-hmr): pass the Include path as a file URL

On Windows the plain absolute path made the include subtree fail to load, so the profile-patch test found no consumer entry. Other Include fixtures in this spec already pass file URLs.

* refactor(loader): commit volatile config in Entry.update and remove volatile-hmr

loader/config-reload was emitted from two places (app-boot profile reconciliation and the HMR root watch) and by hand in tests, because a separate plugin needed a "configuration applied" signal to run its own pass. Every update path already funnels through Entry.update, which is where the Loader decides to skip a remount for a volatile-only change. Entry._commitVolatile now parses the candidate through the fiber's internal/config hook and schema, commits the new values into the running references when the ordinary effective values still match, and emits loader/volatile-update to the owning fiber. A changed ordinary effective value falls back to the ordinary remount instead of leaving the instance stale; an invalid candidate is logged and keeps the running references, with the raw config retained for the next activation. Forced group updates take the same path.

Remove packages/boot/volatile-hmr, its event, the app-boot loader/config-reload declaration and emit, and the HMR emission. Loader runtime and real-file tests move to scripts/loader-volatile-update.spec.ts. Docs, catalogs, the event matrix, module graph and the architecture note follow.

* test: share the volatile schema fixture between the schema and Loader specs

The duplication gate flagged the identical Config fixture in scripts/volatile-config.spec.ts and scripts/loader-volatile-update.spec.ts.

* refactor(loader): reduce config/diff.ts to equalExceptVolatile

Entry.update now computes the strict raw option changes itself and only asks diff.ts whether two raw configs differ at most in volatile fields. The ignoreVolatile flag, the options object and the "config is the sole change" rule leave diff.ts; they were the Loader's decision and now sit next to the commit they gate. The raw-config deepEqual in the volatile branch is gone because the strict change list already answers it.

* refactor(loader): type the volatile comparison against Schemastery's Schema

config/diff.ts narrows the plugin's Standard Schema to Schemastery's own Schema type instead of a hand-written structural interface, which makes vendor/loader a type-only dependent of @deepseek-ai/schemastery. The rescope codemod treats the vendor identifier comparison like schemastery's own source and renames the import through an exact edit.

* fix(loader): address review findings on the volatile commit path

Entry.update only replaces the fiber's pending raw config for a volatile-only change, so a noSave direct update survives a no-op entry update. _commitVolatile logs at debug level when ordinary effective values changed and the ordinary update applies, dispatches loader/volatile-update with a Context receiver like Cordis' own filtered emits, and isolates a throwing listener. Schemastery reports unsupported volatile values as ValidationError with their path and validates a lazy inner once when it is built. The inherited-event catalog gains internal/config and corrected source lines; the Loader spec imports dsh-hmr by package name and covers the new behaviors. Docs record that class-instance ordinary fields always take the remount path and that .default() accepts the raw input type.
2026-09-21 00:04:06 +08:00
gengruilin cbd6562506 docs(web): clarify browser dependency installation 2026-09-20 23:45:19 +08:00
gengruilin b79b64f788 Merge origin/master into fix/webkit-model-picker 2026-09-20 23:42:15 +08:00
imccyu c3c3d95608 test(web): align preview and fence checks with chat folding 2026-09-20 23:36:20 +08:00
Yichen Jiang b73a71e89c Merge pull request #4751 from deepseek-harness/worktree/preset-error-session-creation-1b1161
fix(web): report a refused new session through the Workspace notice
2026-09-20 23:30:38 +08:00
Yichen Jiang c7e6e36dd1 fix(web): keep a superseded creation notice silent and add its e2e
Review follow-ups on the refused-creation notice:

- `openWorkspace` raises `createFailed` only while its navigation is
  current, so a refusal that lands after a later navigation or owner
  disposal stays silent — the gate `initializeDefaultWorkspace` already
  uses and what the README states; two service cases pin it.
- `RowActionToast` switches on the closed notice union and ends in
  `assertNever`, so an unhandled kind fails to compile instead of
  rendering another notice's copy.
- `apps/web/tests/new-session-refused.e2e.ts` drives the composed Web
  server with a user preset whose row cannot resolve and compares the
  notice's ARIA golden, beside the default-Workspace failure scenario.
- `@throws` on `openWorkspace` covers every failure (catalog
  regenerated); `RowToast.createFailed` records why the Host code stays
  in the copy; the README states that other failures show their own
  message.
2026-09-20 23:22:13 +08:00
imccyu f1c035b1e9 test(chat): align presentation coverage and snapshots 2026-09-20 23:20:07 +08:00
imccyu 89ee08b781 docs: record completed chat migration and deferred turn-step work 2026-09-20 22:45:29 +08:00
imccyu bcd14d776c perf(ui-chat): memoize tool and command rows 2026-09-20 22:45:29 +08:00
imccyu b6f99fad55 feat(ui-chat): filter system prompts and permission commands 2026-09-20 22:45:29 +08:00
imccyu 007241d2a8 feat(ui-chat): add text shimmer to running rows 2026-09-20 22:45:29 +08:00
imccyu e24805a536 feat(ui-chat): refine reasoning previews and disclosure cues 2026-09-20 22:45:28 +08:00
imccyu 508329fe57 feat(ui-chat): add presentation policy channel 2026-09-20 22:45:28 +08:00
dcc c2be3d9a0b test(web): pin the desktop update chrome's shared palette
Numeric download progress renders without an ellipsis, and a failure keeps the update chip's palette instead of a separate error color. Pin the progress label and the shared badge background, and let the retry label carry the failure distinction.
2026-09-20 22:41:53 +08:00
Yichen Jiang 93ad804bf1 fix(web): report a refused new session through the Workspace notice
An explicit New Session (sidebar and header buttons, the workspace row
button, and the hero workspace pick) whose `session.create` the Host
refuses — for example a default preset whose composition names a
plugin that no longer resolves (`agent-preset/invalid`) — was only
logged with `console.warn`, so the click looked like it did nothing.

`openWorkspace` now shows the failure through the shared Workspace
notice as a `createFailed` toast quoting the Host's stable code and
message on the long hold, then rethrows as before. Startup restoration
does not go through `openWorkspace` and stays console-only.
2026-09-20 22:40:00 +08:00
dcc 5015426866 fix(ui): unify desktop update badge colors 2026-09-20 22:33:00 +08:00
dcc 422ded6f7a fix(ui): align desktop update tag styling 2026-09-20 22:33:00 +08:00
winewill 6446944fa6 Merge pull request #4669 from deepseek-harness/build/test-release-path-id
build(desktop): isolate test downloads by release ID
2026-09-20 22:06:58 +08:00
winewill 338eea071e Merge remote-tracking branch 'origin/master' into build/test-release-path-id 2026-09-20 21:59:07 +08:00
07akioni 287f879d67 Merge pull request #4738 from deepseek-harness/fix/4728-macos-localizations
fix(desktop): 修复 macOS 中文系统首次启动默认英文
2026-09-20 21:50:03 +08:00
winewill b793dd2df6 Merge remote-tracking branch 'origin/master' into build/test-release-path-id
# Conflicts:
#	apps/desktop/scripts/desktop-package-environment.mjs
#	apps/desktop/tests/package-target-stages.spec.ts
2026-09-20 21:46:58 +08:00
creatixchu db10002cdd test(web): include file items in image drop fixture 2026-09-20 21:34:31 +08:00
creatixchu 5953ab2913 test(composer): use explicit void paste handler 2026-09-20 21:24:49 +08:00
creatixchu 444ce0444b fix(composer): type paste regression and refresh slot catalog 2026-09-20 21:14:44 +08:00
creatixchu 52a4a5bdb3 fix(composer): preserve batches and quoted directory references 2026-09-20 21:12:06 +08:00
07akioni 27fa7ed9ff Merge pull request #4681 from deepseek-harness/xtr/default-workspace-first-send
feat(workspace): prepare a default workspace on startup
2026-09-20 21:04:06 +08:00
Christina Lee ea05e57b60 Merge branch 'master' into fix/4728-macos-localizations 2026-09-20 20:56:39 +08:00
gengruilin 06ef26a80f fix(web): preserve focus when toggling the model picker 2026-09-20 20:56:34 +08:00
liyao 942e496ce7 fix(desktop): declare macOS bundle localizations 2026-09-20 20:45:29 +08:00
Turtle 31e6b3f150 fix(client): rotate every ongoing StateDot loader in phase (#4737)
CSS animations start when their element is inserted, so loaders mounted
at different moments rotated out of step in the sidebar session list.
The ongoing loader now pins its spin and dash animations to document
time zero on mount so every visible loader shares one phase.
2026-09-20 20:45:23 +08:00
_Kerman a4fdaebf52 Merge remote-tracking branch 'origin/master' into xtr/default-workspace-first-send 2026-09-20 20:44:25 +08:00
_Kerman 5ec3353af2 refactor(workspace): reuse toast for default initialization failures 2026-09-20 20:42:50 +08:00
Yif d72f85a0ad fix(desktop): paint an opaque base while minimized to cover the vibrancy reattach gap 2026-09-20 20:41:48 +08:00
creatixchu 1790a09f77 Merge remote-tracking branch 'origin/master' into feat/4562-file-open-apps 2026-09-20 20:33:24 +08:00
Turtle f2a22a3108 test(web): provide favicon environment in desktop boot fixture 2026-09-20 20:05:17 +08:00
Turtle 6b9c01996f fix(web): refresh favicon colors with the system scheme 2026-09-20 19:54:54 +08:00
imccyu 2411452433 Merge pull request #4707 from deepseek-harness/subagent-catalog
perf(session): avoid duplicate cold reads on conversation open
2026-09-20 19:27:13 +08:00
winewill 2c233c64f0 Merge pull request #4716 from deepseek-harness/feat/current-version-display
feat(client): 在通用设置展示当前版本号
2026-09-20 19:19:04 +08:00
imccyu 42f9a76b77 test(client): align remaining navigation refresh expectations 2026-09-20 18:58:22 +08:00
Yudong Han a25780c63b Merge pull request #4725 from deepseek-harness/fix/sidebar-excel-default-app 2026-09-20 18:45:17 +08:00
imccyu 045e2f9360 test(client): omit absent optional Session fixture fields 2026-09-20 18:41:30 +08:00
imccyu ad2dad1186 test(client): cover auxiliary reads with retained Session fixtures 2026-09-20 18:41:30 +08:00
imccyu d25490d8b4 test(subagent): replace legacy unknown cast with typed fixture 2026-09-20 18:41:30 +08:00
imccyu 280048d868 fix(client): align navigation callbacks and projection tests 2026-09-20 18:41:30 +08:00
imccyu d088572e11 fix(session): declare persistence identity for Typert analysis 2026-09-20 18:41:30 +08:00
imccyu be0d63661c perf(client): remove redundant projection reads from navigation 2026-09-20 18:41:29 +08:00
imccyu dfff5056f4 perf(client): defer auxiliary reads until history is ready 2026-09-20 18:41:29 +08:00
imccyu 6fef0f0af9 fix(session-query): stabilize persistence cache identity 2026-09-20 18:41:29 +08:00
Turtle 2099andTurtle e8f3d02da8 fix(web): batch session expansion and keep running sessions visible (#4715)
* fix(web): expand workspace sessions in bounded batches

* fix(web): keep running sessions outside overflow quota

---------

Co-authored-by: Turtle <[email protected]>
2026-09-20 10:38:56 +00:00
imccyu 068cf03441 Merge pull request #4648 from deepseek-harness/worktree/remote-duplex-stream
feat(gateway): let every Remote stream carry a client uplink
2026-09-20 18:32:58 +08:00
Turtle 654caa4bbd feat(bundle): accept an ordered patch file list in dsh.bundle.patch (#4722)
* feat(bundle): accept an ordered patch file list in dsh.bundle.patch

`dsh.bundle.patch` now accepts `string | string[]`. A list names patch
files applied in order as one bundle layer; each file's relative plugin
paths still resolve beside that file. `bundlePatchPaths` in dsh-app-boot
is the one resolver from a declaration to absolute paths, and every
reader uses it: profile loading, plugin-manager reconcile and bundle
rows, the client-runtime roster, and the workspace, cordis-config, and
product-isolation gates. `ProfileLayer.patchPath` becomes `patchPaths`.

* fix(bundle): keep declared patch files package-relative in the workspace gate

path.join produced backslash entries on Windows, so the expected files
list did not match package.json files. bundlePatchFiles returns the
validated declaration as written; bundlePatchPaths joins it.
2026-09-20 18:20:24 +08:00
Ziya d495b2e5f2 Merge pull request #4718 from deepseek-harness/copy/preset-language-20260920
feat(web): clarify agent presets and add mode guides
2026-09-20 18:19:26 +08:00
winewill 21e315a7dd docs(client): record the shell's Current version row in ui-settings-general
The shell registers two General rows after #4716, but README.md and
README.zh.md still described only Developer tools in three places each.
Align both sides and re-record the bilingual pairing hashes.

Also close the review nits: assert the settings.general.item rows dispose
on unload, give the localized-label cases distinct titles (%s printed the
dictionary object, so both read "[object Object]"), complete the
CurrentVersionRow @param, and note why current-version orders last.
2026-09-20 18:18:13 +08:00
ZiyaZhang d620c1e4b7 fix(web): soften segmented tab focus styling 2026-09-20 03:08:34 -07:00
ZiyaZhang 5aa0e3f806 test(web): align developer tools check with preset copy 2026-09-20 03:00:57 -07:00
creatixchu 112c9e5749 feat(web): cite dropped folders and files as @ references on Desktop
The Desktop preload reports a picked file's real path through
__DSH_HOST_PATHS__. Where that bridge exists, the composer inserts a dropped,
picked, or pasted folder or non-image file as the same @path reference chip
the user could have typed, so nothing uploads and the model reads the target
with its file tools; images keep uploading. The drop handler names dropped
directories through the entry API, and a browser without the bridge refuses
a dropped folder with product copy instead of failing it as an upload.
2026-09-20 18:00:26 +08:00
imccyu ffea3c8818 fix(gateway): close the remaining uplink lifecycle gaps 2026-09-20 17:53:18 +08:00
imccyu 5a8d3b7498 docs(agents): state the uplink note in English on both sides and cross-link its neighbours 2026-09-20 17:53:18 +08:00
imccyu 6872651527 docs: regenerate the module graph 2026-09-20 17:53:18 +08:00
imccyu 520c225b87 test(gateway): cover the uplink edge paths 2026-09-20 17:53:17 +08:00
imccyu 755f022ef0 docs: regenerate catalogs and document the mock uplink handle 2026-09-20 17:53:17 +08:00
imccyu 5fbae69d18 docs(agents): pair the Remote uplink stream note in English 2026-09-20 17:53:17 +08:00
imccyu d07182d3d8 docs(agents): record the Remote uplink stream as implemented 2026-09-20 17:53:17 +08:00
imccyu 355df025d4 fix(gateway): harden the Remote uplink lifecycle 2026-09-20 17:53:17 +08:00
imccyu 8ce7c27671 refactor(connection): admit every request as the single operator Peer 2026-09-20 17:53:17 +08:00
imccyu e427600e32 test: pin the scope export roster and map dsh-brand for the proxy type program 2026-09-20 17:53:17 +08:00
imccyu 35602063d7 refactor(typert): name the client stream handle RemoteStreamHandle everywhere 2026-09-20 17:53:17 +08:00
imccyu 77f1a493aa refactor(typert): keep the Remote marker mode as the 'stream' literal 2026-09-20 17:53:17 +08:00
imccyu ebdc1609c6 docs(agents): mark the duplex note's pseudo-code fences as text 2026-09-20 17:53:17 +08:00
imccyu 089004c418 test(gateway): cover the Remote uplink and document it
Tests bring the changed sources to full per-file coverage, the READMEs and
docs describe the uplink, the invocation context, and the Peer registry in
both languages, and the generated catalogs are refreshed.
2026-09-20 17:53:17 +08:00
imccyu 021bd03b70 feat(gateway): let every Remote stream carry a client uplink
Every Typert Remote stream accepts client-to-Host items on the same
logical stream. A method declares the uplink item type in its return
type, RemoteStream<Out, In>, and reads the items through
this.ctx.invocation.uplink(); the invocation also carries the calling
Peer. Client generated methods return a handle that iterates the
downlink and exposes send/end/dispose for the uplink.
2026-09-20 17:53:16 +08:00
fz a1e0ce557d Merge pull request #4460 from deepseek-harness/feat/workspace-dependencies-package
feat(sdk): bundle shared Office Python runtime and configurable skills
2026-09-20 17:50:17 +08:00
winewill 5ff352b0be test(web): include current version without built-in browser 2026-09-20 17:50:05 +08:00
Yichen Jiang a165e82bfc feat(ui-plan): lead the plan chip with the plan glyph and a hover close (#4710)
The composer's plan-mode chip rendered its label followed by a 12px
one-pixel cross, out of step with the sibling access-mode trigger's
glyph-then-label layout. The chip now leads with the 14px plan glyph
(the /plan command's own icon) and swaps it for a new circled close —
IconCloseCircleFill, a filled disc with the cross knocked out — while
the enabled chip is hovered or keyboard-focused, so the pending click
reads as turning plan mode off.

The pill's corners move from a 999px circular capsule to the compact
Button geometry (14px on the 28px row) under the theme's superellipse
curvature, matching every neighbouring control; the lifecycle-chrome
probe now pins that radius and the shared corner-shape.
2026-09-20 17:49:06 +08:00
_Kerman 5294b15b31 docs(workspace): separate constructor parameter tags 2026-09-20 17:48:58 +08:00
_Kerman 78d6f7bc1f test(client): mock default workspace initialization during boot 2026-09-20 17:48:46 +08:00
winewill 9d7e5e30ac Merge remote-tracking branch 'origin/master' into feat/current-version-display 2026-09-20 17:46:50 +08:00
ZiyaZhang 05a229ccc9 feat(web): clarify agent presets and add mode guides 2026-09-20 02:45:48 -07:00
Yudong c1f37fef9d fix(sidebar): open spreadsheets in default app 2026-09-20 17:39:28 +08:00
gengruilin 6613660223 fix(web): keep model picker focus during mouse selection 2026-09-20 17:37:44 +08:00
Yichen Jiang ab9689b2ca fix(web): bind the right Sidebar before same-commit mount effects (#4693)
* fix(web): bind the right Sidebar before same-commit mount effects

A plan review arriving while a global panel was active crashed its opener when the user returned: the frame mounts the Conversation column ahead of the right Sidebar seat, so PlanReviewOpen's mount effect called ctx.sidebarRight.openResource before the seat's passive bindService effect had published the binding. The slot runtime retired the crashed entry for the rest of the page, removing the View full plan link and every later automatic open.

Publish the seat binding from a layout effect, which runs before any passive effect of the same commit regardless of tree order. The seat spec mounts a Conversation-column opener ahead of the seat and returns from a global panel; the plan-review Web scenario delivers a review while the Plugins panel is open and expects the preview and the link after the return. Both fail before the fix.

* fix(web): open the plan review once the Sidebar seat is mounted

Replace the layout-effect binding with an observable: ctx.sidebarRight.mounted publishes the session whose seat is on screen, undefined while a global panel replaces the Conversation. The seat keeps binding from its passive effect. PlanReviewOpen reads the source through a bound hook and opens once a seat is defined, so a review mounting ahead of the seat in the same commit no longer assumes a bound seat and never crashes its entry.

The seat spec's opener stand-in now reads `mounted` the way a slot component does; the service spec pins that only real transitions publish, and the PlanReviewOpen spec covers a review that mounts before any seat and opens once the seat binds.

* test(web): align the plan-review console tripwires and the seat term

The off-screen review case now asserts the reconnect warnings like its siblings and filters crash logs by error level as the other e2e files do. The zh README keeps 席位 for the seat throughout.

* test(plan): keep the review props cast at its baseline fingerprint

verify-no-unknown-casts counts a modified assertion as new. The two PlanReviewOpen specs now share the unchanged baseline cast through one helper and supply the seat hook as a typed value beside it.
2026-09-20 09:35:00 +00:00
fz fa1065b6b2 Merge remote-tracking branch 'origin/master' into feat/workspace-dependencies-package 2026-09-20 17:33:02 +08:00
_Kerman b21fc2c8d1 Merge origin/master into xtr/default-workspace-first-send 2026-09-20 17:26:38 +08:00
winewill 1628c1d030 feat(client): display current version in general settings 2026-09-20 17:23:08 +08:00
_Kerman 2cd03c9758 refactor(workspace): omit unchanged registry state fields 2026-09-20 17:20:54 +08:00
_Kerman 3a7f8f9657 test(web): restore ordinary workspace picker readiness 2026-09-20 17:20:44 +08:00
_Kerman 4d2e420ef8 refactor(workspace): initialize the default workspace on startup 2026-09-20 17:19:54 +08:00
creatixchu 7d002f7eff test: compare Windows file identity and refresh delivery controls 2026-09-20 17:17:40 +08:00
creatixchu e0187ecdf8 Merge remote-tracking branch 'origin/master' into feat/4562-file-open-apps 2026-09-20 17:16:21 +08:00
Yichen Jiang 8d6245bcc8 Merge pull request #4701 from deepseek-harness/fix/settings-navigation-scroll
fix(web): 设置导航支持溢出滚动
2026-09-20 17:14:32 +08:00
winewill 6848f1e118 Merge pull request #4567 from deepseek-harness/fix/windows-mandatory-update-overlay
fix(desktop): Windows 强更使用主窗口内蒙层并修复退出清理
2026-09-20 17:12:17 +08:00
Tianyi Cui 3f33c06f40 Merge pull request #4629 from deepseek-harness/worktree/dynamic-conversation/messages-only
Make the official DeepSeek adapter Messages-only
2026-09-20 17:10:58 +08:00
winewill 032394fc48 Merge remote-tracking branch 'origin/master' into fix/windows-mandatory-update-overlay 2026-09-20 17:01:23 +08:00
Turtle 8f0dd55c2c feat(plugins): render icons from package manifests (#4697)
* feat(plugins): render icons from package manifests

* fix(plugins): include declared icons in canonical package payloads
2026-09-20 17:00:17 +08:00
Turtle 2099andTurtle 3eab1714bc feat(web): add chat link opening preference (#4671)
* feat(web): add chat link opening preference

* docs(web): clarify preference-controlled link routing

* fix(web): hide link preference without a browser

* fix(web): make the built-in browser opt-in

* test(web): honor profile-aware browser availability

---------

Co-authored-by: Turtle <[email protected]>
2026-09-20 17:00:03 +08:00
creatixchu 086b64103f test: verify transient feedback and single-action file controls 2026-09-20 16:51:58 +08:00
creatixchu c7a4fae8e5 fix: complete shared menu types and remove obsolete button styles 2026-09-20 16:48:25 +08:00
creatixchu a4148d687f fix: refine file action menus feedback and shared queries 2026-09-20 16:46:58 +08:00
Dudu-0223 05b056f18c Merge pull request #4635 from deepseek-harness/fix/lazy-subagent-migration
fix(session): preserve unreadable children in migrated subagent catalogs
2026-09-20 16:45:25 +08:00
Yichen Jiang 393f72e32e Merge pull request #4690 from deepseek-harness/worktree/web-desktop-startup-commands-a97723
feat(infra): unify Web and Desktop launch commands
2026-09-20 16:35:26 +08:00
gengruilin 47ee608079 fix(web): allow overflowing settings navigation to scroll 2026-09-20 16:34:49 +08:00
07akioni 76427b4c5f Merge remote-tracking branch 'origin/master' into codex/pr-4553 2026-09-20 16:30:59 +08:00
fz 6b1c9c169d Merge remote-tracking branch 'origin/master' into feat/workspace-dependencies-package 2026-09-20 16:30:31 +08:00
lsdsjy 82ae763772 fix(deepseek-account): rename login source parameter 2026-09-20 16:28:21 +08:00
Xu Hanxiang dff5506934 Merge pull request #4499 from deepseek-harness/fix/windows-plugin-list-type-size
fix(client): raise the package identity contrast in the plugin views
2026-09-20 16:27:26 +08:00
Yichen Jiang 7d56ca5cee Merge remote-tracking branch 'origin/master' into worktree/web-desktop-startup-commands-a97723 2026-09-20 16:27:07 +08:00
Yichen Jiang 44cf9b703d docs(readme): point developers at dev:web and the Make targets 2026-09-20 16:25:03 +08:00
mektpoy 230d975a04 docs(client): explain the identity tiers and the detailName scope
The shared rule now states why the collapsed identity and the expanded value keep two label tiers, and the detailName comment names the row identity it also renders.
2026-09-20 16:18:56 +08:00
mektpoy 40edba6a8d Merge remote-tracking branch 'origin/master' into fix/desktop-dev-runtime-target 2026-09-20 16:13:36 +08:00
Xu Hanxiang 5dc6767d7b Merge pull request #4491 from deepseek-harness/fix/windows-menu-tooltip-overlap
fix(desktop): place Windows caption tooltips below the titlebar
2026-09-20 16:02:54 +08:00
_Kerman 43372b28ce Merge origin/master into xtr/default-workspace-first-send 2026-09-20 16:00:37 +08:00
fz 686986fdea Merge remote-tracking branch 'origin/master' into feat/workspace-dependencies-package 2026-09-20 16:00:35 +08:00
winewill 1e20aecef5 Merge remote-tracking branch 'origin/master' into fix/windows-mandatory-update-overlay 2026-09-20 15:59:42 +08:00
_Kerman d4d03b7c41 docs(workspace): clarify default directory lookup and cancellation 2026-09-20 15:58:53 +08:00
_Kerman 8e313eaa30 test(workspace): cover first-use configuration and preset failures 2026-09-20 15:58:33 +08:00
_Kerman cfec29eb69 fix(web): show first-use preparation and update browser readiness 2026-09-20 15:58:10 +08:00
_Kerman 6fa56a0cf7 fix(conversation): preserve first drafts across Session retirement 2026-09-20 15:57:45 +08:00
mektpoy 91fcd9180a docs(client): name the caption bubble owner and record the badge gap
The occupying badge chooses its own side, and the update badge keeps a right-opening bubble that the Windows caption menu can cover; ui-settings-general records that as a known limitation.
2026-09-20 15:55:29 +08:00
winewill dc81ed348c Merge pull request #4684 from deepseek-harness/build/windows-cache-concurrency
build(desktop): parallelize Windows signature cache hits
2026-09-20 15:55:23 +08:00
Yichen Jiang 453e8c09ac Merge remote-tracking branch 'origin/master' into worktree/web-desktop-startup-commands-a97723 2026-09-20 15:51:01 +08:00
Yichen Jiang 78dcbc8587 feat(infra): unify Web and Desktop launch commands
Web and Desktop now share one command pair: start:* launches the artifacts
of a prior complete build and dev:* builds first, then launches. start:web
is new and spells out the same source launch vector as `pnpm dsh web`;
dev:web runs `pnpm run build`, the three rebuild watchers, and `dsh web` in
one terminal, with --skip-build and --no-serve for the previous two-terminal
flow. The launcher gate pins both scripts to the dsh CLI, and a root
Makefile names the same commands as make web|dev-web|desktop|dev-desktop|build.

The dev-web stage supervisor waits for stages the terminal already
interrupted, escalates only survivors, forwards SIGTERM once, and reports a
stage that exits on its own. Signal listeners are persistent because the
tsdown watchers bundle signal-exit, which re-raises a signal when it finds
no other listener.
2026-09-20 15:50:41 +08:00
winewill f3e8021eaa Merge remote-tracking branch 'origin/master' into fix/windows-mandatory-update-overlay 2026-09-20 15:48:35 +08:00
fz 9e49d7dbcf Merge remote-tracking branch 'origin/master' into feat/workspace-dependencies-package 2026-09-20 15:46:05 +08:00
Dudu-0223 e848b6226f Merge remote-tracking branch 'origin/master' into codex/fix-migration 2026-09-20 15:45:12 +08:00
Dudu-0223 e44efedeb0 fix(session): isolate corrupt compressed headers during discovery 2026-09-20 15:44:52 +08:00
mektpoy 8b8f10a8c6 docs(ui-sidebar): scope the Windows caption bubble claim
The previous sentence claimed every caption icon bubble opens below the caption, but only the sidebar toggle and New Session derive side=bottom; an occupying sidebar.toggle.badge keeps the default side.
2026-09-20 15:39:48 +08:00
winewill 466aa2232b Merge remote-tracking branch 'origin/master' into build/windows-cache-concurrency 2026-09-20 15:37:42 +08:00
mektpoy 3515c48a37 fix(desktop): resolve the development runtime directory from the build target
The unpackaged shell derived the primary-runtime directory from process.arch, while preparePrimaryRuntime() resolves the target through resolveDesktopBuildTarget(), which fixes Windows to x64. On a Windows ARM64 host the shell looked under targets/win-arm64 while the payload was under targets/win-x64, and the missing office-skills/scripts/check_office.py became a fatal startup dialog.

The launcher now resolves that directory through the same resolver and passes it to the shell as DSH_DESKTOP_PRIMARY_RUNTIME_DIR.
2026-09-20 15:37:38 +08:00
winewill d4c5a1723f test(desktop): isolate update UI in directory-picker preload integration 2026-09-20 15:36:49 +08:00
winewill ecb00a6cd0 Merge remote-tracking branch 'origin/master' into build/windows-cache-concurrency 2026-09-20 15:36:17 +08:00
lsdsjy 1b83e0ff13 Merge pull request #4665 from deepseek-harness/fix/developer-tools-preferences
fix(web): preserve plugin tabs and enable developer tools by default
2026-09-20 15:34:51 +08:00
winewill c193e0dfee Merge pull request #4683 from deepseek-harness/fix/macos-menu-product-name
fix(desktop): localize macOS application menu names
2026-09-20 15:34:32 +08:00
winewill db7ee5f487 build(desktop): parallelize Windows signature cache hits 2026-09-20 15:32:37 +08:00
winewill bea5bde6cc docs(desktop): clarify localized native menu behavior 2026-09-20 15:27:06 +08:00
winewill 015e60ff40 test(desktop): satisfy strict lint in mandatory update regressions 2026-09-20 15:23:36 +08:00
lsdsjy 04debcbd65 feat(ui-settings-account): link to Platform account information 2026-09-20 15:23:26 +08:00
winewill 69ac149090 fix(desktop): isolate mandatory update actions and handle main-window lifecycle 2026-09-20 15:20:04 +08:00
fz 35fe8e0966 Merge pull request #4677 from deepseek-harness/fix/4664-atomic-native-addon-extraction
fix(pkg): make SEA native addon extraction atomic
2026-09-20 15:18:26 +08:00
Kexin Liu 883311e556 Merge pull request #4349 from deepseek-harness/refactor/client-session-presentation
fix(web): prevent session blank rollback after acceptance
2026-09-20 15:16:49 +08:00
Dudu-0223 21ec132c0e test(preview): expect versioned unknown child catalog entries 2026-09-20 15:15:43 +08:00
Huanqi Cao d0f9a5c410 Merge remote-tracking branch 'origin/master' into fix/windows-acl-delete-escape
# Conflicts:
#	docs/config-catalog.i18n.yaml
2026-09-20 15:12:34 +08:00
Dudu-0223 a978ad1994 fix(subagent): version unknown catalog payloads locally 2026-09-20 15:09:37 +08:00
winewill fd75c47ae9 fix(desktop): localize macOS application menu names 2026-09-20 15:08:57 +08:00
winewill 12f0608cad Merge master into fix/windows-mandatory-update-overlay 2026-09-20 15:07:10 +08:00
_Kerman b93f55f7ff Merge remote-tracking branch 'origin/master' into xtr/default-workspace-first-send 2026-09-20 15:03:03 +08:00
fz 3085139699 test(runtime): align fixtures with current type-safety gates 2026-09-20 15:02:02 +08:00
_Kerman e71df56b1a feat(workspace): create the default workspace on first send 2026-09-20 14:59:15 +08:00
liukx0205 5e13010b90 test(web): document suite-level browser setup and bound the reconnect polls 2026-09-20 14:54:27 +08:00
liukx0205 b882e7fc20 docs(session): record the client-local engagement memory limits 2026-09-20 14:54:27 +08:00
liukx0205 d8480db9f0 refactor(web): read engagement retention from one identity set
Address review: retention now has a single definition (retainedIds) shared by
the list-refresh sweep and the single-id prune, an already-engaged identity
skips the redundant row mutation, and the ledger documents why Host-asserted
running is recorded before any holder exists while the client-local acceptance
callback requires one.
2026-09-20 14:54:20 +08:00
fz f25957d442 Merge remote-tracking branch 'origin/master' into feat/workspace-dependencies-package 2026-09-20 14:50:54 +08:00
fz 065def9d41 docs: use compliant issue references in native extraction note 2026-09-20 14:50:48 +08:00
Dudu-0223 f984683956 fix(subagent): store unknown mode in the existing catalog event 2026-09-20 14:46:54 +08:00
Tianyi Cui 5a53758b03 test: preserve plugin-manager integration after stack rebase 2026-09-20 14:45:26 +08:00
lsdsjy 4f0ee6b105 fix(settings): defer developer features until host preference resolves 2026-09-20 14:45:24 +08:00
winewill 8e5073ee61 docs(desktop): clarify test release ID reuse and qualification 2026-09-20 14:44:36 +08:00
liukx0205 4bff618b17 docs(web): note the metadata hint applied over the engagement rule 2026-09-20 14:44:25 +08:00
liukx0205 ae58d210eb docs(web): scope the second projection store to a discarded store 2026-09-20 14:44:01 +08:00
liukx0205 7aa2d4b5cc docs(web): note the retained subagent catalog on session removal 2026-09-20 14:44:01 +08:00
liukx0205 e3f0d3fb6e fix(web): preserve session engagement across blank list refreshes 2026-09-20 14:43:46 +08:00
Tianyi Cui ddf11a4f0e fix: name Anthropic Messages in DeepSeek endpoint guidance 2026-09-20 14:37:58 +08:00
Tianyi Cui 0959c5be45 Align provider guides with Messages-only configuration 2026-09-20 14:37:58 +08:00
Tianyi Cui 9ab5c8e672 Fix Messages real-API package resolution and usage assertions 2026-09-20 14:37:58 +08:00
Tianyi Cui 385a540d7e Fix Messages-only consumer validation and document recovery 2026-09-20 14:37:58 +08:00
Tianyi Cui 2b29f31dc6 test(llm): assert native Messages file metadata 2026-09-20 14:37:19 +08:00
Tianyi Cui 99e22ebbeb refactor(llm): make the official DeepSeek adapter Messages-only 2026-09-20 14:37:19 +08:00
Dudu-0223 6f3140d7cf Merge origin/master into fix/lazy-subagent-migration 2026-09-20 14:37:01 +08:00
fz 5992977f2e fix(pkg): make SEA native addon extraction atomic
@yao-pkg/pkg's SEA bootstrap extracts native addons from the VFS into a
per-user cache ($PKG_NATIVE_CACHE_PATH/pkg/<sha256>/) with plain
writeFileSync, so concurrent first starts of the same executable truncate
files another process has already mapped: SIGBUS on Linux, "Library not
loaded" for sharp's libvips on macOS. Extend the existing pnpm patch so the
three write sites go through a same-directory temporary file and
renameSync; a cache path is then either absent or complete, every process
finishes its own copy pass before it loads, and identical content makes
concurrent renames harmless without a lock. Cache location, hash keying,
and per-file comparison are unchanged. The single-exe Agent Note records
the decision and the measured before/after runs.

Closes #4664
2026-09-20 14:36:27 +08:00
07akioni 90f0dc8f7d Check terminal descendant quiescence without requiring PID reaping 2026-09-20 14:31:11 +08:00
Dudu-0223 17a52a8fe3 fix(subagent): retain unreadable children in migrated catalogs 2026-09-20 14:30:02 +08:00
Turtle 8cf9c0eded fix(bash): allow blank justification without escalation (#4662)
* fix(bash): allow blank justification without escalation

* fix(bash): permit empty reasons when repeating the current mode
2026-09-20 14:28:57 +08:00
creatixchu c1ee42cd9c fix: portal file action tooltips outside clipped cards 2026-09-20 14:28:15 +08:00
winewill c05771d828 build(desktop): isolate test downloads by release ID 2026-09-20 14:17:51 +08:00
07akioni 2f4b354253 Fix Office packaging tests for static checks and Windows paths 2026-09-20 14:17:36 +08:00
lsdsjy c7c900a912 fix(settings): enable developer tools by default 2026-09-20 14:15:59 +08:00
lsdsjy a026ab1397 fix(onboarding): scope developer tools to trajectory tab visibility 2026-09-20 14:11:17 +08:00
creatixchu a4cd2f57a0 fix: keep file action feedback visible while hovering delivery cards 2026-09-20 14:07:32 +08:00
creatixchu a6daee84ca test: enable the shared opener in the delivery replay 2026-09-20 14:06:00 +08:00
creatixchu 6f1857ae1b test: refresh delivery snapshots for shared file controls 2026-09-20 14:04:54 +08:00
creatixchu 03fde971e2 build: allow browser-safe file association metadata validation 2026-09-20 14:02:18 +08:00
creatixchu 1f7e491526 docs: describe shared delivery and review file actions 2026-09-20 14:00:41 +08:00
07akioni 552bf8a869 Merge master into desktop Office ASAR fix 2026-09-20 14:00:04 +08:00
creatixchu 28089001a2 fix: complete file action slot contracts and branch coverage 2026-09-20 13:59:00 +08:00
imccyu bc2c9bd11a Merge pull request #4427 from deepseek-harness/session-actions-slot
feat(client): add extensible session menu actions
2026-09-20 13:56:15 +08:00
creatixchu 59fdc2604d test: provide the file action slot runtime in delivery fixtures 2026-09-20 13:56:09 +08:00
creatixchu b80722812b fix: scope shared file actions to their owning views 2026-09-20 13:55:27 +08:00
creatixchu 56d0534c30 refactor: share native file actions across delivery and review views 2026-09-20 13:53:16 +08:00
Huanqi Cao e553344063 test(sandbox): drop new unknown casts after the merge-forward
The no-unknown-casts ratchet landed on master after this branch, so the new stub tables in the ACL failure paths can no longer assert through unknown. Route them through a typed partial stub, type the bigint handles as NativePtr, and prune the three retired baseline entries.
2026-09-20 13:42:58 +08:00
creatixchu 33782150cd fix: name the default application in opening tooltips 2026-09-20 13:42:31 +08:00
creatixchu 6e260998c9 fix: refine shared file controls and native query errors 2026-09-20 13:39:11 +08:00
imccyu ee61acfe7e docs(client): describe session row entries as plain menuitem buttons
The Client Slot catalog serves `cordis-client-runner` closures, which cannot
`require` anything, so the `sidebar.workspaces.session.menu.item` example
now renders a `role="menuitem"` `<button>` and the doc names
`MenuItemButton` as what the shipped rows use rather than what an entry must
render; the README separates the Module Loader lane, which does reach the
primitive through `require`, from the runner lane. The Agent Note and the
contract module doc say the entries read the pin and archive sets through
hooks their own faces inject, that the owner draws no separator, and that
the strip keeps clicks from opening the row. `RowActionToast` gets its
contract sentence and the rename-assembly spec points at the e2e that
exists.
2026-09-20 13:33:58 +08:00
imccyu c4949c4d0c fix(client): isolate row action clicks and collapse submenus from the menu list
The Session row's action strip stops click propagation itself, so a
`sidebar.workspaces.session.row.action` entry that renders a plain button no
longer opens the row as a side effect; the shipped buttons drop their own
handling. `Menu` decides submenu exclusivity on the list's own bubbles, by
DOM: reaching a top-level row that is not a submenu parent, by pointer or by
focus, closes the open card for data rows and component rows alike, which
the per-row handlers only did for data rows. A group start on the list's
first row draws no hairline.

The first live-fixture e2e case registers the scaffold's cleanup right after
launch, as the file's other cases do. The three pin scenarios the browser
spec lost when the order write moved into `UiWorkspace.pinSession` — newer
saved orders during a pending pin, a Workspace disappearing during one, and
members whose summaries are temporarily missing — return as service cases.
2026-09-20 13:33:55 +08:00
creatixchu 27b7fd0e2f fix: preserve Shell reference storage during failed handoff cleanup 2026-09-20 13:26:18 +08:00
creatixchu 7943fb3917 Merge master into file application controls 2026-09-20 13:25:10 +08:00
_Kerman dfcef60d06 Merge pull request #4513 from deepseek-harness/xtr/fix-snapshot-source-ranges
fix(session-snapshot): expand source references before writing fixtures
2026-09-20 13:24:41 +08:00
Yichen Jiang c6bc1a2fab Merge remote-tracking branch 'origin/master' into worktree/experimental-voice-input
# Conflicts:
#	apps/desktop/electron-builder.config.mjs
#	apps/desktop/tests/macos-signature.spec.ts
#	apps/desktop/tests/main-startup.spec.ts
#	apps/web/tests/expected/plugin-config/official.expected.md
#	apps/web/tests/expected/plugin-manager/live-enabled.expected.md
#	apps/web/tests/expected/plugin-manager/manager.expected.md
#	apps/web/tests/plugin-config.e2e.ts
#	apps/web/tests/plugin-manager.e2e.ts
#	apps/web/tsconfig.json
#	docs/module-graph.i18n.yaml
#	docs/module-graph.md
#	docs/module-graph.zh.md
#	packages/client/ui-conversation/src/client/skeleton/InputBar.tsx
#	packages/client/ui-plugin-manager/src/client/locales.ts
#	packages/client/ui-plugin-manager/src/client/presentation.ts
#	packages/client/ui-plugin-manager/tests/components.client.spec.tsx
#	packages/client/ui-primitives/README.i18n.yaml
#	packages/client/ui-primitives/README.md
#	packages/client/ui-primitives/README.zh.md
#	packages/client/ui-primitives/src/StateDot.tsx
#	packages/client/ui-primitives/src/icons/index.tsx
#	packages/client/ui-primitives/tests/icons.client.spec.tsx
#	packages/experimental/README.i18n.yaml
#	packages/experimental/README.md
#	packages/experimental/README.zh.md
#	packages/extensions/cordis-client-runner/src/client/slot-catalog.ts
#	pnpm-lock.yaml
#	scripts/gen-cordis-catalog.ts
#	tsconfig.host.json
2026-09-20 13:22:11 +08:00
creatixchu 6493cdac9c fix: retain the Windows Shell host until asynchronous handoff settles 2026-09-20 13:14:49 +08:00
_Kerman 1491975dba Merge remote-tracking branch 'origin/master' into dshw/pr-deepseek-harness-deepseek-harness-4513
Re-record the session-snapshot README bilingual pairing sidecar, and admit
the source-reference regression fixture with the v4 producer-owned `user`
message source kind.
2026-09-20 13:09:14 +08:00
imccyu 3821dc58e3 Merge pull request #4639 from deepseek-harness/fix-migration-v4
fix(session): serve cold forked sessions from the projection cache
2026-09-20 13:04:11 +08:00
creatixchu 39af517961 test: evaluate the Windows fixture output path as a PowerShell expression 2026-09-20 12:58:58 +08:00
creatixchu cbc51b5446 test: isolate native application identity and cold-history associations 2026-09-20 12:51:18 +08:00
imccyu 7d1afeccb0 test(web): register the live fixture rows in the session menu list
The fixture plugin's rows join `sidebar.workspaces.session.menu.item` after
the shipped rows, the first one opening the plugin group with
`separatorBefore`, and dismiss the menu through `useMenuOpenState`.
2026-09-20 12:40:35 +08:00
imccyu 4517603c28 fix(client): rename the row rename request type off the Host name
The typert Cordis catalog drops any exported name that two declarations
share, so ui-workspace's `SessionRenameRequest` made the generator remove
Session Controller's request type from the model-facing API catalog. The
row-side pending rename is `SessionRenameTarget`: the Session and the title
the dialog seeds from.
2026-09-20 12:40:11 +08:00
creatixchu 5218958366 test: keep native association expectations strictly typed 2026-09-20 12:35:53 +08:00
creatixchu 4fb9d34275 fix: use the native test task timeout for fixture readiness 2026-09-20 12:32:05 +08:00
creatixchu b689c724d1 docs: describe cross-platform file opening and pin desktop browser fixtures 2026-09-20 12:28:41 +08:00
creatixchu b4b0c4e4c8 test: exercise native Linux file handoff and document shared adapters 2026-09-20 12:24:09 +08:00
imccyu 21586fe28c feat(client): compose session row actions from slot lists
A Session row's "..." menu and its hover buttons become two list slots the
WorkspaceBrowser entry declares, `sidebar.workspaces.session.menu.item` and
`sidebar.workspaces.session.row.action`. The shipped actions register from
apply the way a client plugin does: pin (menu 100, button 200), rename (200),
fork (300), archive (menu 400, button 100). An entry receives only the row
identity, reads the pin and archive sets through Set hooks its own face
injects, decides its own visibility, renders `MenuItemButton` or one icon
button, and dismisses the menu through the slot-level `useMenuOpenState`
hook bound from the row's render occurrence.

Each action's behavior lives in its injected callbacks. `UiWorkspace` gains
`pinSession` and `unpinSession`; the pin write into the saved orders happens
there, through the viewing-store instance the apply creates and hands the
browser's registration as its handle (the ui-layout pattern), reading the
memberships current at completion. The rename dialog and the row-action
notice are `shell.overlay` entries fed by apply-closure observables through
the faces' `hooks`; the archived notice switches the archived filter
directly. WorkspaceBrowser threads no action callbacks, renders no toast,
and hosts no rename dialog; its title double-click raises the same rename
request.

In ui-primitives, `MenuItemButton` is the plain `role="menuitem"` row for
menus whose rows are components: it shares a data row's markup and styling
and needs no shared state, because `Menu` decides the keyboard walk from the
DOM and now runs the post-selection focus return once on the list's click
bubble for every row kind. `Menu` accepts such rows as `children` beside
`items`, which becomes optional together with `onSelect`; `separatorBefore`
lets a row start a group with the same hairline a separator entry draws. The
context-based `MenuAction` goes away with its extension group.
2026-09-20 12:20:24 +08:00
creatixchu aac990d2d4 test: cover desktop lookup fallbacks and cancel native fixture work 2026-09-20 12:20:04 +08:00
mektpoy 63190fc870 fix(client): unify the plugin identity colour on label-tertiary
Review feedback on this PR: keep one identity colour rather than raising the
card subtitle to label-secondary. The detail-page package name moves from
label-caption to label-tertiary; the settings card subtitle keeps its own
colour and shares only the 12px size with the row it expands into.
2026-09-20 12:19:13 +08:00
imccyu 5ca9b77c44 test(client): expect the pin row ahead of the session menu actions
master's session row menu now leads with Pin session and SessionNode
carries pinned/archived, so the slot-action specs and the Web golden take
the row as it is after the rebase.
2026-09-20 12:17:48 +08:00
Magolor d849d656a5 fix(client): use baseline menu primitive 2026-09-20 12:17:48 +08:00
Magolor 4db234b408 fix(client): align session action slot contract 2026-09-20 12:17:47 +08:00
Magolor fec5c888c4 fix(client): preserve session action slot contract 2026-09-20 12:17:47 +08:00
Magolor cfcd3f3b0e test(client): cover session action focus handoff 2026-09-20 12:17:47 +08:00
Magolor e5a72d73df feat(client): add extensible session menu actions 2026-09-20 12:17:47 +08:00
creatixchu a8c5ca85a1 test: cover Linux associations and native Windows Shell handoff 2026-09-20 12:15:15 +08:00
creatixchu a1545a9bf5 feat: share file associations across macOS Windows and Linux 2026-09-20 12:10:54 +08:00
Dudu-0223 f44b78a622 fix(session): complete parent catalogs while isolating child failures 2026-09-20 12:09:57 +08:00
creatixchu 219bbe47c7 feat: reuse the opening menu in a larger preview action 2026-09-20 12:00:03 +08:00
Dudu-0223 c08ead6900 Merge remote-tracking branch 'origin/master' into fix/lazy-subagent-migration
# Conflicts:
#	packages/client/ui-plugin-manager/tests/components.client.spec.tsx
#	packages/client/ui-workspace/README.i18n.yaml
#	packages/client/ui-workspace/README.md
#	packages/client/ui-workspace/README.zh.md
2026-09-20 11:58:21 +08:00
creatixchu 71b7bbeb88 fix: wait for file associations before enabling the default action 2026-09-20 11:55:48 +08:00
creatixchu 6111f879fd test: verify shared opener menu toggling and SSH visibility 2026-09-20 11:50:40 +08:00
creatixchu 5c409c39bc fix: type shared opening fixtures and document default actions 2026-09-20 11:48:58 +08:00
creatixchu e969aaff58 refactor: share compact opening controls for files and directories 2026-09-20 11:46:50 +08:00
lsdsjy c74c8bc471 Merge origin/master into feat/desktop-welcome-and-window-controls 2026-09-20 11:37:38 +08:00
lsdsjy f20c3e6a87 fix(desktop): align welcome typography and action sizing 2026-09-20 11:31:41 +08:00
lsdsjy f0a35eba0d feat(credentials,ui-settings-account): display bonus wallets separately 2026-09-20 11:31:41 +08:00
lsdsjy 0c0789e4df fix(deepseek-account-platform): identify embedded desktop requests 2026-09-20 11:31:41 +08:00
lsdsjy 9b53f0abbb fix(ui-settings-account): center collapsed account avatar 2026-09-20 11:31:41 +08:00
lsdsjy fb829bfc5c fix(desktop): refine welcome sign-in and workspace entry focus 2026-09-20 11:31:41 +08:00
lsdsjy 2d0f431181 feat(deepseek-account,ui-settings-account): display profile avatars with icon fallback 2026-09-20 11:31:41 +08:00
lsdsjy adcd85c08a fix(deepseek-account-platform): preserve client type in login completion redirects 2026-09-20 11:31:41 +08:00
lsdsjy 4232634dd7 fix(desktop): keep workspace hidden during startup activation 2026-09-20 11:31:41 +08:00
lsdsjy 575de9ccbd fix(ui-settings-account,deepseek-account): reset copy feedback and retain successful profiles 2026-09-20 11:31:41 +08:00
lsdsjy 52d5ecd7c6 fix(desktop): update welcome copy and remove forced preview 2026-09-20 11:31:41 +08:00
lsdsjy d952001c8c fix(ui-settings-account,desktop): adopt shared menu styling after master rebase 2026-09-20 11:31:41 +08:00
lsdsjy 68f8d32d83 fix(desktop,deepseek-account): share platform headers for Host API requests 2026-09-20 11:31:40 +08:00
lsdsjy 8cadbe8655 fix(desktop): clean up embedded views and complete packaged startup 2026-09-20 11:31:40 +08:00
lsdsjy d9456b9464 fix(ui-settings-account): fall back to masked contact in sidebar 2026-09-20 11:31:40 +08:00
07akioni 31b9b5cb77 fix(account-controller): declare generated schema dependency 2026-09-20 11:31:40 +08:00
lsdsjy d58c8cfcde fix(desktop): reconcile welcome startup with current update lifecycle 2026-09-20 11:31:40 +08:00
lsdsjy fe6702cb20 feat(credentials): configure inference origin and discard mismatched account grants 2026-09-20 11:31:40 +08:00
lsdsjy b73a2c8d2d fix(desktop,credentials,ui-settings-account): configure embedded deployment and disable label selection 2026-09-20 11:31:40 +08:00
lsdsjy 3402c95291 fix(ui-settings-account): hide account tab while signed out 2026-09-20 11:31:40 +08:00
lsdsjy 5b7195e032 feat(desktop,credentials,client): update sign-in protocol and Platform experience 2026-09-20 11:31:40 +08:00
lsdsjy 048297321a feat(desktop,credentials,client): integrate DeepSeek account sign-in and Platform pages 2026-09-20 11:31:40 +08:00
lsdsjy 753eff67b1 feat(desktop,client): add the native welcome window 2026-09-20 11:31:27 +08:00
Huanqi Cao 94a2d4af28 Merge remote-tracking branch 'origin/master' into fix/windows-acl-delete-escape
# Conflicts:
#	docs/config-catalog.i18n.yaml
2026-09-20 11:17:22 +08:00
creatixchu c6c9da3eba chore: retire the removed path-action unknown-cast baseline 2026-09-20 11:03:54 +08:00
creatixchu dd39d56af5 test: remove legacy unknown assertion from path-action fixture 2026-09-20 11:02:45 +08:00
creatixchu 79798d8c5a fix: satisfy package publication and file-action coverage gates 2026-09-20 11:00:05 +08:00
imccyu 4b02d03ed5 test(session): compare the forked cold row's recency with the list formula
The cold row's updatedAt is the later of the header's creation time and the cached last prompt; the fork's creation time is wall-clock and can trail the inherited prompt by a millisecond, so the restart test asserts the same maximum instead of the prompt time alone.
2026-09-20 10:54:20 +08:00
creatixchu 0c3045d488 fix: validate native metadata and refresh generated source references 2026-09-20 10:45:21 +08:00
creatixchu 5325babc7b test: cover default native command adapter selection 2026-09-20 10:43:06 +08:00
creatixchu e435340eff test: exercise application selection through the recorded Web session 2026-09-20 10:41:48 +08:00
creatixchu 0ae1b2f91c docs: describe native file application selection and platform scope 2026-09-20 10:39:42 +08:00
creatixchu 0839011204 fix: include native association types in client compilation 2026-09-20 10:37:11 +08:00
creatixchu 03d44191b3 fix: expose browser-safe file association metadata to Remote generation 2026-09-20 10:35:56 +08:00
creatixchu 97877bd8b4 test: cover file handler selection and shared path authorization 2026-09-20 10:34:24 +08:00
creatixchu d6d9d47c49 fix: constrain application icon pixels before transport 2026-09-20 10:32:57 +08:00
creatixchu 39bde1a5c9 feat: query file applications for sidebar opening (#4562) 2026-09-20 10:31:32 +08:00
imccyu de055efa9a test(session): list a forked Session's cached title after a Host restart
A real fork made through sessions.fork is checkpointed into the projection cache, the Context is disposed, and a Host booted over the same storage root serves the fork's title and list metadata from the header alone, as a kind: 'cached' block, without inspecting or opening the log.
2026-09-20 02:04:37 +08:00
imccyu fd76c9534f docs(session): sync the client merge-rule prose with cached and sequenced rows
The session-controller README, the per-session store JSDoc, and the projcache predecessor-recovery and durable-web-queue-recovery notes described one higher-seq-wins rule and the asOfSeq -1 title hint. They now state that higher-seq-wins applies among Host-sequenced values, that cached list blocks yield to the connected Session's baseline, and that the predecessor title carries its stored row's watermark under kind: 'cached'.
2026-09-20 01:48:34 +08:00
imccyu 6ab579b643 fix(web): label the assembled-remote fixture's list projection blocks
The built-client fixture served newly created Sessions with a control-baseline block as their list projections. Since SessionProjectionHints requires `kind`, the real client's applyListBlock would have hit assertNever on it; the created Session is live on the fixture Host, so its block is sequenced.
2026-09-20 01:48:33 +08:00
imccyu f3310dc746 docs(session): record the list block kind decision and cross-link the client merge rules
The listing identity note describes the `kind` field, rejects routing on the -1 sentinel, and links the projcache predecessor-recovery and projection-owned client state notes; the latter's client merge rules now distinguish Host-sequenced rows from cached list hints.
2026-09-20 01:31:52 +08:00
imccyu c4d40864fd fix(client): route list projection blocks by kind
A `sequenced` list block merges key by key under higher-seq-wins like any Host-computed value, so a delayed control baseline at a lower cut can neither overwrite nor clear a newer live-session list value. Only a `cached` block goes through applyCached and yields to every sequenced write. The projection store's module docs describe the two row kinds.
2026-09-20 01:31:52 +08:00
imccyu 1b51a1ad2d fix(session): label list projection blocks with their sequence space
SessionProjectionHints gains a required `kind`: `sequenced` when the Host's live registry computed the block for an attached Session, `cached` when a header-only listing viewed it from the persisted projection cache. `asOfSeq` stays the producer's own watermark in that space, so the cache read faces report the lowest served row seq again instead of the -1 sentinel. The two fields are independent: `kind` names the sequence space, `asOfSeq` the position in it.
2026-09-20 01:31:52 +08:00
imccyu 2ef33c7f28 docs(session): record the projection cache listing identity decision
Agent Note for the lifecycle identity read-only face and the client store's cached/sequenced row kinds: mechanism, rejected alternatives, and what stays untouched.
2026-09-20 01:31:52 +08:00
imccyu 9ff8f26450 fix(client): keep list-cached projection rows below sequenced writes
ProjectionValueStore rows are either cached (viewed from the persisted checkpoint by the session list, no comparable seq) or sequenced (computed by the connected Host, seq comparable within the connection). Cached values fill empty keys only; every baseline discards cached rows before seeding, and every frame replaces them regardless of seq, so a connected Session's values always supersede list hints. The list response and api-session/added blocks write cached rows.
2026-09-20 01:31:52 +08:00
imccyu 86ca9de07e fix(session): read cold seeded rows from the projection cache by header
The projection cache read-only face (cachedSnapshot, cachedPredecessorTitle) now matches the lifecycle identity a header witnesses (formatVersion, createdAt, cwd, isSeeded) and serves its rows as a cached block with asOfSeq -1. The fold face (hydratePrepared, write, coldSnapshot) keeps the full identity including the inherited cut. The session list and @ completion drop their isSeeded guards, so forked sessions keep their cached title and list metadata across a Host restart; list-children keeps its body fallback.
2026-09-20 01:31:52 +08:00
Tianyi Cui bfcfc9ed2f Merge pull request #4603 from deepseek-harness/worktree/session-attachment-carriers-20260919
fix(session): restrict attachment discovery to declared event content
2026-09-20 00:38:00 +08:00
Tianyi Cui 85c6e80e0c Merge remote-tracking branch 'origin/master' into worktree/session-attachment-carriers-20260919 2026-09-20 00:27:54 +08:00
Tianyi Cui 4b49137aa6 Merge pull request #4357 from deepseek-harness/worktree/fix-deepseek-files-json-errors
fix(llm): classify malformed Files JSON responses
2026-09-20 00:16:58 +08:00
Tianyi Cui 2319cbe476 Merge pull request #4623 from deepseek-harness/worktree/simplification-skill-survey-20260919
docs: refine simplification discovery and add nine proposals
2026-09-20 00:12:35 +08:00
Tianyi Cui 3f9567a4c9 Merge pull request #4604 from deepseek-harness/worktree/team-opaque-json-20260919
fix(agent-team): preserve opaque JSON in mailbox projections
2026-09-20 00:11:59 +08:00
imccyu 00dac23ba0 Merge pull request #4621 from deepseek-harness/worktree-fix0919
fix: 修复侧边栏 Fork、终端提示与持久 Shell 取消行为
2026-09-19 23:49:59 +08:00
imccyu d8c993a622 docs(shell): clarify queued cancellation regression coverage 2026-09-19 23:31:31 +08:00
imccyu 54b0aae8e6 docs(web): clarify retained subagent status sources 2026-09-19 23:31:31 +08:00
imccyu cdd082c4ae test(web): preserve terminal liveness evidence after layout loss 2026-09-19 23:31:31 +08:00
imccyu a05f2244cf fix(plugins): remove unknown casts from metadata loading and fixtures 2026-09-19 23:05:40 +08:00
imccyu d67545f367 docs: sync catalogs and contracts for session fixes 2026-09-19 23:05:40 +08:00
imccyu 8771607f8b test: cover sidebar fork and persistent shell cancellation regressions 2026-09-19 23:05:40 +08:00
imccyu 9d4fa54c2d fix(shell): settle persistent shell cancellation without throwing its reason 2026-09-19 23:05:40 +08:00
imccyu b7b587692d fix(web): disable terminal recovery and cleanup slots 2026-09-19 23:05:39 +08:00
imccyu a7ce695461 fix(web): position new forks before their source 2026-09-19 23:05:39 +08:00
imccyu 1e48dae2aa fix(web): keep sidebar forks from switching sessions 2026-09-19 23:05:39 +08:00
Dudu-0223 e6e5d95b29 test(session): align migration corpus and CLI expectations with lazy reads 2026-09-19 23:03:38 +08:00
Dudu-0223 6362113ef8 Merge master into fix/lazy-subagent-migration 2026-09-19 22:53:29 +08:00
Dudu-0223 1c16bc9a0e test(subagent): use branded descriptor sequence in restored identity 2026-09-19 22:47:59 +08:00
Dudu-0223 a9985b49ae fix(session): migrate historical subagents only when opened 2026-09-19 22:47:01 +08:00
mektpoy 88ee6039c6 Merge remote-tracking branch 'origin/master' into fix/windows-plugin-list-type-size 2026-09-19 22:33:52 +08:00
mektpoy bb2326f044 fix(client): raise the plugin package name contrast on the detail page
The package name under a plugin's title is the exact spec that installs it, yet
it was the palest alias in the palette: label-caption, 2.13:1 against white,
lighter than the free-text description below it. Use label-secondary, the same
alias the description uses, for 5.80:1.
2026-09-19 22:24:35 +08:00
imccyu 3863992e67 Merge pull request #4571 from deepseek-harness/worktree-pluginlocale
feat: 插件元信息支持按 export 多语言展示
2026-09-19 22:15:53 +08:00
Xu Hanxiang 128d7d81d0 Merge pull request #4574 from deepseek-harness/fix/4573-browser-use-shared-instances
fix(browser-use): share the installation scope and MCP client instances
2026-09-19 22:04:23 +08:00
Tianyi Cui 4ffc8c7bef docs: clarify survey evidence and proposal cleanup steps 2026-09-19 21:58:03 +08:00
mektpoy faa6986b2b docs(browser-use): use the runtime resolution vocabulary in the instance note
The profile resolution lookup order note, merged today, owns the mechanism this
note describes and names it runtime resolution with an interception layer at
$DSH_HOME/profiles/node_modules. This note adopts that vocabulary and links the
owning record instead of restating the resolution order.

It also records the boundary the sibling experimental providers leave open: a
profile that installs one of them beside this package hoists their
dsh-mcp-client dependency into the profile's own node_modules, so this package's
peer resolves there and the two copies keep separate serverName reservations.
The scope split does not return, because none of them declares dsh-scope.
Issue #4628 tracks moving those declarations to peers.

Refs #4628
2026-09-19 21:53:43 +08:00
mektpoy 3014457c60 fix(client): share one size for the plugin inventory entry identity
Keep the Loader entry id at one size in both places it appears - the card
subtitle and the row the card expands into - by moving the shared code-font
typography into a single rule. The subtitle keeps its own secondary label
colour against the expanded row's primary one.
2026-09-19 21:46:43 +08:00
Tianyi Cui cf75d1d099 docs: refine simplification discovery and add nine proposals 2026-09-19 21:31:31 +08:00
Tianyi Cui 3a0d640214 test(llm): show malformed Files payloads in case names
Interpolating an empty body directly produces empty parentheses in the failure title, making the empty-response case harder to distinguish from the truncated JSON case in CI logs.

Use JSON formatting for the existing case object so the operation and body are visible, including an explicit empty string. Keep the same inputs, error classification, original-cause assertions, and single-request checks.
2026-09-19 21:19:46 +08:00
Tianyi Cui 914bd34ea7 test(session-log-export): type attachment fixtures without unknown bridges
The attachment-policy fixtures introduced three assertions to unknown and
changed two previously recorded assertions. The new repository gate rejects
those escape hatches even though the earlier branch CI passed.

Give the user, tool-result, and inbox fixtures their required V4 message
metadata and declared identifier types. Declare the fixture's plugin content
tag through ContentBlockMap, and check the flat tool-result fixture directly
as its concrete SessionEvent type. Keep intentional malformed inbox entries
and opaque extension fields in the existing negative cases.

Remove all five unknown bridges without adding allowances. Delete the two
baseline entries retired by the rewritten existing fixtures; the other
three assertions were never part of the accepted baseline. Attachment
runtime code and the tests' access-denial and complete-log assertions remain
unchanged.

Validation: the Host TypeScript aggregate and all 52 archive tests pass.
verify-no-unknown-casts reports no new or stale entries, with 1575 existing
assertions remaining; the baseline adds zero allowances and retires two.
2026-09-19 21:19:32 +08:00
Tianyi Cui d4ff95c8e2 docs(llm): distinguish Files JSON decoding from metadata validation
The operation, HTTP status, and parser cause added by responseJson belong to JSON decoding failures. Existing metadata validation also uses INVALID_RESPONSE but does not provide those fields, so the broader phrase parsing failures promises too much.

Limit the README guarantee to JSON decoding and keep metadata-validation behavior unchanged. Keep the Agent Note focused on why the existing INVALID_RESPONSE code is used without inferring a transport retry, and link to the README for the error-field details. Update both languages and their pairing records.
2026-09-19 21:19:07 +08:00
Tianyi Cui 0c44e5461d fix(session): restrict attachment discovery to declared event content
Attachment reads and ZIP export previously inferred ownership from common
field names on arbitrary events. Opaque ignorable plugin payloads could
therefore cause storage reads, while declared compaction content was missed.

Select attachment-bearing fields by current built-in event type in both
readers. Read direct image and file blocks, including flat V4 tool results
and developer messages, without interpreting extra block fields. Leave
unknown payloads and malformed inbox entries opaque. The archive preserves
the complete logical log even when its payload does not authorize a read.

V4 validates title requests as one text block, so do not treat them as an
attachment carrier. Compaction keeps its declared ContentBlock fields for
valid custom producers; the basic producer's text-only output is documented.
Historical wrappers remain the adjacent converters' responsibility rather
than a second compatibility path in attachment readers.

Adapt the original policy change to master after V4 landed, retaining its
author credit and scope. Cover tool-result export explicitly, keep Team
fixtures free of invented message roles, and verify that denied carriers
never read storage. The 889 owning tests pass with 100% statement, branch,
function, and line coverage for both changed runtime files.

Co-authored-by: Tianyi Cui <[email protected]>
2026-09-19 21:08:29 +08:00
mektpoy 4167b60997 Merge remote-tracking branch 'origin/master' into fix/4573-browser-use-shared-instances 2026-09-19 21:03:19 +08:00
Tianyi Cui df3f4477ea test(agent-team): type opaque JSON fixtures without unknown assertions
The new no-unknown-casts check rejects two assertions introduced by these projection regressions. Both fixtures intentionally represent unknown content: malformed input sent to the admission checks, and a cached plugin block whose opaque field was already lost.

Declare those inputs as unknown variables instead of asserting their expressions to unknown. Keep the same malformed values, content assertions, checkpoint replay, and production validation. Do not expand the recorded cast allowances or change any runtime behavior.
2026-09-19 20:59:52 +08:00
Tianyi Cui f56aecb314 test(agent-team): remove an unused store from checkpoint restoration
The cold checkpoint regression calls only the projection registry register and restore methods. Neither needs SessionStore, so mounting and disposing that extra service obscured the fixture dependency. Keep the registry lifecycle and the recovered-content assertion unchanged.
2026-09-19 20:59:52 +08:00
Tianyi Cui 50d38688be docs(agent-team): state the local content validation scope precisely
The Team projector declares only a subset of current content validators; the existing file and image validator gaps are not fixed by opaque JSON preservation. Limit the new prose to those local validators instead of promising full ContentBlockMap validation.

Keep cache behavior in the README and invalidation rationale in the existing decision note. Document why unknown JSON objects pass through by reference without changing any parser branch or expanding this repair into unrelated validation policy.
2026-09-19 20:59:52 +08:00
Tianyi Cui 1e6630a856 fix(agent-team): preserve opaque JSON through mailbox projections
A valid unknown content block can own a __proto__ JSON field. Zod object reconstruction silently drops that key when Team events are projected and when checkpoints are admitted, even though the original Session log still contains it.

Validate the unknown block tag without reconstructing its decoded JSON fields. Keep the current V4 local validators and the rejection of retired tool-result content. Bump only the Team projection cache stateVersion from 3 to 4 so old lossy checkpoints are rebuilt from the unchanged log; this is not a Session format or schema change.

Cover direct and nested opaque JSON fields inside accepted native V4 content, plus public registry restoration from an older lossy checkpoint. Historical tool-result conversion remains owned by the Session migration and is not restored in the Team parser. Update the README and existing Team decision rationale together.

Adapted from the Team JSON preservation work in b8f1da92599123e5d5bae6c174f73b5b778f3554 by Tianyi Cui.

Co-authored-by: Tianyi Cui <[email protected]>
2026-09-19 20:59:52 +08:00
Tianyi Cui d6a0e3323e fix(llm): classify malformed Files JSON responses
Empty or truncated successful Files API bodies currently escape as bare SyntaxError values. They omit which Files operation failed and the successful HTTP status, obscuring the diagnostic while the caller still needs to reject the response.

Route upload, list, retrieve, and delete JSON parsing through one helper that converts only SyntaxError to LlmError INVALID_RESPONSE. Preserve the operation, HTTP status, and original parser cause; transport and cancellation failures retain their original identity. Do not introduce retries or reinterpret malformed JSON as a successful response.

Keep regressions for both Messages and Chat Completions, all four operations, empty/truncated bodies, single request execution, and non-parser error identity. This improves classification and diagnostics without claiming to repair the remote cause of intermittent empty responses.

Co-authored-by: Tianyi Cui <[email protected]>
2026-09-19 20:59:51 +08:00
Tianyi Cui b1dbe92356 Merge pull request #4618 from deepseek-harness/worktree/fix-migration-json-typing-20260919
fix(test): type migrated JSON without an unknown cast
2026-09-19 20:57:47 +08:00
Tianyi Cui 77bcc35adb fix(test): type migrated JSON without an unknown cast
The merged concurrent migration test used an as-unknown widening assertion after JSON.parse. Master now rejects newly introduced unknown casts, so that test blocks the repository gate and unrelated PR validation. Give the map callback an explicit unknown return type instead. Keep the gate baseline unchanged; parsing and assertions have the same runtime behavior.
2026-09-19 20:52:09 +08:00
mektpoy 76f0422653 Merge remote-tracking branch 'origin/master' into fix/4573-browser-use-shared-instances 2026-09-19 20:32:48 +08:00
Tianyi Cui a22ac3284d Merge pull request #4611 from deepseek-harness/worktree/parallel-v4-corpus-migration-20260919
perf(session): parallelize V4 migration with JSON reports
2026-09-19 20:27:44 +08:00
Tianyi Cui fafa154a14 Merge pull request #4610 from deepseek-harness/worktree/prohibit-as-unknown
feat(infra): prohibit new unknown casts and clean up existing uses
2026-09-19 20:27:11 +08:00
Tianyi Cui 84659abe3d docs(session): clarify bulk migration cache ownership
Name the JSONL backend and explain that its cache reuses recently decoded logs. This makes the bounded-batch rationale concrete without duplicating private implementation constant names in the operational description.
2026-09-19 20:19:43 +08:00
imccyu a9f205c584 refactor(plugins): rename PackageMeta to PluginLocalizedMeta 2026-09-19 20:19:08 +08:00
imccyu acec2ba713 docs(plugins): document PluginLocalizedMeta type name 2026-09-19 20:19:08 +08:00
imccyu 8ceaa725e0 build(plugins): validate locale resources and refresh catalogs 2026-09-19 20:19:07 +08:00
imccyu 0d611ea151 feat(web): display plugin-owned locale metadata 2026-09-19 20:19:04 +08:00
imccyu 0b7cb4102b feat(plugins): resolve installed metadata through runtime resource exports 2026-09-19 20:19:04 +08:00
imccyu f8240240fa docs(plugins): define localized display metadata and fallback rules 2026-09-19 20:19:03 +08:00
Tianyi Cui 0e46c4ed26 docs(session): specify fatal migration job behavior
An unexpected visitor rejection represents a batch-level programming or reporting failure, rather than a normal per-Session refusal. The scheduler already drains its active workers before surfacing it and does not start deferred retries. State that behavior explicitly so callers do not assume the retry pass runs after a fatal batch failure.
2026-09-19 20:18:58 +08:00
Tianyi Cui 0ab7976461 test(session): exercise the source-change retry through the CLI
The parent-child integration case can succeed without observing a race, while scheduler-only tests do not exercise the actual error classification or CLI output. Inject one genuine source-change error at the persistence open boundary in an isolated child process, then delegate to the real backend. Assert DEFERRED, completion of the other input, one RETRY, preserved source bytes and the resulting parent catalog. This adds no production test hook or timing-based race.
2026-09-19 20:18:41 +08:00
Tianyi Cui 33d93ab155 fix(session): accept safe numeric job-count overrides
The command promises any positive safe integer, but its additional decimal-spelling regex rejected valid expert overrides such as 1e2 and +1. Let Number parse the supplied value and retain the positive safe-integer check. This preserves the CPU-based default cap while allowing equivalent numeric spellings and larger explicit values.
2026-09-19 20:16:30 +08:00
Tianyi Cui 0dd9a282b8 test(session): clean migration fixtures without traversing junctions
The source-entry regression creates a Windows junction to the real checkout. Its local unlink normally removes it, but fallback teardown must remain safe if that unlink fails. Use the repository fixture cleanup helper to unlink reparse points before recursive deletion and retain its bounded Windows handle-release retries. This changes only test resource cleanup.
2026-09-19 20:14:54 +08:00
Tianyi Cui e65d349de3 fix(infra): reconcile cast baseline with Session V4 master 2026-09-19 20:13:58 +08:00
Tianyi Cui fd0654e96b fix(infra): stabilize cast fingerprints and reject unknown unions 2026-09-19 20:13:58 +08:00
Tianyi Cui 580bdc7258 refactor: remove redundant unknown casts 2026-09-19 20:13:58 +08:00
Tianyi Cui c5d775d437 feat(infra): prohibit new assertions to unknown 2026-09-19 20:13:58 +08:00
Dudu-0223 bc45bd821a Merge pull request #4530 from deepseek-harness/fix/compaction-threshold-reserves-completion
fix(compaction): reserve output tokens and 64K pressure headroom
2026-09-19 20:10:38 +08:00
Tianyi Cui f476a25de3 feat(session): emit structured corpus migration summaries
A text error list does not show how failures are distributed across historical source versions or let developers aggregate repeated rejection reasons. Emit and save the same JSON report with per-version outcomes, stable failure groups and per-input diagnostics, plus runtime, checkout and report locations. Keep source versions from physical discovery rather than migrated handle headers.

Classify only known diagnostic fields such as unexpected members, unknown events, child prerequisites and sequence gaps. Preserve original messages and use an explicit fallback for other failures; the report makes no inference about historical producer bugs and cannot influence admission or retry decisions. Regression checks cover printed/file/log equality, private report files, grouped distinct sequence coordinates and source-file preservation.
2026-09-19 20:00:11 +08:00
Dudu-0223 ec7030afd3 fix(compaction): default summary output cap to headroom 2026-09-19 19:56:54 +08:00
Dudu-0223 9b016b6425 refactor(compaction): require an explicit output reservation 2026-09-19 19:56:53 +08:00
Dudu-0223 555b664b08 fix(compaction): reserve configurable 64K pressure headroom 2026-09-19 19:56:53 +08:00
Dudu-0223 b02b20d713 fix(compaction): pin output caps and cover proactive reserve replay 2026-09-19 19:56:51 +08:00
Dudu-0223 a1cebebd40 refactor(compaction): drop the spec's reservedCompletionTokens field
Review follow-up: nothing consumed the field. The reserve stays an input to
`resolveCompactSpec`, whose returned budgets already express its effect, so
`ResolvedCompactSpec` keeps its exported shape.

Note the name collision a consumer would have hit: `ResolvedTargetPolicy.maxTokens`
is the compaction *summarization* output cap (default 8192), not the routed
request's cap reserved from the same window.
2026-09-19 19:56:51 +08:00
Dudu-0223 0fadb08fbd fix(compaction): gate pressure on the message budget, not the whole window
Proactive pressure scaled `thresholdRatio` by the routed model's entire
context window, but a request also reserves output tokens the provider
charges to that same window — the LLM service deliberately never clamps
them (`packages/llm/llm/src/index.ts`).

With a 1,000,000-token window and the default 256,000-token request cap the
gate therefore sat at 800,000 while the provider refused any prompt above
792,576. Observed in a real session: three requests died at 792,850 /
797,639 / 793,352 message tokens ("maximum context length is 1048576 ...
you requested 1048850 tokens"), no pressure compaction ever fired, and the
overflow recovery that did run could not condense an ~800k-token history
into the 8192-token summary cap.

Scale both ratios by the message budget left after the reserve, sourced from
the effective request envelope's `maxTokens` and falling back to the
adapter's per-request output default. A reserve that leaves no message
budget reports a target-specific pressure configuration error, matching the
existing per-route warning policy rather than silently disabling proactive
compaction. The headless compaction e2e advertises a coherent capacity/cap
pair so its 8000-token message budget and 4000-token threshold are unchanged.

Related #1841
2026-09-19 19:56:51 +08:00
Tianyi Cui e105557046 perf(session): parallelize V4 corpus migration
Sequential corpus migration waits for each Session to finish I/O and publication verification before starting the next. Use a bounded queue with one shared persistence Context, defaulting to available CPU count capped at 16. Expert --jobs overrides accept any positive safe integer. Stable input indices and separate completion counts keep interleaved progress useful for large corpora without opening every log simultaneously.

A concurrently upgraded child can invalidate its parent evidence. Preserve the backend revision checks and defer only the explicit source-change error for one serial retry after initial work drains. Corruption, unsupported formats and writer locks remain failures; historical files and existing V4 data remain unchanged. Deterministic scheduler tests cover 10,000 inputs, bounded overlap and retry ordering, while real raw/zstd corpora verify parent catalogs and serial-equivalent output. Performance timing was stopped at user request because the host has competing workloads.
2026-09-19 19:49:30 +08:00
Tianyi Cui 0fa88d4875 Merge pull request #4320 from deepseek-harness/release/session-log-v4
feat(session): introduce Session format V4
2026-09-19 19:44:48 +08:00
mektpoy 31925760ca Merge remote-tracking branch 'origin/master' into fix/4573-browser-use-shared-instances 2026-09-19 19:31:56 +08:00
mektpoy 73bf301227 Merge remote-tracking branch 'origin/master' into fix/windows-menu-tooltip-overlap 2026-09-19 19:30:41 +08:00
Tianyi Cui 8dc1d0e3ed feat(session): add one-time V4 corpus migration command
Give developers one pnpm command to exercise the release migration against their own Session roots. Inventory physical generations so malformed entries remain visible, then reuse JSONL write-open locking, validation, and exclusive successor publication with the full root available for child-catalog evidence. Open existing V4 files read-only and preserve every historical generation.

Report each selected source and outcome, continue after individual failures, repeat all errors at the end, and retain a private temporary report with checkout/runtime details and full causes. A failed run exits nonzero. Explicitly close handles because source-launch await-using lowering inside a loop can retain a previous iteration error. Real-entry regressions cover raw/zstd tool-result conversion, repeated runs, unchanged originals, failure continuation, and unsupported layouts. Document the command in the migration cookbook.
2026-09-19 18:51:42 +08:00
Tianyi Cui b28fca7e87 docs(session): correct incomplete child evidence migration policy
The V4 acknowledgement still said missing child descriptors reject migration. The converter already skips backfill when a child has no single supported own descriptor, while explicit identity, timestamp, and mode conflicts still reject publication. Align both language versions with that behavior so release reviewers and corpus testers do not expect an obsolete refusal. Preserve every machine acknowledgement and schema fingerprint.
2026-09-19 18:51:32 +08:00
Tianyi Cui 370e1efcdb test(session-reference): use declared plugin sources in V4 fixtures
The conversation fixture used an undeclared goal source and forced it
through MessageSource with a double assertion. That bypassed the V4
producer-owned source types instead of exercising an input accepted by
createUserMessage.

Use the fixture's existing declared test source for both injected plugin
messages and remove the unused MessageSource import. The resolver excludes
all non-user, non-checkpoint sources, so these cases do not depend on goal
identity or need a goal-package dependency. Message content, event order,
captured sequence numbers, and every projection assertion stay unchanged.

Validation: all 49 session-reference tests pass, the Host TypeScript
aggregate passes, and the diff passes whitespace checks.
2026-09-19 18:22:32 +08:00
Tianyi Cui fdff56c293 fix(cli): drop retired link-mode override after master integration
The release branch still passed resolutionMode: link from the source CLI
and documented a separate link/runtime selector. The integrated master
has removed that selector and its materializer in favor of one runtime
resolver that also supports source launches. The spread kept compiling
because excess-property checking did not reject it, but the property was
ignored by runProfile and the retained prose contradicted the runtime.

Remove the dead option/comment and its obsolete bilingual paragraph.
The CLI entry and the architecture note now match the integrated master;
keep source-module identity verified through its real source-launch and
tool-execution tests. This retires an old integration workaround, without
changing Session V4 or restoring a second profile resolution backend.
2026-09-19 18:03:33 +08:00
Tianyi Cui 5da1515286 test(cli): inspect flat V4 results in source launch smoke
The new master source-tool smoke reads isError and content from a V3
nested tool-result block. After integration with the V4 release, that
block no longer exists: the durable message itself has role tool,
toolCallId, isError and result content. The old assertions fail to
compile and cannot verify the current writer's output.

Read the existing V4 fields while retaining the real source-profile
launch, single Tools instance, call identity, sandbox error and shell
output checks. Do not impose the old single-wrapper cardinality on the
flat content array. This adapts the incoming regression test without
changing the V4 format, migration implementation or stored recordings.
2026-09-19 18:01:49 +08:00
Tianyi Cui 8b0b2dc1d8 Merge master b86916de into release/session-log-v4
Integrate the master tip observed for the final V4 landing preparation on
top of the already merged migration stack. Preserve the V4 writer, flat
message migration, extension policy, catalog validation and historical
recordings while bringing in upstream profile resolution and desktop/UI
changes.

Resolve four generated translation-pairing records from the combined
English/Chinese text. Keep this master checkpoint separate from any
integration fixes and retain all reviewed parent history. Follow-up
validation covers the affected launch/UI paths and migration replay.
2026-09-19 18:00:36 +08:00
Tianyi Cui 2e061ec93f Merge pull request #4583 from deepseek-harness/worktree/v4-lossless-tool-results-20260919
fix(session): preserve extensions while flattening V3 tool results
2026-09-19 17:51:02 +08:00
Tianyi Cui 56f8c7f51b docs(session): keep migration mappings specific to each edge
The cookbook previously promoted plugin prefixes and owner separation into a general requirement. That overstates the migration policy: direct source kinds and ordinary metadata need no new namespace solely because they are extensible.

Require each edge to document only the mappings its target needs and why, preserve nested opaque data, and permit alpha refusal of unexpected source fields that would otherwise acquire new target meaning. Exact V3 source and ToolSchema choices remain in the V3-to-V4 README; no runtime, schema, or provider behavior changes.
2026-09-19 17:27:14 +08:00
imccyu b3961e06a0 Merge pull request #4608 from deepseek-harness/worktree-removelink-rename
refactor(boot): rename the profile resolution generation to the runtime resolution
2026-09-19 17:25:04 +08:00
Tianyi Cui bb81d312ae docs(session): scope extension prefixes to declared mappings
The convention identifies how an edge preserves the identifiers and extension fields that it explicitly maps; it does not require every extension-owned field in every source slot to be renamed.

Keep the cookbook general enough for preserved stream-start extras while retaining the exact field-owner mappings documented by each migration edge. This changes policy wording only, not the current V4 schema or conversion implementation.
2026-09-19 17:20:53 +08:00
Tianyi Cui 7528528007 test(session): name the wrapper pass-through assertion precisely
This case calls only liftToolResult; the native validator has separate coverage. Name the behavior actually asserted so the test title does not claim both paths are covered by one assertion. Assertions and production behavior are unchanged.
2026-09-19 17:20:53 +08:00
Tianyi Cui 3e31760bc7 docs(session): explain historical result field ownership
The flat result conversion prefixes every extension-owned field, not just names currently colliding with core fields. Correct the cookbook wording to match that complete mapping.

Record why retaining opaque message and wrapper owners does not invent a shared owner or a new core metadata field. The deliberate costs are retaining duplicate opaque values and requiring plugin consumers to use V4 names; the first-class tool-message decision and runtime implementation stay unchanged.
2026-09-19 17:20:53 +08:00
Tianyi Cui 4ab9101271 docs(session): state the flat result converter failure contract
The exported converter rejects malformed wrappers with SessionFormatError in addition to refusing unsupported nesting. Document both failure categories so callers can distinguish corruption from a converter support limit.

Remove the obsolete claim that recorded replay compares serialized field order: the native-writer parity assertion compares parsed objects. The emitted fields and their order remain unchanged.
2026-09-19 17:20:53 +08:00
Tianyi Cui 45ce6ef21d docs(session): clarify converter fixes leave existing successors intact
An incoming converter runs only when a historical generation is selected. Fixing a mapping after the target ships does not revisit already-produced target files, so allowing compatible converter fixes must not imply repairing those files automatically.

Keep the user-approved ability to extend or correct historical conversion without a target version bump. Require documentation of changed accepted mappings and continued support for existing outputs, and distinguish that work from changing target representation or interpretation incompatibly. Keep operational alpha guidance in the cookbook and the owning decision note focused on the runtime facts.
2026-09-19 17:20:53 +08:00
Tianyi Cui c7923096c7 test(session): bind tool-result parity to retained V3 replay
The parity assertion previously compared a fixed V3 log with whichever generation the ordinary scenario selected. A legitimate V4 re-recording could change call ids and make the assertion compare different model inputs.

Run an additional replay-only variant through the existing headless runner with both its replay file and migration expectation bound to the retained V3 recording. Keep the ordinary scenario on its highest generation, retain call ids in the comparison, and preserve every committed recording.

Validation: the ordinary replay, retained-V3 replay, and old-wrapper negative control pass. Changing only the latest V4 call ids makes the former assertion fail but leaves the corrected retained-V3 replay passing; both fixture and source bytes are restored after this control.
2026-09-19 17:20:53 +08:00
Tianyi Cui e90c55e0ab docs(session): distinguish converter support from target compatibility
The compatibility obligation belongs to the released target format.
An incoming converter can be fixed or support more historical inputs after
the target ships, as long as its output preserves that target's meaning.
Alpha releases may reject valid historical cases without an implemented,
evidenced mapping instead of expanding the format for speculative inputs.

Record the native-writer oracle, pre-normalization comparison, supported
flat V4 tool-result representation, uniform plugin naming, owner collision
rules, retained source files and read-only private corpus handling in the
migration cookbook. Align the existing architecture note with this rule;
keep native target changes and source conversion fixes distinct.

This updates contributor guidance only. It does not change finalization
checkpoint machinery, existing V4 schemas, or any released codec.
2026-09-19 17:20:53 +08:00
Tianyi Cui 1e7686b2c9 test(session): compare migration with raw native writer output
The ordinary snapshot comparator prepares both inputs through the current
format catalog. That can hide a writer still emitting an old tool-result
wrapper: both sides become the same normalized representation.

For the existing stock tool-call-turn replay, explicitly migrate its
retained V3 recording and compare result message fields with the freshly
harvested native log before format normalization. Check both generation
headers and remove only the generated message ID from this comparison.
Do not use the scenario's selected V4 successor as the migration input.

Add a negative control whose header claims the current writer while its
body keeps the V3 wrapper; the raw comparison must reject it. The same
shipped headless profile and stock bash tool provide the positive case.
Keep all recordings and their selected generations unchanged.
2026-09-19 17:20:53 +08:00
Tianyi Cui 0c8180c9b6 fix(session): preserve V3 result extensions in flat tool messages
The ordinary V3 writer records a user-role message containing one result
wrapper. V4 writes the same tool output directly in a tool-role message.
Keep that native V4 representation instead of introducing a nested message
content type for hypothetical historical shapes.

Preserve extra historical fields when removing the wrapper. Prefix outer
message fields with plugin:message: and result fields with plugin:result:
so equal names retain their original ownership and cannot overwrite the
interpreted call ID or error flag. Include the complete old name, even
when it is already prefixed, and preserve every JSON value and own key.
Only the validated wrapper supplies the interpreted result fields.

Leave unimplemented nested-result conversion as an explicit refusal that
preserves its source. Describe this as the converter's current limit, not
an impossibility or a reason to extend the established V4 format.

Cover owner/name collisions, opaque special keys, absent error flags,
unchanged sources and strict native reopening through plain and compressed
read/write persistence. No current message type or schema is added.
2026-09-19 17:20:53 +08:00
Tianyi Cui 869367d5d0 test(session): continue catalog backfill after unavailable evidence
Isolated complete/incomplete-child tests did not distinguish skipping one child
from prematurely stopping the entire candidate loop. A stop could therefore
silently omit a later child's fully supported catalog fact.

Pair a complete child with an older unavailable child so deterministic ordering
processes the unavailable evidence first. Cover missing, unsupported-version,
and multiple-descriptor evidence, requiring exactly the complete child's fact
with the correct sequence and timestamp.

A temporary continue-to-break mutation failed all three new cases. Restoring
the implementation passed the focused 19 cases and the complete selected owner
run: 428 passed, one compression-specific skip, with 100% coverage on facts.ts
and migration.ts. This commit changes tests only.
2026-09-19 17:17:29 +08:00
Tianyi Cui e838401a9b docs(session): state incomplete catalog evidence limits
The child-catalog README did not record the lasting missing-entry consequence
under Known Limitations, and its validation wording implied that duplicate or
unsupported descriptors had their discovery fields checked. The converter only
checks those fields when exactly one supported own descriptor is available.

Specify that distinction, retain creation-time checks for existing entries, and
state that current V4 reads do not rescan children to repair an omitted entry.
Document the first-descriptor composition reader and last-descriptor identity
projection accurately: multiple own records violate the establishing provider's
exactly-once rule, so the backfill policy does not choose between those consumer
policies. This corrects the rationale without changing migration admission.

Update the paired English/Chinese package docs and decision note together.
Validation: doc-sync 41/41, full lint, and owner tests passed; production source,
frozen generations, persistence schema, and recorded fixtures are unchanged.
2026-09-19 17:17:29 +08:00
Tianyi CuiandDudu-0223 ade7035fd8 fix(session): preserve parents with incomplete child catalog evidence
A readable V3 child may have no own descriptor, an unknown descriptor version,
or multiple descriptors. Those states do not provide a unique discovery fact,
but they do not make the parent or the retained child history unreadable.
Rejecting the entire parent prevented otherwise valid V3 Sessions from opening.

Append a missing parent catalog row only for exactly one supported own child
descriptor. Keep existing parent entries and every child record when no fact is
available. Retain rejection of mismatched creation times, unambiguous mode/label
conflicts, malformed known fields, unreadable membership and revision drift.

Cover raw and compressed read/write publication, preserved source bytes, late
descriptor availability, inherited descriptors and Preview packing. Document
that unavailable discovery evidence can leave a child absent from its parent
catalog and does not trigger automatic backfill after the V4 successor exists.

Extracts the child-evidence behavior from 7ba7e174dd1 and the Preview regression
from 9c85eb60bd25, preserving the original migration authors' work while keeping
message representation and extension interpretation outside this commit.

Co-authored-by: Dudu-0223 <[email protected]>
2026-09-19 17:17:29 +08:00
Tianyi Cui c6f6aa3293 fix(session): preserve V3 tool-schema metadata keys
The V3 writer on master declares and emits only name, description and
parameters. deferLoading is a first-class V4 persistence addition whose
provider behavior is not implemented yet. Prefixing every historical extra
ToolSchema key changes ordinary plugin metadata without a format need.

Preserve all ordinary tool-definition keys and JSON values unchanged,
including vendor metadata, own __proto__ properties, already-prefixed keys
and nested parameters. Remove the general key-renaming mapper. Reject only
an own top-level deferLoading field in V3 input: there is no established V3
meaning to translate, and the alpha converter may refuse unsupported source
forms rather than invent compatibility semantics. Leave native V4's
optional-true declaration, admission and provider refusal behavior intact.

The regression tests fail on the old mapper for metadata preservation and
unsupported-field refusal. Physical plain/zstd read/write tests verify source
bytes and identity remain unchanged and no V4 successor appears on refusal;
controls preserve nested parameters.deferLoading and native V4 true values.
No Session format/schema change, frozen codec edit, or historical recording
version bump is needed.
2026-09-19 17:15:38 +08:00
Tianyi Cui d5a109c60f fix(session): preserve direct V3 source kinds during wrapper conversion
Convert an unknown legacy plugin wrapper such as
{kind: 'plugin', plugin: 'acme'} into {kind: 'plugin:acme'}, retaining the
complete plugin name after one prefix. Keep the existing explicit
first-party producer renames and preserve all non-identity metadata.

A direct source already records its producer in kind. Reclassifying
unknown direct kinds into plugin:source:<name> needlessly changes that
attribution and forces plugin consumers to adopt new identifiers even
though V4 accepts their existing representation. Return every valid
direct source unchanged, including already-prefixed names. Remove the
obsolete direct-kind table and two-category identity helper rather than
adding collision classification or another mapping layer.

Update the source conversion examples, codec/restore regressions, and
native fixture producers together. Refresh only the owning Python
advanced scenario's unshipped V4 outputs; preserve historical V0-V3
recordings and their generation selection. Event names, content tags,
and ToolSchema migration remain separate concerns.

Refs #2692
2026-09-19 17:11:45 +08:00
imccyu cf3505b88e docs: call the package table the runtime resolution everywhere it is named
The generation note keeps generation for succession only and names the table, the interception, and the routes with the code's words; the CLI reference, Desktop, plugin-package-inventory, and loader-smoke READMEs, the bundled-runtime, agent-teams, plugin-bundles, and source-launch notes, and the client AGENTS guide follow. Pairing records re-recorded.
2026-09-19 17:06:35 +08:00
imccyu f7f22a6b48 refactor(boot): finish the interception vocabulary in code comments and the app-boot README
The Consumer-owned strictness bullet names the Loader subtree that agent presets own instead of the runtime resolution; the service field, its file comment, the dispose JSDoc, and the route JSDoc use interception; the native-after-interception route documents both anchors it can carry; stale module-fallback comments name the runtime resolution; importFromResolution matches its parameter.
2026-09-19 17:06:35 +08:00
imccyu b605d114bd docs(boot): finish the runtime resolution terminology in the generation and desktop notes 2026-09-19 17:06:35 +08:00
imccyu c0f34b4bcd docs(boot): name both installation anchor sources
The CLI derives the installation anchor from import.meta.url and the Desktop Host builds it from its runtime directory; neither is a symlink, so the installation root is kept as given. The note and the traversal comment say so instead of attributing every launcher to import.meta.url.
2026-09-19 17:06:35 +08:00
imccyu 06b0ff9560 docs(boot): describe the installation closure size without point-in-time counts 2026-09-19 17:06:35 +08:00
imccyu 058d1d779b docs(boot): keep the ancestor chain code block language neutral
The numbered chain lists directories only; the legend for ①-⑤ moves into prose so both language sides can read it while the code block stays byte-identical for the pairing gate.
2026-09-19 17:06:35 +08:00
imccyu 9848ac75f2 test(cli): explain the sandbox-unavailable acceptance in the source tool smoke 2026-09-19 17:06:35 +08:00
imccyu 048fc3e580 docs(boot): state that the installation anchor is already a real path
The lookup-order note records that the launcher derives the installation anchor from import.meta.url, so bundle discovery and dependency traversal start from the same real directory.
2026-09-19 17:06:35 +08:00
imccyu 15e07d2721 refactor(boot): rename the profile resolution generation to the runtime resolution
RuntimeResolution, RuntimeResolutionEntry, RuntimeResolutionOptions, and createRuntimeResolution replace the generation-named table types; installRuntimeInterception returns a RuntimeInterception; PluginPackages takes { resolution }; Worker environment data carries { resolution }; routes are native, interception, and native-after-interception. replace() keeps its successor-generation wording.
2026-09-19 17:06:34 +08:00
imccyu 5f48b2fff6 docs(boot): call the package table the runtime resolution
Notes and the app-boot README name the startup package table the runtime resolution and the installed hook the interception; generation remains the word for the additive successor semantics of replace().
2026-09-19 17:06:34 +08:00
imccyu bffd317c8c Merge pull request #4570 from deepseek-harness/worktree-removelink
fix(boot): keep runtime-only profile resolution and real package anchors
2026-09-19 17:04:22 +08:00
Yifffan a30f3a68c8 Merge pull request #4509 from deepseek-harness/feat/macos-sidebar-ui-and-drag-refine
feat(client): frame-owned macOS window chrome with single drag owner
2026-09-19 16:58:41 +08:00
imccyu 109efc7942 test(boot): cover the projection removal edge cases
A profile with the legacy directory but no node_modules, and a symlink whose target parent is gone, exercise the remaining branches of removeLinkProjections; the non-ENOENT realpath failure is a host filesystem fault and is excluded from coverage like its siblings.
2026-09-19 16:49:21 +08:00
Yif 9c48e74409 test(web): capture sidebar snapshots after the track-easing gate settles
The columnTransition probe raced data-animating's transitionend/600ms
settle: checkpoints recorded whichever side of the window the capture
landed on. Waiting for the attribute to drop pins the steady state —
no declared transition (computed 'all') — deterministically.
2026-09-19 16:39:47 +08:00
Yif 45d77b0e8e fix(ui-theme): match the scrollbar sheet exclusion against /-separated scan paths 2026-09-19 16:39:46 +08:00
imccyu 02fec3209e fix(boot): remove link-backend profile projections on profile load
removeLinkProjections unlinks the symlinks under a profile's node_modules whose target lies inside <profile>/.dsh-module-fallback/node_modules and then removes that directory; pnpm-installed packages and every other symlink stay. loadProfile and the Desktop applyRelease call it once per launch. Regression: with bundle A installed but deselected and bundle B selected, B's same-named plugin resolves instead of A's projected copy.
2026-09-19 16:34:07 +08:00
imccyu 0c342a81c4 docs(boot): record the one-time removal of link-backend profile projections
The lookup-order note, the cleanup-removal note, and the app-boot and Desktop READMEs state that profile load removes the .dsh-module-fallback projections the dsh 0.1.5 releases wrote into a profile, so a deselected bundle's projected plugin no longer shadows the same-named plugin the generation selects from another bundle.
2026-09-19 16:34:07 +08:00
Yif 9e95a7b183 fix(client): keep fullscreen sidebar strips draggable via fine-grained dockkit subtraction
Drop the fullscreen panel's whole-box no-drag: it swallowed the strips'
blank-run window drag and made no-drag the steady state, turning the
open/close app-region nudge into a computed-value no-op (electron#32341)
exactly when a hidden panel opens straight into fullscreen. The panel now
declares no steady-state app-region in either presentation, and ui-dockkit
subtracts the one box base.css never matches — the plain-div split divider —
alongside tabs, strip-end chrome, and pane bodies. New specs pin both
contracts against the stylesheets on disk.
2026-09-19 16:19:44 +08:00
Yif c6c1df6869 fix(ui-theme): normalize stylesheet-scan paths to forward slashes for Windows 2026-09-19 16:19:34 +08:00
Yichen Jiang 72d5194ce4 test(settings-models): drop an unnecessary JsonValue assertion in the add-card spec 2026-09-19 16:10:26 +08:00
Yichen Jiang f7fe7a1060 test(docs): re-record the translation prompt after the model-provider terminology rows 2026-09-19 16:08:17 +08:00
Yichen Jiang 7d06fcdc05 fix(settings-models): derive the add panels, lock the switch in flight, pair tabs with panels
The add card's panels are now derived from the shown mode and the modes
visited since it opened, and each renders only while its mode is still
offered, so a refresh that changes which modes are offered — every
configurable row gone, the pi-ai namespace unmounted, a route freed
after the catalog was exhausted — can neither leave the card without a
panel nor keep a form whose write the Host would refuse. The catalog
draft resolves to the chosen row while its namespace can take the
write, else to the first addable row; closing either panel forgets it,
so a later refresh cannot open a row editor unasked.

Both cards report a write or an endpoint interrogation in flight, and
the switch and the catalog select lock while one is, since a switch
underneath it would orphan the answer or the picker it opens. The
SegmentedControl takes the owner's id and pairs each tab with its panel
through `aria-controls` and `aria-labelledby`; a card offering one mode
carries that mode as its title instead of an orphan tabpanel, and a
locked segment names its reason as hover text. `fullWidth` goes: no
consumer asked for it.
2026-09-19 16:04:34 +08:00
Yichen Jiang 536a94b188 fix(settings-models): exact Models nav locators, scoped vendor term, review nits
The new "添加模型提供商" button shares a prefix with the Models nav entry,
so every e2e that clicked the nav through a non-exact locator resolved
two buttons once the page rendered; the nav locators are exact now.

The vendor sense of "provider" is registered in the terminology table
as 模型提供商 with its scope — the Models settings copy, the providers
user guide, and this package's README — while the generic provider rows
keep 提供方, and the package's Chinese README uses the vendor term
throughout. The custom card's header comment no longer claims a mode
switch that a custom-only card does not render, and the SegmentedControl
suite gains a positive Home/End case whose first enabled option is not
the first option.
2026-09-19 15:48:10 +08:00
Tianyi Cui ec2c4f2a42 test(session): isolate vocabulary fixture Git commands from caller paths
The new vocabulary fixture helper set cwd to a private repository but still
inherited GIT_DIR, GIT_WORK_TREE and GIT_INDEX_FILE. A negative control with
two disposable repositories demonstrated that git add/commit changed the
foreign repository's HEAD instead of the intended fixture repository.

Remove only those three location variables from the child environment.
Leave the caller's environment untouched and retain the existing identity,
hook and signing overrides. The regression checks the foreign HEAD and work
tree remain unchanged while the intended target commits its private file.
This is confined to the test helper; it adds no production Git abstraction
or Session-format behavior.
2026-09-19 15:43:10 +08:00
Tianyi Cui 696aa5f97c docs(session): describe retained source kinds by historical admission
The preserved direct-source set is the predecessor migration's accepted set
without the plugin wrapper. It includes older coordinator and subagent-report
identities even though those names have no current producer declaration.
Describe that historical admission fact instead of implying every retained
identity has a live V4 owner. No mapping or runtime behavior changes.
2026-09-19 15:43:10 +08:00
Tianyi Cui 4d9c4e7a12 fix(session): reject retired tool results in V4 Team checkpoints
Central V4 event admission already rejects tool-result content in Team
queue records, but projection checkpoints use the Team state schema directly.
After restoring the baseline Team parser during extraction, the production
SessionProjectionRegistry.restore() path accepted a decoded native V4
checkpoint containing the retired wrapper without reading an event.

Remove only that V4-incompatible schema arm. Keep the existing unknown-JSON
parser unchanged; its independent own-key preservation fix remains in the
standalone master PR, where recursive V3 tool results are still valid.

Exercise the real checkpoint restoration recipe with a valid text state and
an invalid retired-result state, plus queued-event projection. The new
checkpoint rejection failed before this change; central event admission and
the complete Team owner suite pass afterward with 100% projection coverage.
No Session schema, format version or projection state version is changed:
the rejected value is already outside the native V4 content vocabulary.
2026-09-19 15:43:10 +08:00
Tianyi Cui 88897a16c6 refactor(session): separate Team opaque JSON preservation from migration
Retaining own keys in the Team projection/checkpoint parser fixes a generic
JSON reconstruction defect. It is independent of V3-to-V4 identifier
conversion and can be reviewed directly against master, where recursive V3
tool-result content is still valid.

Restore the Team implementation, tests and paired README to this layer's
parent after the standalone owner captured the exact delta from 85737364be9.
The standalone port retains V3 validators and applies only the non-cloning
unknown-JSON repair. Migration content handling for team/message/queued and
all approved namespace, delivery and source-admission rules remain intact.

This removes an independent runtime/parser policy from the format stack;
it does not change any Session schema, codec, version or stored source file.
2026-09-19 15:43:10 +08:00
Tianyi Cui d5302e33f8 refactor(session): separate attachment carrier policy from V4 migration
The event-type allowlist and opaque-payload attachment policy change live
Session authorization/export behavior independently of a format upgrade.
They belong in a focused PR against master, where supported native V3 nested
tool-result content must also be handled deliberately.

Restore the session-controller and archive implementation, tests and README
pairs to this layer's parent. Remove the test-only session-format-catalog
dependency and its lock entry with the migration-specific attachment tests.
The original changes are captured from immutable commit 85737364be9 for the
standalone work; migration identifier conversion and reader checks are intact.

This extraction keeps the integration stack's existing attachment behavior
until the separate policy change is reviewed. It makes no Session-format,
schema or version change.
2026-09-19 15:43:10 +08:00
Tianyi Cui 11ef33b336 fix(session): reject unknown required V3 events before target interpretation
The physical V3 codec admits an unclassified event envelope without deciding
whether its type is required. The V3-to-V4 stage then used shared message
walkers and target vocabulary, so an invalid required developer/message could
acquire the V4 developer role although historical V3 restoration rejects it.
Generic required unknown events were also left to the wrong version's reader.

After preserving explicitly ignorable extensions, require every remaining
source type to belong to the fixed V3 vocabulary before interpreting payloads.
This is a fail-loud check for invalid or nonconforming source data, not new
compatibility handling for third-party events, which carry ignorable:true.
The approved plugin identifier conversion and native V4 readers are unchanged.

Exercise a complete developer-event sequence through direct migration,
historical/current catalogs, ignorable conversion and native V4 restoration;
add a generic unknown type. Both negative controls accepted the invalid source
before this guard. Plain/zstd read/write regressions verify refusal preserves
the source generation and never publishes a partial V4 successor.
2026-09-19 15:43:10 +08:00
Tianyi Cui fc74dbf4ea fix(session): limit delivery migration checks to the V4 target
A V3 delivery marker claiming generation 4 would become active when its
header advances to V4. The integration baseline already refused that case.
Renaming every higher-generation marker instead introduced a general
conversion policy for future formats that this V3-to-V4 edge does not own.

Restore the targeted generation 4 refusal. Keep other delivery event names,
payloads and coordinates unchanged, including generations 5 and 99, which
remain inactive in V4. Their future activation checks belong to a concrete
later format edge. Preserve the separate plugin-event identifier policy.

Cover read/write opens for both plain and zstd storage: target-generation
refusal preserves source bytes and publishes no successor, while later
markers survive V4 publication and native reopening unchanged. Update the
snapshot-normalizer cases and document the alpha converter coverage limit:
unsupported histories may fail without source mutation, and later converter
fixes may expand support without a new version when V4 output stays compatible.
2026-09-19 15:43:10 +08:00
Tianyi Cui 28b209867f fix(session): preserve historical stream-start extension field names
V3 and V4 declare the same block-start fields: type, index and blockType.
The migration's identifier policy can qualify an extension blockType, but
prefixing every additional field has no current V4 field collision to solve.
That rewrite changed captured keys to anticipate hypothetical future fields.

Retain every non-blockType field and value exactly, including already
prefixed names and own JSON keys. Keep the approved blockType identifier
prefix and the separate ToolSchema field policy unchanged. Update the local
conversion tests to verify both known and extension starts retain metadata
and to use the actual raw-record time field. Later field-specific conversion
belongs to a concrete source/target change instead of this edge.
2026-09-19 15:43:09 +08:00
Tianyi Cui 2833ff566e test(session): expect prefixed V3 opaque event identities on JSONL open
The JSONL retired-content admission matrix covers both historical V3 input
and native V4 input across read/write and plain/zstd storage. V3 migration
now prefixes unknown ignorable event identities with plugin:, while native
V4 reads retain their stored identity. The shared expectation still required
the unchanged V3 event tag and caused four deterministic CI failures.

Select the expected event type by input generation while retaining the full
payload, envelope, and immutable source-byte assertions. This corrects the
test for the intended migration without weakening native admission or
changing runtime behavior. The focused 20-case matrix now passes; restoring
the old expectation reproduces the same four failures locally.
2026-09-19 15:43:09 +08:00
Tianyi Cui bf29247fad fix(session): namespace historical V3 extensions with plugin prefixes
V3 permits external event, source and content identities and additional
request-tool fields. Copying those values into V4 can give an old plugin
named tool a core source identity, turn old tool-addition content into a
registry instruction, or activate formerly ignored deferLoading and delivery
metadata. Rejecting such collisions would instead refuse valid source logs.

Keep historical extensions in the existing extensible JSON representation.
Prefix unknown content and ignorable event tags with plugin:, preserving
all other content fields and opaque event payloads. Prefix external source
identities with plugin:plugin: or plugin:source: so wrapper and direct kinds
cannot collide even when original names already contain a category prefix.
Known first-party mappings remain explicit and unchanged.

Prefix extra request-tool field names and block-start field names with
plugin:, retaining exact values and existing-prefix doubling. Core schema
parameters, arguments, replay state and nested plugin data are not traversed.
Keep future-generation delivery records inactive after header promotion.
Pin the V3 event vocabulary independently of the current writer and provide
an offline verifier against an explicitly selected historical source commit.

Attachment authorization and export inspect declared event content only.
Team checkpoints preserve all own keys of unknown JSON blocks while still
rejecting malformed known content and retired core tool-result blocks.
Exercise physical read/write/reopen, prefix injectivity, own __proto__ keys,
provider opacity and retained source-identity snapshots.

No generic extension block, metadata wrapper or new schema-tooling type is
introduced. V4 type declarations, persistence schema, checkpoint and frozen
V0-V3 codecs remain unchanged. Canonical tool-result lifting and incomplete
child-catalog policy remain separate concerns.

Adapted from the extension-preservation work in 7ba7e174dd1 by Tianyi Cui.
Co-authored-by: Tianyi Cui <[email protected]>
2026-09-19 15:43:09 +08:00
Tianyi Cui cc970738d8 docs(session): scope checkpoint JSON preservation to domain reads
The cache reader preserves opaque JSON keys, but each projection still
parses the returned checkpoint through its own stateSchema before hydration.
State that ownership explicitly so documentation does not imply this reader
fix also changes projection-defined transformations or cloning behavior.

Distinguish the synthetic V7 StorageDomain regression fixture from released
cache archives. Describe its read, put and reopen path directly, and group
its JSON validation checks separately from archived-version recovery.
Translate the Chinese Agent Note headings and keep the paired docs aligned.
The note retains the borrowed-value tradeoff and links the independent
predecessor-format and lifecycle-identity decision.

Production expressions and all test assertions remain unchanged; only
contracts, fixture descriptions, test labels and grouping are clarified.
The fix does not add another persistence format or broaden migration scope.
2026-09-19 15:43:09 +08:00
Tianyi Cui df0145271d fix(session): preserve opaque checkpoint keys on domain reopen
The projection-cache writer preserves lossless JSON, including own keys named
__proto__. The z.json reader rebuilds objects and drops those keys, so opening
the same stored checkpoint can produce state different from Session replay.
This affects embedded result metadata and opaque extension payloads as well
as other projection state.

Validate with the existing isJsonValue predicate used by the writer's
snapshotJsonValue rules. It rejects non-JSON and lossy values without
rebuilding valid borrowed records. Keep the stored representation and domain
version unchanged: this is a reader correction, not a new cache format.

Exercise a fixture captured through the actual pre-fix writer with a real
StorageDomain open, rewrite, close and reopen. Preserve nested prototype-named
keys and retain negative coverage for invalid JSON values. Document why this
validation matches the storage-domain immutable borrowed-value contract.

Use neutral arbitrary JSON in the new opaque-state fixture so this reader
regression stays independent of Session message representation. Its own
prototype-named keys and nested arrays are unchanged in purpose. Confirm
the fixture matches the pre-fix StorageDomain writer output and that the
old z.json reader fails its key-preservation assertion. No released Session
recordings or historical cache fixtures are edited.
2026-09-19 15:43:09 +08:00
Tianyi Cui d7e523b8b8 perf(session): traverse stored JSON arrays by index
Cold current-generation reopen freezes the complete decoded event graph
before publishing shared-frozen history. Compact assistant streams carry
125,000 text values and 124,600 time deltas in the required benchmark;
for-in traversal enumerates their array indexes as string property keys.
An open-only profile attributes about 4.2 ms to complete event freezing.

Use numeric indexing for arrays produced by JSON decoding and the fixed
migration catalog. Those arrays contain indexed JSON values only. Keep
every Object.freeze call and every nested-object visit, preserve the
object-key path including own __proto__ and constructor data, and never
infer deep immutability from a shallow Object.isFrozen result. No decoding,
admission, relationship validation, or freezing moves past open().

The unchanged five-sample V4 reopen median falls from 22.6 to 17.7 ms on
M4 Pro / Node 24.19. Restoring and rebuilding the exact old traversal gives
21.7 ms. First-history and resume also improve; Agent retained heap stays
4.5 MB and every 128 MB case completes. Keep the existing 63 ms gate,
fixture dimensions, cold-process setup, and all other budgets unchanged.
Local measurements establish removed work; hosted CI remains the required
runner verdict.

Cover nested opaque arrays and objects, special JSON keys, rejected
mutations, and shared event identity through historical V3 and native V4
reads. Record the before/candidate/restored samples and the local-versus-CI
limit in the existing performance note.
2026-09-19 15:43:09 +08:00
Tianyi Cui 1613a10c03 merge: integrate pinned master into session-log-v4
Merge master 7e2b049d8e2523127a3b4fba38cab4748ef0c72f, observed on
2026-09-19 at 07:38:37 UTC, into release/session-log-v4 at
c4663e8671e33bc2cfdbc868b0b18d979508d39b. Keep this base integration
separate from the five persistence repair PRs so their review diffs
remain focused and can be rebased without moving their fixes here.

Combine master's pin ordering and archived-session filter with V4's
parent-owned direct child catalog. Both grouped and flat sidebar rows
must pass the Session list to the V4 activity calculation; restoring
master's summary-lineage traversal would reintroduce the removed
subagent ownership and count unrelated descendants.

Keep master's removal of the archived-session settings page. Its only
release-side test change renamed a Session list fixture field, so no
runtime behavior is lost by deleting that obsolete test. Adapt the
release-side direct-child-status regression to the new row/filter
arguments and preserve its assertions.

Retain master's workspace ordering and row-motion documentation while
keeping the V4 child-catalog wording. Reconcile the generated bilingual
records against the merged documents. Frozen V0-V3 codecs, recorded
Session JSONL inputs, and the current V4 persistence schema are unchanged.

Refs #2692, #3273
2026-09-19 15:42:11 +08:00
Yif 699d06a7eb feat(client): widen overlay top margins to the frame's top clearance
Publish --dsh-frame-top-clearance on html[data-platform='darwin'] so
body-portalled overlays inherit it; useAnchoredMaxHeight and the portalled
Menu widen their 12px viewport margin to it, Modal centralizes the vertical
padding its cards previously each computed, and the trigger menu keeps an
84px margin on every platform so it stops below the conversation header.
Reduce Transparency replaces the sidebar tint with a 90%-opaque fill.
2026-09-19 15:37:47 +08:00
imccyu 45417197c6 refactor(boot): name routes and collectors after the interception layer
Resolution routes are native, generation, and native-after-generation: Node keeps the importer's lookup, answers from the generation entry at the interception layer, or resumes its chain at that layer. The profile module collects installation-scope and profile-scope packages and reads package manifests under those names; the generation types describe the interception layer instead of a fallback table.
2026-09-19 15:34:09 +08:00
imccyu f34f8ef04d docs(boot): name the native lookup after the generation in the generation note 2026-09-19 15:34:09 +08:00
imccyu 82337a2e71 test(boot): canonicalize temporary roots with the native realpath
Windows tmpdir spells the user directory with an 8.3 short name. Generation anchors and Node's own resolution report the long name, so the resolver, generation, and real-launch fixtures now create their roots through realpathSync.native and compute expected paths the same way.
2026-09-19 15:19:32 +08:00
Yichen Jiang 8a320a276b feat(settings-models): one add entry with third-party and custom-API modes
The Models page offered two side-by-side buttons, "Add provider" and
"Add a custom provider", which asked a new user to classify their
provider by an implementation fact — whether it is in the installed
pi-ai catalog — before seeing anything. One "Add model provider" button
now opens one card whose segmented mode switch chooses between a
third-party model provider (the catalog select over the provider
editor) and a custom model API (the create form for a relay, a
self-hosted server, or any other OpenAI- or Anthropic-compatible
endpoint), with a one-line purpose beneath the switch. A panel mounts
the first time its mode is shown and stays mounted, hidden, while the
card is open, so switching modes discards neither draft.

A mode is offered only while its namespace is mounted and disabled
while it has nothing to offer, which keeps the old per-button
availability; with one mode the card shows that form alone. The
protocol pickers name each protocol by its product name while the
stored value stays the schema identifier, and an unnamed protocol shows
that identifier. The Chinese copy of this page calls a provider 提供商
throughout.

The e2e scenarios drive the single entry and its custom-API mode, and
the ARIA goldens that carry the page copy are re-recorded. The user
guide describes the one entry with fresh screenshots in both languages.
2026-09-19 15:14:52 +08:00
Yichen Jiang 6840878bf2 feat(ui-primitives): add SegmentedControl
A tablist of two or more equal-width segments with one sliding
indicator, for switching a card or panel between a few mutually
exclusive modes. The owner holds the selection; `label` names the list
and every segment label is owner-localized. Arrow keys, Home, and End
walk the enabled segments with a roving tabindex, and the keyboard
follows the selection once the owner adopts it.

Segments are equal grid tracks sized to the widest label when the
control hugs its content, which is what lets the indicator be placed
arithmetically from the segment count and selected index rather than by
measuring the DOM. The indicator draws its stroke inside the soft
elevation shadow, as every elevated surface does.
2026-09-19 15:14:37 +08:00
imccyu 03e3a3bca0 refactor(desktop): note the runtime descriptor validation in applyRelease
applyRelease keeps readDesktopRuntime for its fail-loud validation after the cleanup consumer was removed.
2026-09-19 15:06:26 +08:00
imccyu 1c806e5a16 docs(desktop): record the profile cleanup removal
A simplification note records why production Desktop no longer cleans core-package copies out of the profile, keeps the 2026-09-15 motivation and rejected alternatives, and names the capability given up and the reintroduction conditions; the superseded 2026-09-15 note is consolidated into it. The bundled-runtime note drops its cleanup qualification paragraph, and the Desktop README describes launch preparation without the cleanup step.
2026-09-19 15:06:26 +08:00
Tianyi Cui fe46abc02f Merge pull request #4594 from deepseek-harness/worktree/guard-ci-bubblewrap-pin-20260919
test(ci): reject prunable Ubuntu bubblewrap URLs
2026-09-19 15:03:37 +08:00
imccyu 71c7f6cc09 refactor(boot): name the interception layer parent and note intentional anchors
routeScoped names the profile parent whose node_modules is the interception layer. The installation traversal records why the installation root keeps its caller-supplied directory and why generation construction keeps its Promise return type.
2026-09-19 14:39:45 +08:00
imccyu 0e2097ac18 docs(boot): describe the interception layer in the generation note and README
The generation note and the app-boot README no longer claim that runtime lookup hides the shared position or recognizes profile projections: generation entries occupy their package names at $DSH_HOME/profiles/node_modules and every other name sees that directory as an ordinary ancestor. Pairing records re-recorded.
2026-09-19 14:39:44 +08:00
Huanqi Cao 08217140b8 Merge remote-tracking branch 'origin/master' into fix/windows-acl-delete-escape 2026-09-19 14:31:47 +08:00
imccyu 81b6934804 docs(boot): add the English lookup-order note and refresh the wrapper note
Translate the profile lookup-order Agent Note section for section and record its pairing. The desktop-web-wrapper note names the runtime resolution generation instead of module fallbacks and drops the one-time desktop-runtime-state cleanup that no longer exists.
2026-09-19 14:28:07 +08:00
Huanqi Cao 7201569aef docs(config-catalog): carry the source-line refresh to the zh pair
The generated catalog is a bilingual pair, so the regenerated English side
must be brought over with its Chinese counterpart before the pairing record
is re-written.
2026-09-19 14:17:59 +08:00
imccyu d71b8a2f1c refactor(boot): narrow legacy-links exports and refresh resolution JSDoc
isPackagedExecutable has one caller in its own module; the installation traversal comment names the runtime resolver instead of the removed fallback directory.
2026-09-19 14:12:02 +08:00
Yif a80f5186a6 Merge remote-tracking branch 'origin/master' into feat/macos-sidebar-ui-and-drag-refine 2026-09-19 14:05:21 +08:00
Huanqi Cao 85724fb5b0 docs(config-catalog): refresh the sandbox-local Config source line
The comment edit in sandbox-local/src/index.ts moved the Config block from
line 44 to 45, and the generated catalog records that line.
2026-09-19 14:05:18 +08:00
Huanqi Cao 4753e45f95 test(sandbox): probe cmd's own > NUL redirection
The CI Windows runner reports an empty \0 for a PowerShell-level
'>nul' redirection around the cmd call, and PowerShell's '>nul' opens a file
named nul rather than exercising cmd's redirection. Pass the whole command
to cmd so it parses '>NUL' itself and prove it with cmd's own output.
2026-09-19 13:54:48 +08:00
Yif de74a45f60 feat(desktop): raise the main window minimum width to 520 2026-09-19 13:42:52 +08:00
Yif 3f7a209835 revert(ui-primitives): drop the tooltip pointer-focus suppression 2026-09-19 13:42:50 +08:00
Huanqi Cao 3d5ba3b83f fix(sandbox): scope the delete deny to containers, clearing the review round
The inheritable `Deny Everyone FILE_DELETE_CHILD` also reached FILES, and
0x40 is a member of FILE_ALL_ACCESS: measured against a real runner, a
Medium caller's CreateFileW(GENERIC_ALL) on every file, directory, and temp
root inside a granted root returned ACCESS_DENIED. The deny now carries
CONTAINER_INHERIT_ACE, so files keep working; a FullControl open of a
DIRECTORY inside a granted root stays denied and is pinned as the residual
cost of denying a right the full-access mask contains.

Also from the review:

- Leaks on fail-closed paths: the label ACL on both early SetEntriesInAclW
  exits, the read descriptor when the label build fails, and the parsed
  capability SID when either well-known-SID creation fails.
- `revokeWrite` keeps the shared Low label while another capability grant
  remains on the directory, so two grants on one path stay independently
  revocable; `hasExactLabel` delegates to hasExactEntry.
- The delete probe uses process.execPath, restrictTokenIntegrity guards a
  zero-length SID, and verify/abi-probe.cpp covers the new constants
  (compiled; static_asserts pass).
- Consumers updated in the same change: sandbox-local source comments and
  README pair, pwsh-sandbox README pair, docs/subsystems/sandbox pair, and
  the 2026-07-06 sandbox note pair no longer cite the Everyone-write
  boundary as a current partial reason.
- New Agent Note records the mandatory-integrity decision, the deny scoping,
  the standing label with its externality (other Low-integrity processes
  running as the user, surviving the session), and the rejected
  alternatives; the 2026-08-08 note cross-links it.
- New runner pins: NUL stays writable under the Low token, the FullControl
  file/directory split, and the shared-label revoke. Per-file coverage back
  to 100%.
2026-09-19 13:42:47 +08:00
Yichen Jiang ff3d162401 Merge pull request #4600 from deepseek-harness/worktree/fix-markdown-preview-images
fix(web): render local images in Markdown previews
2026-09-19 13:31:42 +08:00
Yif c8e3cb2b87 test(web): track breadcrumb text crumb and shell.leading seat in e2e and goldens 2026-09-19 13:31:41 +08:00
imccyu 938b62c0aa refactor(desktop): drop link-era profile cleanup
Remove cleanProfileCorePackages and migrateDesktopProfileLinks with their specs; applyRelease keeps runtime validation, settings migration, and profile creation. A regression pins that installed packages, declarations, legacy state, and the lockfile survive a launch.
2026-09-19 13:29:13 +08:00
imccyu a311386983 test(cli): cover shared-layer packages in real profile launches
The src/lib x installed/npm-link fixture adds a stale @deepseek-ai/dsh-tools link in profiles/node_modules, a plugin dependency absent from the generation with nested external leaves, and byte-for-byte preservation of profile files and link targets.
2026-09-19 13:29:13 +08:00
imccyu 6f57030958 fix(boot): occupy the interception layer instead of hiding it
The generation stands in for $DSH_HOME/profiles/node_modules/<name> only for names it carries; other names see that directory as an ordinary ancestor. The interception layer is the first ancestor node_modules above the importer's profile directory, so application-owned profiles follow the same rule. Stale .dsh-module-fallback projections are ordinary symlinks: their shape recognition leaves the resolver, the local package names, and the bundle dependency traversal.

The resolver spec gains a table-driven matrix over importer, package kind, ancestor layers, and directory or symlink form, with readable fixture names.
2026-09-19 13:29:13 +08:00
imccyu db831733da docs(boot): record the profile lookup order and interception layer
Add the Chinese Agent Note describing profile resolution as Node's ancestor node_modules chain with the generation occupying the package directories at $DSH_HOME/profiles/node_modules, the ESM/CommonJS, web/desktop, and npm-link scenarios, and the developer onboarding guide.
2026-09-19 13:29:13 +08:00
imccyu 518c7a604b fix(boot): preserve runtime fallback precedence 2026-09-19 13:29:13 +08:00
imccyu fcc6cc8c12 docs(boot): align runtime-only resolution contracts 2026-09-19 13:29:13 +08:00
imccyu 0bac274273 test(cli): run source tool smoke after build 2026-09-19 13:29:13 +08:00
imccyu 4b79c744ab docs(cli): clarify source smoke build prerequisites 2026-09-19 13:29:13 +08:00
imccyu 6d8aac066d fix(boot): resolve profile dependencies from real package directories 2026-09-19 13:29:13 +08:00
imccyu 4b9141a89e docs(boot): document runtime-only profile resolution 2026-09-19 13:29:13 +08:00
imccyu 9fd0a5ad52 refactor(boot): keep runtime-only profile resolution 2026-09-19 13:29:13 +08:00
Yif 8f2f13bc48 Merge remote-tracking branch 'origin/master' into feat/macos-sidebar-ui-and-drag-refine 2026-09-19 13:26:16 +08:00
imccyu bd2247c510 Merge pull request #4505 from deepseek-harness/feat/session-pin-archive
feat(workspace): 会话置顶与侧边栏归档筛选
2026-09-19 13:25:57 +08:00
Yichen Jiang 7951cee0d5 fix(web): render local images in Markdown previews 2026-09-19 13:21:37 +08:00
Huanqi Cao 688d7b694b test(sandbox): drop the host-dependent whoami integrity assertion
whoami /groups reports the mandatory label on this workstation but produces
no matching line on the CI Windows runner, so the assertion pinned the host
rather than the product. The lowered integrity level stays covered by the
unit pin on restrictTokenIntegrity and by the behavior that only holds with
it: the Everyone-Modify denial, the outside-delete denial, and the
cross-root delete denial. README records the cmdlet as host-dependent
diagnostic noise again.
2026-09-19 13:05:01 +08:00
imccyu 7e4045c4c0 test(client): align sidebar interactions and browser snapshots 2026-09-19 12:54:14 +08:00
imccyu 7baccf5d85 fix(workspace): unify pin IDs and preserve complete session order 2026-09-19 12:54:13 +08:00
imccyu f1e06a69ef fix(ui-primitives): preserve toast lifetime across rerenders 2026-09-19 12:54:13 +08:00
imccyu 842bf71350 perf(workspace): replace auto-animate with scoped row motion 2026-09-19 12:53:24 +08:00
Yichen Jiang 320e961122 test(jobs): refresh background tool snapshot headers 2026-09-19 12:22:46 +08:00
Tianyi CuiandLegGasai 766e6f9176 test(ci): reject prunable Ubuntu bubblewrap URLs
The merged dependency repair in #4589 uses a recorded Launchpad build,
but #4591 also requires a regression guard against restoring the live
Ubuntu package pool. Those pool URLs disappear when versions are
superseded, even when the content digest is pinned.

Adapt LegGasai's URL guard from closed PR #4590 to the build-specific
endpoint that landed in #4589. Check the endpoint structure without
copying the current build number, version, or checksum into the test,
and reject Ubuntu pool downloads anywhere in the script so adding a
pool fallback cannot bypass the guard.

Negative controls restore the original archive URL and inject security
and ports pool fallback URLs; each fails the new guard. The original
script is restored byte-for-byte before all 44 workflow tests pass.
This adds only a static regression test, with no network calls or
changes to the download script or CI execution behavior.

Co-authored-by: LegGasai <[email protected]>
2026-09-19 12:08:11 +08:00
Yif 34d78d843d feat(workspace): restore the hover title reveal with an 8px jitter floor
Hovering a session row again scrolls a clipped title to its far edge
(the #4423 reveal), but overflow of at most 8px stays put so barely
clipped titles do not jitter. The hover mask fade yields back to plain
clipping — at the scrolled extent the fade would cover the revealed
tail. Row action buttons tighten from gap 12 to 10.
2026-09-19 12:04:07 +08:00
Yif 1d221f9723 fix(workspace): end-aligned action tooltips, slower hover cards, archived-row alignment 2026-09-19 12:04:07 +08:00
Yif 9ca19474ab fix(workspace): tooltip the row hover actions and open the sliders glyph 2026-09-19 12:04:07 +08:00
Yif 94c62ea564 fix(client): draw the sidebar new-chat glyph at the regular weight 2026-09-19 12:04:07 +08:00
Yif 5474efe890 fix(client): lighten the ellipsis glyph and adopt the Toast success tone 2026-09-19 12:04:07 +08:00
Yif bd0461c894 fix(workspace): give the compact-label row spec the pin/archive props 2026-09-19 12:04:07 +08:00
Yif 3e96ed79a7 fix(workspace): steady hovered session titles and polish the sidebar controls
Hovering a session row no longer scrolls its clipped title (the mask fade
stays), the one-time archived-filter callout and the content-search
unavailable notice are gone, the group-row new-session button shares the
new-chat glyph, and the view-options trigger uses the two-slider icon with
a 200px menu floor and a visible separator hairline.
2026-09-19 12:04:07 +08:00
Yif 33d496951c feat(workspace): session pinning and the sidebar archived filter
Pin sessions to the top of the sidebar and reach archived sessions
through the view-options filter instead of a settings page; archiving
gains an undo/filter toast, and row motion stays reserved for pin jumps
and archive fades.
2026-09-19 12:04:07 +08:00
Tianyi Cui df65ea84ee Merge pinned master 386ed6b6 into session-log-v4
Include the user's merge of #4589 so Linux CI uses the available, verified
Ubuntu bubblewrap 0.12.0 build instead of the pruned 0.9.0 archive URL.
The user explicitly authorized this CI-prerequisite refresh without waiting
for the ordinary hourly master-following interval.

Preserve the reviewed upstream commit and exact one-file patch. The recorded
Launchpad build URL, checksum, extraction and namespace probe match #4589,
whose supported Ubuntu 24.04 preparation and execution checks passed. Bash
syntax and merge whitespace checks pass; no product runtime, Session format
or snapshot generation changes are introduced by this merge.
2026-09-19 12:01:04 +08:00
Yichen Jiang 37b31ba0d4 Merge branch 'worktree/jobs-api-review-3052' into worktree/jobs-api-review-3127 2026-09-19 11:58:57 +08:00
Yichen Jiang 8eb96113c7 Merge remote-tracking branch 'origin/master' into worktree/jobs-api-review-3052 2026-09-19 11:58:01 +08:00
Tianyi Cui c167f6d3e0 Merge pull request #4589 from deepseek-harness/worktree/fix-ci-bubblewrap-012-20260919
fix(ci): pin a compatible bubblewrap 0.12.0 build
2026-09-19 11:55:07 +08:00
Tianyi Cui 32ec78c79e fix(ci): pin a compatible bubblewrap 0.12.0 build
Ubuntu removed the superseded 0.9.0-1ubuntu0.1 payload from its live
archive pool, so the shared Linux CI preparation step fails with HTTP
404 before tests start. Address Ubuntu's recorded Launchpad build so
future pool pruning does not invalidate the pinned download location.

Select upstream's 0.12.0 security release for CVE-2026-87766 instead of
Ubuntu's isolated 0.9.0 backport, whose regressions and support limits
are documented by upstream in Launchpad bug 2166359. Keep checksum
verification, direct extraction, and the existing namespace probe.

Verify build 33546835 against its published SHA256 and archive SHA512.
Its amd64 ELF requires at most GLIBC_2.38 and x86-64 baseline; its
packaging requires Linux 5.10. Ubuntu 24.04 provides glibc 2.39, Linux
6.8, and the required libcap2/libselinux1 versions. This changes only
the CI helper's pinned payload, not product sandbox configuration.
2026-09-19 11:39:05 +08:00
Tianyi Cui 91212afc00 Merge pinned master e83fbddc into session-log-v4
Include the user's merge of #4586 so the V4 integration and its child PR
exercise the corrected spill-policy synchronization tests. Preserve the
upstream commit and authorship and keep this master checkpoint separate from
migration changes and the remaining benchmark investigation.

The merge changes only the spill-policy test file. Its added and removed
lines match the reviewed upstream fix exactly, while the release branch's
existing V4 fixture adaptations remain intact. No runtime source, persisted
format, or snapshot generation changes.

Validation: all 31 spill-policy owner tests pass with 100% package coverage.
2026-09-19 11:30:48 +08:00
Tianyi Cui f863b47a48 Merge pull request #4586 from deepseek-harness/worktree/fix-spill-policy-gates-20260919
test(spill-policy): synchronize slow-backend regressions on events
2026-09-19 11:23:10 +08:00
Yichen Jiang 3bcac0b878 test(web): expect three startup plugin batches in the keyless smoke
The application phase's map-form combo URL passes the 3 KiB limit once
the four settings companions join the composition, so the module system
partitions that phase into two combos beside the bootstrap one, as the
loading model allows. The smoke pins the three immutable batches and
still checks every combo's script, map, and cache header.
2026-09-19 10:46:51 +08:00
Yichen Jiang 08c3189c3c Merge branch 'worktree/jobs-api-review-3052' into worktree/jobs-api-review-3127
# Conflicts:
#	apps/web/tests/snapshots/live-job-stream/settled.expected.md
#	apps/web/tests/snapshots/live-job-stream/streaming.expected.md
#	snapshots/web/background-job-list/running.expected.md
2026-09-19 10:43:19 +08:00
Yichen Jiang 9f3ddc1a19 test(web): re-record the bundle golden after the detail-page chrome change
The crumb, uninstall, and configure controls no longer render an icon child in the accessibility tree; the bundle page golden this branch added follows the row and official goldens master re-recorded.
2026-09-19 10:41:31 +08:00
Yichen Jiang c4bc8b847a Merge branch 'worktree/jobs-api-review-3052' into worktree/jobs-api-review-3127
# Conflicts:
#	packages/client/ui-jobs/src/client/JobListAction.tsx
#	packages/shell/bash-local/tests/executor.spec.ts
2026-09-19 10:40:27 +08:00
Yichen Jiang 629c53d5a0 test(jobs): refresh snapshots for decorative icon accessibility 2026-09-19 10:40:25 +08:00
Yichen Jiang 30b882a1b2 fix(jobs): keep client detail tests on the pure renderer 2026-09-19 10:37:48 +08:00
Yichen Jiang 3135e4d612 Merge remote-tracking branch 'origin/master' into feat/plugin-detail-slots
# Conflicts:
#	apps/web/tests/expected/plugin-config/row.expected.md
#	packages/client/ui-plugin-manager/src/client/PluginManagerPage.tsx
#	packages/client/ui-primitives/src/index.ts
#	packages/client/ui-primitives/src/settings-form/fields.tsx
#	packages/client/ui-settings-plugins/package.json
#	packages/client/ui-settings-plugins/src/client/locales.ts
2026-09-19 10:36:07 +08:00
Yichen Jiang 70cacfdcaa test(shell): cover cancellation before reading failed launch results 2026-09-19 10:35:48 +08:00
Yichen Jiang 5c10701df2 Merge remote-tracking branch 'origin/master' into worktree/jobs-api-review-3052
# Conflicts:
#	apps/web/tests/expected/agent-preset-selection/header.expected.md
#	packages/client/ui-chat/src/client/chat/ContextInjectionRow.tsx
#	packages/client/ui-jobs/package.json
#	packages/client/ui-jobs/src/client/JobListAction.module.css
#	packages/client/ui-jobs/src/client/JobListAction.tsx
#	packages/client/ui-jobs/tests/job-list-action.client.spec.tsx
#	packages/client/ui-subagent/README.i18n.yaml
#	packages/client/ui-subagent/README.md
#	packages/client/ui-subagent/README.zh.md
#	packages/client/ui-subagent/src/client/SubagentHeaderLineage.tsx
2026-09-19 10:35:09 +08:00
Tianyi Cui 9f34db2373 test(spill-policy): await observed spill and dispatch stages
The slow-backend regressions start real NodeRuntime subprocesses, but
vi.waitFor silently imposed a one-second deadline on normal scheduling and
tool execution. Under concurrent Linux and Windows coverage this rejected
valid runs before the tested spill or dispatch stage was reached.

Resolve promises at the actual save-entry and dispatch-start observations,
and fail immediately if the run completes before those observations. Queue
all three requests up front in the bounded-backlog case so a missing third
start tests backpressure rather than child request timing. Cleanup releases
both queued and future spill gates and awaits execution completion.

Keep the existing outer test deadlines, product limits, result assertions,
and ordering checks. A temporary 1.2-second tool delay reproduces both old
failures and passes with these tests; temporarily removing the actual
backpressure wait still fails the third-dispatch assertion. No experiment
or product-code change is included.

Fixes #4585
2026-09-19 03:21:10 +08:00
Tianyi Cui 87f0df0550 Merge pinned master 5ec3ffe337 into session-log-v4
Keep the master refresh separate from the V3-to-V4 lossless-conversion and
checkpoint JSON fixes so the release baseline and each feature change remain
independently reviewable. This checkpoint extends the already-published
4435f6f519 merge before the #4583 child is rebased.

Integrate exactly master 5ec3ffe337062ab5bb4955933008afe8d0a0b915. Its #4156
changes add watched file/document refresh, filesystem watch readiness,
directory expansion restoration, and chat bottom-follow fixes. They update
shared filesystem and Client consumers without changing Session format or
snapshot generation.

The merge has no conflicts. Its 114-file scope and line counts match upstream;
code and prose additions/removals are unchanged. Only the two translation
pairing records incorporate the release branch's retained V4 documentation.

Validation: 762 owner tests passed (1 platform skip), 11 focused Web tests
passed, full build passed, and all 41 doc-sync checks passed. Runtime closure
and Client package checks passed; fs-local publint completed with the existing
source-wildcard export warning.
2026-09-19 02:39:04 +08:00
imccyu 29598c7ce9 Merge pull request #4156 from deepseek-harness/feat/issue-3930-preview-refresh
feat(web): refresh previews after turns and native file changes
2026-09-19 02:26:26 +08:00
Huanqi Cao 7450e1eb24 docs(sandbox): tighten the comments added by the delete-confinement fix
One home per fact: the parent-FILE_DELETE_CHILD rationale now lives in the
acl module doc and the package README instead of being retold in
restrictTokenIntegrity, init(), and grantWrite. Drops the duplicated
inline narration; behavior unchanged.
2026-09-19 02:25:01 +08:00
Huanqi Cao 36e632751e fix(sandbox): deny the ambient parent-delete right inside granted roots
The Low integrity label confines writes and deletes to the labeled roots,
but every granted root is labeled Low, so the integrity check alone still
let one confined child delete inside ANOTHER granted root (another
workspace, a live sibling temp directory): Windows authorizes a delete from
the parent directory's FILE_DELETE_CHILD right, which the write-restricted
intersection never reaches.

Each grant now also denies FILE_DELETE_CHILD to the world SID in the same
SetNamedSecurityInfoW call, so the capability ACE's DELETE bit is the only
delete authority inside a granted root. The idempotent skip additionally
requires the exact deny, so a root granted by an earlier build receives it
on its next provision. Delete paths inside the roots keep working; a file
whose own DACL grants no DELETE is no longer deletable through its parent's
rights, for the confined child and for the user's own processes alike.
2026-09-19 02:06:30 +08:00
Huanqi Cao d5ad3baeb5 fix(sandbox): confine Windows deletes with a Low integrity label
The write-restricted token's capability-SID intersection only covers the
write bits of the object's own access check. Windows also authorizes a
write or a delete from the parent directory's FILE_DELETE_CHILD right, and
no restricting SID has to co-sign that authority: the confined child could
therefore delete any file its ambient user SIDs control. `cmd /c del`
outside the workspace succeeded under BOTH modes, and .NET
File.Delete/DeleteFileW did too wherever the ambient DACL held Modify.

Lower the restricted token to Low integrity and apply a Low no-write-up
mandatory label to every granted directory in the same SetNamedSecurityInfoW
call that adds its capability ACE. The kernel evaluates the integrity policy
inside the access check whichever right supplied the authority, so the
granted roots stay writable while everything else becomes delete-proof as
well as write-proof. The label inherits to new children and follows the
existing standing/revocable grant lifecycle.

Also closes the Everyone-Modify ambient write boundary, and pins the delete
paths (cmd, .NET, Remove-Item, libuv) in runner.spec.ts.
2026-09-19 01:38:29 +08:00
imccyu 5c0b43f16e test(web): include connection diagnostics in SVG failures 2026-09-19 01:16:12 +08:00
imccyu 072ea695b9 fix(chat): retain bottom follow during stream updates 2026-09-19 01:16:12 +08:00
Tianyi Cui a7a8fa3c16 Merge pinned master into session log v4
Bring the Browser guide subtitle simplification from master
b605980eb049ce2cc578cdb608f7f8dee66f7b56 onto release checkpoint
2958f0f5cf67f81b18ad690a82c68c606f246d0c.

Keep this upstream synchronization in its own merge commit so the release
refresh can be reviewed independently from the lossless tool-content
conversion. The conflict-free merge contains the exact two-file upstream
patch: shorter English and Chinese guide subtitles and matching assertions.

Validation: all three ui-sidebar-browser apply tests pass, scoped type-aware
lint passes, the Client UI locale gate passes, and the first-parent patch
matches the pinned master delta from 0e9385ad8b in both full-diff and stable
patch-id comparisons.
2026-09-19 00:54:54 +08:00
Turtle 2099andTurtle 29d96fa538 fix(web): simplify Browser guide subtitle (#4561)
Co-authored-by: Turtle <[email protected]>
2026-09-19 00:39:22 +08:00
imccyu 7e09291f15 fix(sidebar): preserve expansion during directory restoration 2026-09-19 00:10:11 +08:00
Tianyi Cui bd5b1cd44f test(session): keep persistence fixture inventory keys portable
The accepted-baseline tests inventory temporary files with keys produced by
the host path.join. On Windows those keys contain backslashes, but the status
lookup and acknowledgement-preservation filters use repository-style slash
paths. Windows coverage therefore reads undefined for the status assertion;
the filtered preservation loops also select no files on that platform.

Use posix.join for logical inventory keys while keeping native path.join for
filesystem access. All platforms now exercise the same status lookup and
preservation assertions. This is confined to the test helper and changes no
production admission rule, compatibility policy, schema, or recorded fixture.
2026-09-19 00:07:18 +08:00
Tianyi Cui e134ba8765 fix(session): format unvalidated row fields explicitly in diagnostics
The retired-content guard runs on parsed physical JSON before normal event
admission so recovery cannot suppress a retired wrapper after another invalid
row. At that point type and sequence fields are still JSON values rather than
validated event fields. Implicit interpolation therefore violates the typed
lint rule on the final combined branch.

Use explicit String conversions only for the error subject. Keep the guard's
ordering and refusal checks unchanged; requiring an already-valid envelope
here would weaken the pre-recovery check. This does not change the stored
representation or add a migration rule.
2026-09-18 23:51:53 +08:00
Tianyi Cui 5152c097bb docs(session): catalog every V3-to-V4 transformation and refusal
The V3-to-V4 README described the main representation changes but did not
enumerate the complete admission and preservation rules. That left reviewers
without a package-owned reference for supported histories, child evidence,
native validation, or cases that must fail without publishing a successor.

Document historical conversion separately from native V4 admission, including
source renames and collisions, first-class tool results, child catalogs,
delivery metadata, retained identities and references, lifecycle checks,
developer-message constraints, fork behavior and recovery entry points.
Explicitly identify the retired-wrapper refusal and the limited scope of
general content validation rather than implying a complete payload validator.

Require this transformation/admission catalog in the version-addition
cookbook so future adjacent migrations document each supported mapping,
unchanged representation, refusal and preservation obligation. Preserve the
accepted-baseline policy: compatible V4 additions use new acknowledgements;
only breaking changes require a new writer version.
2026-09-18 23:49:21 +08:00
Tianyi Cui cbb4df8750 fix(session): refuse retired tool-result content during V4 admission
Released V3 accepts tool-result wrappers in ordinary user content. Leaving
those wrappers in a V4 artifact violates its declared content types and
reaches provider serializers that cannot represent them. Converting such
rows would require a new event, role, or source ownership decision.

Reject retired result tags only in known interpreted content slots and
raw Assistant stream block starts/ends. Reuse the native retired-syntax
admission path before recoverable suffix suppression and target artifact
restoration before successor publication. Preserve the existing system
and tool-result validators and the canonical V3 tool/result wrapper lift.

Known ignorable developer rows receive the same check during encoding;
unaware physical readers still defer their opaque developer payloads.
Tool arguments, replay state, schema parameters, nested metadata, and
unknown ignorable event payloads are not recursively scanned.

Real JSONL regressions cover V3 migration and native V4 read/write in
plain and Zstandard storage, including a corrupt native prefix. Refusals
preserve source bytes and the sole generation; opaque payloads survive.
No declared schema, frozen V0-V3 codec, or snapshot generation changes.
2026-09-18 23:49:20 +08:00
imccyu a77250603c fix(fs): watch parent before acknowledging missing files 2026-09-18 23:45:02 +08:00
Tianyi Cui 0112eaf3a8 feat(session): finalize the accepted V4 compatibility baseline
The integrated V3-to-V4 records contain the header increase that authorizes
their breaking changes. Leaving those terminal records editable would let a
later change reuse that already accepted 3-to-4 increase, so version 4 would
not establish a stable compatibility baseline for persisted user data.

Capture the accepted root classifications and semantic record digests in a
separate finalization checkpoint. Validate that checkpoint before rendering
or writing acknowledgement artifacts, and preserve its accepted records even
after the current writer advances. Keep publication evidence independent:
the verified released version remains 3 until a release establishes otherwise.

Compare future writer-4 schemas with the accepted baseline using the existing
compatibility classifier. Compatible additions remain permitted through new
same-version acknowledgement records. Breaking changes require a higher
writer and their own header transition; comments, aliases and source-location
changes do not alter the accepted semantics. Later unaccepted compatible
records retain the existing update workflow.

Document the distinction between finalization, publication and compatible
evolution, with regression coverage for locked records, incomplete or
inconsistent checkpoints, additive changes and future-version transitions.
2026-09-18 23:40:56 +08:00
imccyu 5b13cba35c test(workspace-files): cover initially missing watch targets 2026-09-18 23:27:30 +08:00
imccyu 4f55590aea fix(sidebar): correct automatic refresh and watch lifecycles 2026-09-18 23:27:30 +08:00
imccyu 9493ed6f61 test(sidebar): cover automatic refresh and watch lifecycle 2026-09-18 23:27:29 +08:00
imccyu 19d1f87492 fix(sidebar): satisfy lint and documentation checks 2026-09-18 23:27:29 +08:00
imccyu cf9213ca5f fix(sidebar): finalize resource auto-refresh behavior and tests 2026-09-18 23:27:28 +08:00
imccyu c71e907490 feat(web): add watched sidebar resource refresh 2026-09-18 23:27:28 +08:00
Yif b46af4d288 Merge remote-tracking branch 'origin/master' into feat/macos-sidebar-ui-and-drag-refine 2026-09-18 23:05:35 +08:00
Yif 1cfb776604 fix(ci): re-record slots pairing, refresh breadcrumb golden, cover null-panel nudge branch
The master merge invalidated docs/subsystems/slots.md's recorded pairing hashes; the minimal-preset UI golden still expected the breadcrumb as a disabled button, which darwin drag-band avoidance intentionally turned into plain text; and the app-region nudge effect's unattached-ref return had no darwin first-render test.
2026-09-18 23:05:24 +08:00
Tianyi Cui 498d0e8ecd merge: refresh session-log-v4 from pinned master
Merge master 0e9385ad8b9e75146f4baec2e731dd961f11c87c, observed when
this final integration step started, into the merged V4 release branch.
Keep the complete upstream history and do not advance to later master
commits during finalization.

Bring the Web trust-domain and relative-route handling, plugin install
cancellation, and npm registry mirror configuration into the common
release baseline. Retain the V4 codecs, producer-source policy,
parent-owned catalogs, and fork/migration behavior from the merged stack.

The merge needs no manual conflict resolution and changes no Session
JSONL inputs, snapshot generations, format packages, or persistence
schema/history files. This checkpoint precedes the separate V4 freeze
and audit fixes so each change keeps its own rationale and validation.
2026-09-18 22:35:14 +08:00
mektpoy 135104fb10 docs(browser-use): record the profile plugin instance-sharing decision
Adds the implemented Agent Note for the rule issue #4573 exposed: an
out-of-tree plugin declares every host runtime whose cross-instance identity
matters as a peer (plus dev), never as a dependency.

Records the mechanism (dsh-scope mints its tag per module instance), what the
duplicate does to a per-Agent registration, the alternatives that lost
(mounting on agent.ctx, a shared Symbol.for tag, version alignment), and the
open coverage gap: verify-package-dependencies selects no packages/experimental
package, so tests/shared-host-runtimes.spec.ts is the only pin.
2026-09-18 22:22:07 +08:00
fz 87cd0d8eb9 fix(skill): validate workspace dependency entry types 2026-09-18 22:19:37 +08:00
Yif d1e7bd0901 fix(client): keep the drag-gate fixture path outside packages/ so verify-package-paths resolves 2026-09-18 22:18:52 +08:00
imccyu 94607e48d6 Merge pull request #4578 from deepseek-harness/revert-4565-ihsiang/work-details-toggle
Revert "feat(web): add work details presentation modes"
2026-09-18 22:12:19 +08:00
imccyu 50460879db Revert "feat(web): add work details presentation modes" 2026-09-18 22:11:53 +08:00
涂轶翔 60f2b1a2c9 Merge pull request #4429 from deepseek-harness/feat/developer-session-changes
feat(session): add developer changes using historical tool schemas
2026-09-18 22:11:29 +08:00
ihsiang 68a684d9c0 Merge pull request #4565 from deepseek-harness/ihsiang/work-details-toggle
feat(web): add work details presentation modes
2026-09-18 22:04:42 +08:00
Yif 6f77b92121 docs(notes): reconcile shell.leading note with the sessionless conversation.header.leading seat
Master's sessionless-sidebar work renamed the conversation leading seat to
root-scoped conversation.header.leading instead of deleting it; the note now
states both seats' current roles rather than claiming outright removal.
2026-09-18 22:01:05 +08:00
Yif 64556a1285 fix(client): restore fullscreen panel drag subtraction, correct seat mount-condition docs
The fixed fullscreen right panel sits over the frame's leading band and the
sidebar logo row's drag rects, so without a whole-box no-drag its
non-interactive content dragged the window; restore the subtraction (darwin).
The shell.leading note and AppFrame comment now state the real mount
condition (darwin && sidebarCollapsed) instead of the platform-agnostic
collapsedWidth === 0 claim, which Windows' caption collapse also satisfies.
2026-09-18 21:51:01 +08:00
yixiangihsiang daa2387546 fix(ui-chat): derive trigger titles from durable sources 2026-09-18 21:47:37 +08:00
fz bffd23d3bc Merge remote-tracking branch 'origin/master' into feat/workspace-dependencies-package 2026-09-18 21:45:56 +08:00
fz 44e4750673 test(sdk): match Office startup wait to conversion deadline 2026-09-18 21:45:15 +08:00
Yif d94aef5214 Merge remote-tracking branch 'origin/master' into feat/macos-sidebar-ui-and-drag-refine 2026-09-18 21:42:09 +08:00
Yif 50f7bc0bc6 fix(client): declare pointerup/pointercancel on Tooltip AnchorProps 2026-09-18 21:41:57 +08:00
Yichen Yan 2708df4a9c Merge pull request #4203 from deepseek-harness/yyc/web-relative-routes-reviewed
fix(web): resolve feature routes from the document directory and gate browser routes
2026-09-18 21:28:35 +08:00
Yif 5467209f8e fix(client): settle tooltip pointer flag, harden drag gate, scope shared-curve doc
Clear pointerFocus on pointerup/pointercancel so a press that never moves
focus cannot swallow the next keyboard focus; match prefixed/unprefixed
app-region with !important tolerance and prove the allowlist path rejects
an offender; scope the ui-layout shared-curve limitation to data-animating.
2026-09-18 21:23:11 +08:00
mektpoy de8b10f8ab fix(browser-use): share the installation's scope and MCP client instances
A profile installs @deepseek-ai/dsh-experimental-browser-use-runtime from npm
beside the dsh installation, and that package declared @deepseek-ai/dsh-scope
and @deepseek-ai/dsh-mcp-client as dependencies, so the install carried its own
copy of both.

dsh-scope mints its scope-tag symbol per module instance, so the copy's
createScope tags a context the host's scopeOf cannot read. Every Agent's MCP
tools then registered in the global tool layer: the first Agent succeeded and
each later Agent's tool synchronization hit a name conflict, which
failOnStartupError turned into a failed creation.

Both are now peer dependencies (plus dev), which is how every other consumer of
ctx.tools and dsh-scope declares them and how a profile install reaches the
installation's single instance through the profiles module fallback.
schemastery stays a dependency: it carries no instance-local state.

Fixes #4573
2026-09-18 21:21:10 +08:00
Yichen Jiang 82c7889b0e Merge pull request #4539 from deepseek-harness/worktree/npm-registry-mirror-config-029a47
feat(plugin-manager): pick a registry and fall back through mirrors when one is unreachable
2026-09-18 20:58:06 +08:00
Yif 8837c532d1 Merge remote-tracking branch 'origin/master' into feat/macos-sidebar-ui-and-drag-refine 2026-09-18 20:54:38 +08:00
Yichen Jiang 61b099b77c fix(client): draw the registry popover's hairline as its elevation stroke
master's elevation contract (elevation-styles.client.spec.ts) rejects a
neutral border beside an lv/elevation shadow: the stroke belongs to the
shadow. The registry options keep their l3 hairline through
--dsw-elevation-stroke-color, as Menu does, with border: 0.
2026-09-18 20:50:04 +08:00
Yichen Jiang 5fe06ee64b Merge remote-tracking branch 'origin/master' into worktree/npm-registry-mirror-config-029a47
# Conflicts:
#	packages/client/ui-plugin-manager/src/client/PluginManagerPage.tsx
#	packages/client/ui-plugin-manager/src/client/manager-store.ts
2026-09-18 20:37:39 +08:00
Yichen Jiang 27207bfedb test(plugin-manager): stop an install before its first run deterministically
The cancelled-between-attempts case raced a zero-delay timer against the
file restore between two registries; under parallel load the restore won,
the second attempt started, and the once-only mock let it reach the real
pnpm. The stop now lands while the registries are read, before any run,
which is the same guard and cannot race.
2026-09-18 20:28:15 +08:00
Yichen Jiang 4d75946def Merge pull request #4552 from deepseek-harness/worktree/plugin-install-close
fix(web): allow closing pending plugin installations
2026-09-18 20:25:17 +08:00
lsdsjy 6ad7c991bf test(ui-chat,web): align browser coverage with work details folding 2026-09-18 20:23:46 +08:00
Yichen Jiang 1dfd026b84 Merge remote-tracking branch 'origin/master' into worktree/npm-registry-mirror-config-029a47
# Conflicts:
#	apps/web/tests/expected/plugin-install-cancel/cancelled.expected.md
#	docs/event-producer-consumer.i18n.yaml
#	docs/event-producer-consumer.md
#	docs/event-producer-consumer.zh.md
#	packages/boot/plugin-manager/README.i18n.yaml
#	packages/boot/plugin-manager/README.md
#	packages/boot/plugin-manager/README.zh.md
#	packages/client/ui-plugin-manager/README.i18n.yaml
#	packages/client/ui-plugin-manager/README.md
#	packages/client/ui-plugin-manager/README.zh.md
#	packages/client/ui-plugin-manager/src/client/PluginManagerPage.tsx
2026-09-18 20:21:59 +08:00
Yichen Jiang 75b20ea98c fix(web): remove unreachable fallback and pin snapshot scroll 2026-09-18 20:17:28 +08:00
mektpoy 771ebf4ace fix(web): scan plain src for a dual-face browser package without src/client 2026-09-18 20:06:44 +08:00
Yichen Jiang e7094c81e2 Merge origin/master into worktree/plugin-install-close 2026-09-18 20:03:08 +08:00
lsdsjy 50b7dd21a2 fix(ui-chat,web): isolate process updates and restore lifecycle details 2026-09-18 20:02:15 +08:00
Yichen Jiang f938ebad64 fix(web): recover lost plugin installation responses 2026-09-18 19:58:04 +08:00
Yichen Yan de2e799b0e Merge pull request #4202 from deepseek-harness/yyc/web-trust-domain-reviewed
feat(web): serve the shell, API and plugin resources from the document directory
2026-09-18 19:46:36 +08:00
lsdsjy afc62255a5 test(ui-chat): include performance settings in migration fixtures 2026-09-18 19:36:48 +08:00
lsdsjy 93caf245b4 fix(ui-settings): enable developer tools by default 2026-09-18 19:34:33 +08:00
lsdsjy f7daba8ca7 fix(ui-chat): map legacy normal work details to detailed 2026-09-18 19:34:33 +08:00
lsdsjy cb561cbe4e fix(ui-chat,web): align work detail labels and Bash snapshot 2026-09-18 19:22:24 +08:00
lsdsjy 6a9febf6fe Merge remote-tracking branch 'origin/master' into fix/pr4565-ci 2026-09-18 19:18:11 +08:00
Tianyi Cui 8913d033dc refactor(chat): match user messages before developer refusal
The input-message definition classifies ordinary user, steering, and injected
context messages. Put that supported path first so readers can follow its
matching rules directly, then handle deferred developer history afterward.

Keep the append/checkpoint predicates, matched identity, null result for
other events, and explicit developer error unchanged. This is a local
readability change with no presentation or persistence behavior change.

Validation: all 61 conversation-node definition tests pass, including user
classification and developer refusal. Scoped lint and whitespace checks pass.
2026-09-18 19:08:11 +08:00
Yixiang TuandTianyi Cui e0bd7e1960 feat(session): add developer changes using historical tool schemas
Session V4 reserves developer-role messages for incremental model-visible
session changes. The role, tool addition/removal blocks, and deferred-loading
marker need a durable representation before independent provider and UI
integrations can emit or consume them. Known developer events must match an
open turn and step; unsupported providers and UI consumers fail explicitly.

Request headers already capture full tool definitions. Persisting another
ToolSchema in each addition would duplicate Session information and create
two definitions that could drift. Additions therefore store toolName, and
the containing developer event uses headerSeq to select one earlier known
request/header for all its additions. A same-name replacement keeps its
historical definition identity through restart, fork and surface changes.
Removals require no definition copy, and ordinary developer messages omit
headerSeq. Generic sourceEventSeqs remains independent derivation metadata.

The rejected inline tool field is optional never with @persistenceReserved.
The schema extractor retains that uninhabited slot, so permitting a future
value requires a format bump instead of being mistaken for compatible
optional metadata. Unrelated optional fields remain additive and readable.

Native and Session admission reject missing, forward, non-header, unknown,
ambiguous and incomplete referenced definitions, and reject the retired
inline tool field. Validate complete schema fields only for referenced
definitions, preserving unrelated header behavior and extra JSON metadata.
Unknown ignorable records remain opaque. Empty developer nodes retain their
surface position without producing a message or shadowing the system head.
No automatic emitter, provider activation, UI implementation, or speculative
public resolver is added.

Keep the core-owned developer source binding beside the introduced role,
including the request-only type rejection and attribution round trips. Update
consumer guards, SDK/snapshot handling, bilingual owner documentation, and
the same unreleased V4 acknowledgement. Keep earlier tool/source migration
work and its documentation in the parent. Preserve the existing recorded
Cordis inspection generation from the original developer change.

Integration keeps the final source layer as the sole parent and preserves
master's Messages input filtering, empty-user omission, empty tool results,
and system-update placement. Developer/deferred guards still run before
conversion. Native V4 admission, header references, reservation declarations,
and generic tooling match the validated marker candidate. Parent-owned
fixture and generator fixes remain in the parent.

Earlier feature validation passed full build, full lint, 41 doc-sync gates,
and 16 hygiene gates. The unchanged native implementation retains that evidence.
The focused Session/V4/request-input run passes 884 tests across 38 files with
100% statement, branch, function and line coverage for the V3-to-V4 package
and Session surface. Consumer tests pass 482 with one existing skip; tool,
message and optional-field tests pass 146. Cordis inspection and compaction
replays pass in both source and built modes (two cases per mode). Ten invalid
referenced-definition/unknown-header controls failed before their scoped
guards and pass after them.

Reservation validation: the actual generated developer root records tool as
optional primitive never. Widening it requires a bump, while traceId remains
additive and preserved; all 85 focused reader/classifier tests pass.

Final-parent validation: 205 tests across eight provider, type, catalog,
Session and native-format files pass. The preserved parent fixture verifies
Messages input-history replay; runtime/declaration and parent-owned-file
comparisons show no loss during the final rebase.

Co-authored-by: Tianyi Cui <[email protected]>
2026-09-18 19:08:11 +08:00
Tianyi Cui b88afa8a72 fix(ptc): name persisted producer attribution ptc-mode
The producer-source refactor introduced the unreleased V4 discriminator
code-mode even though this source identifies images deferred from successful
PTC subcalls. Name it ptc-mode before V4 ships so persisted attribution
matches the producer and does not freeze obsolete terminology into new logs.

Keep the live writer, V3-to-V4 migration output, collision guard, and reserved
producer policy aligned. Both released tools-code-mode and tools-ptc plugin
lookup keys map to ptc-mode. Preserve those historical keys, opaque message
text and IDs, all frozen V0-to-V3 generations, and recorded Session inputs.

Update the owning tests, producer documentation, current catalog, and V4
acknowledgement. The canonical schema audit identifies exactly three changed
roots; reversing only the source literal restores each prior digest. The
large generated JSON diff comes from graph ordering, with 61 roots and 572
types retained.

Validation: the seven-file focused run passed 298 tests and exposed four
stale current-output expectations in two migration files. After correcting
those expectations, both files passed all 23 tests. All 41 documentation
gates and full lint passed; frozen generations and recorded inputs are
unchanged.
2026-09-18 19:07:12 +08:00
_KermanandTianyi Cui fb79a944f5 refactor(llm): separate durable producer sources from request inputs
Give durable user context its producer-owned source kind instead of a generic plugin wrapper. The V3-to-V4 conversion preserves the frozen rename and collision rules, all own JSON metadata, event coordinates, and historical request-only kinds. Native catalog and JSONL admission retain source validation so unknown attribution can survive without its producer while malformed owned sources are refused.

Use an explicit user-only RequestUserInput for auxiliary LLM prompts that do not own durable message identity. Auto Review and compaction remove their request-only source registrations while retaining prompt content and freezing; provider serializers and projections keep the same wire data, assistant replay isolation, image ordering, and tool correlation. Session writes, Agent delivery, and recorded title requests continue to require identified durable messages.

Bind the saved compatibility policy only to user sources and qualify tmux location attribution without weakening fixed system, model, or tool semantics. Rebuild the current inventory and unpublished V4 acknowledgement on the actual tool-role parent, preserving its rationale and all accepted historical records. The catalog format version records tooling metadata; Session remains V4. Developer-role policy and activation belong to their own feature.

The latest-master Messages adapter, serialization, request-input typing, EpochHeader reservation, and attribution checks pass 103 tests across five files. The retained V3 Messages input-history snapshot passes through the source profile; Host/Client typechecks and all 41 documentation gates pass. The generated comparison records four changed roots and 447 unchanged type fingerprints. Latest-master ARIA changes are retained, with producer source labels applied independently; new Messages fixtures retain their producer names as declared source kinds and use the fixed system-message helper; recorded Session generations are unchanged by this regrouping.

Co-authored-by: Tianyi Cui <[email protected]>
2026-09-18 19:07:12 +08:00
Tianyi Cui 29f7e7bdf5 fix(session): reserve retired system text in request-header schemas
Native V4 admission and Session adoption reject every request/header.header.system field because system prompts belong to system/message events. EpochHeader omitted that key, so the generated schema classified adding optional system text as a same-version optional-property addition even though released readers would reject it.

Declare system?: never with the shared persistenceReserved marker so the schema records the existing prohibition. Widening the field to string now changes a forbidden value set and requires a version bump. Keep both runtime guards and all unknown-metadata handling unchanged, and document the reservation alongside the current header type and V4 acknowledgement.

The regression extracts the actual event:request/header schema, checks the optional-never field, widens only that property, and verifies the version-bump diagnostic and native refusal. It fails without marker extraction and passes with the shared tooling support. The focused reservation, retired-syntax, and Session surface suites pass 65 tests; all 436 type-equiv blocks match their source. Generic marker extraction belongs to the foundational tooling change.
2026-09-18 19:05:33 +08:00
_KermanandTianyi Cui f4a32dbd0a refactor(llm): flatten tool results and validate native V4 sessions
Represent tool results as tool-role messages and migrate released wrappers
without losing outer own JSON fields, call identity, error state, or writer
field order. Refuse nested or wrapper-only information that has no lossless
current representation before successor publication.

Validate current event relationships directly instead of projecting V4 data
back into V3. Keep lifecycle, PTC, title/command, compaction, system-field and
retired-syntax checks on catalog restoration and ordinary JSONL opens. The
Worker JSON equality helper must inspect own keys so it cannot admit PTC
arguments that Node rejects.

Keep the source role change, required native admission and consumer fixture
updates in one complete review layer. Explain shared-type changes in the
persistence record and the native reader rationale in its existing note.
Use checked branded fixture identities and discriminant narrowing instead
of unknown double casts; fixture values and assertions remain intact.

This recomposes the original tool-role work by _Kerman with the independently
validated migration and admission repairs by Tianyi Cui. Frozen generations
and snapshot input version selection are retained.

Co-authored-by: Tianyi Cui <[email protected]>
2026-09-18 19:05:32 +08:00
Tianyi Cui c6e62ff971 fix(persistence): retain explicitly forbidden JSON fields
A reader can reject a property name while its type declares that property
as optional never. Dropping it from the extracted schema makes a future
optional JSON value look additive, although the existing reader refuses it.
The type history must retain that explicit prohibition.

Support the argument-free @persistenceReserved marker only on optional
properties whose values are never or undefined. Keep those properties as
optional primitive-never nodes, so later JSON values change an existing
field and require a version bump. Reject invalid placement, arguments,
repeated tags, required or JSON-valued declarations, and mapped uses that
violate the reservation. Keep every unmarked omission rule unchanged.

The classification regression failed before this change with an additive
optional-property result and now requires a type-change bump. All fifteen
focused reservation cases and all 210 tooling tests pass, including inherited
fields, misuse, and unmarked preservation. Host typecheck, scoped lint, and
pairing pass. Regeneration leaves the unannotated baseline catalog and
inventory byte-identical; all 61 roots still match their existing history.
No runtime property is activated in this tooling commit.
2026-09-18 19:05:32 +08:00
Tianyi Cui a6981c56f6 feat(persistence): add stable catalogs and policy-aware schema review
Traversal ordinals and first-owner paths gave shared persistence types
unstable display names. Keep authored names and source locations as metadata,
use structural digests for identity, and render complete kind/form groups
with bounded array labels. Preserve historical rendering and stored graphs.

Record supported source compatibility promises with their property binding,
policy version, discriminator, preservation rule, and qualified kind set.
The extractor and classifier require explicit opt-ins in both compared
schemas; ordinary or unsupported changes retain strict classification.
Policy-bearing graphs use the format-2 fingerprint domain while unannotated
graphs keep their format-1 fingerprints. Runtime owners activate bindings
only with the corresponding reader behavior; this tooling layer activates
none and changes no package source or Session generation.

Provide a read-only before/after review command that groups shared structural
evidence without replacing authoritative compatibility diagnostics. Match
changed alternatives only through unique literal discriminators and retain
ambiguous additions and removals as independent facts.

At the X baseline, all 61 roots and 472 schemas, digests, and source fields
are unchanged. The inventory remains format 1 and removes only 296 synthetic
names. Existing acknowledgement history still matches every root. All 195
focused tooling tests, Host build/full lint, and bilingual pairing pass.
2026-09-18 19:05:32 +08:00
Tianyi Cui bc710ac726 test(web): await the settled branch action in fork snapshots
The merged performance/usage preference hides elapsed-time controls by
default. The release-only mid-turn fork scenario still waited for
"Ran for", so its ARIA check timed out despite a completed child turn.

Wait for the visible, enabled branch action belonging to live turn 2.
That action requires the settled turn tail and directly exercises the
fork affordance this scenario owns. Preserve the existing exact-cut,
synthetic closer, resume-header, completed-turn, and streaming checks.
Keep the default preference rather than enabling unrelated timing UI.

Refresh only the obsolete elapsed-time button in the ARIA expected
output. Session JSONL inputs, historical generations, snapshot versions,
and production code remain unchanged. The former wait failed locally;
the four-case scenario passes in ordinary comparison mode after this fix.
2026-09-18 19:02:10 +08:00
winewill 925f609092 Merge remote-tracking branch 'origin/master' into fix/windows-mandatory-update-overlay 2026-09-18 18:47:20 +08:00
Yif 761f53223a feat(desktop): align main window default and minimum size with Codex (1280x820, min 480x600) 2026-09-18 18:44:24 +08:00
winewill d7fd452e96 test(desktop): exercise embedded mandatory update recovery on Windows 2026-09-18 18:40:31 +08:00
mektpoy 0e23ee6789 Merge branch 'yyc/web-trust-domain-reviewed' into yyc/web-relative-routes-reviewed
# Conflicts:
#	packages/client/ui-chat/tests/assistant-markdown-path-images.client.spec.tsx
#	packages/client/ui-open-in-app/README.i18n.yaml
#	packages/client/ui-open-in-app/README.md
#	packages/client/ui-open-in-app/README.zh.md
#	packages/client/ui-open-in-app/src/client/index.ts
#	packages/session-query/session-log-export/README.i18n.yaml
2026-09-18 18:35:31 +08:00
Yif e9b928d993 Merge remote-tracking branch 'origin/master' into feat/macos-sidebar-ui-and-drag-refine
# Conflicts:
#	.agents/notes/implemented/feature/2026-09-13-macos-hidden-titlebar-vibrancy.i18n.yaml
#	.agents/notes/implemented/feature/2026-09-13-macos-hidden-titlebar-vibrancy.md
#	.agents/notes/implemented/feature/2026-09-13-macos-hidden-titlebar-vibrancy.zh.md
#	docs/subsystems/slots.i18n.yaml
#	docs/subsystems/slots.md
#	docs/subsystems/slots.zh.md
#	packages/client/ui-conversation/src/client/contract/slots.ts
#	packages/client/ui-conversation/src/client/skeleton/ConversationRoot.module.css
#	packages/client/ui-conversation/tests/skeleton.client.spec.tsx
#	packages/client/ui-sidebar/README.i18n.yaml
#	packages/client/ui-sidebar/README.md
#	packages/client/ui-sidebar/README.zh.md
#	packages/client/ui-sidebar/src/client/HeaderLeadingControls.tsx
#	packages/client/ui-sidebar/src/client/index.ts
#	packages/client/ui-sidebar/tests/apply.client.spec.tsx
#	packages/extensions/cordis-client-runner/src/client/slot-catalog.ts
2026-09-18 18:34:12 +08:00
Yichen Jiang 998d2ce8c9 fix(client): narrow the registry options to their content 2026-09-18 18:33:17 +08:00
mektpoy 2c6f634771 Merge branch 'master' into yyc/web-trust-domain-reviewed
# Conflicts:
#	tsconfig.host.json
2026-09-18 18:31:17 +08:00
Yichen Jiang 78522443f7 fix(client): keep the registry's host to its option
The control beside the guide, the attempt line, the run badges, and
the failure lists name a registry by its name alone; only the option
in the list carries the host it names.
2026-09-18 18:30:37 +08:00
winewill fdc4887c38 fix(desktop): embed Windows mandatory updates in the main window 2026-09-18 18:26:40 +08:00
Yichen Jiang c34e33cbd9 fix(client): title the registries by their hosts and drop the fallback note
The default registry reads as such with the host pnpm names, the
mirror carries its host too, and no line under them repeats it or
describes the order the Host would ask; the order shows only as the
Host asks it.
2026-09-18 18:25:57 +08:00
Tianyi Cui c95d045fdc docs(session): correct fork seed marker ownership
Arbitrary-sequence forks place the inherited end-seed marker before their
synthetic tool, step and turn closers. The Session constructor receives that
whole prepared seed, so firstLiveSeq can follow the inherited marker and the
constructor is no longer its sole producer. The retained documentation still
claimed both facts and could lead a consumer to use the wrong ownership cut.

Name buildForkSeed and the constructor as the two core producers, distinguish
the inherited cut from ordinary restore markers, and retain the warning about
plugins appending their own markers. Correct the subsystem reference's claim
that interrupted is the only synthetic turn ending: forked is also never
emitted by a live loop. Keep the bilingual reference and type-equivalent
excerpts aligned with the source JSDoc.

Refresh the required persistence catalog after the JSDoc change. Its schema
inventory changes only three source-line locations; all 61 roots, 472 types
and fingerprints remain identical. Parsed TypeScript declarations printed
without comments are also identical, so this changes neither runtime behavior
nor persisted fields, historical generations or recorded Session fixtures.
2026-09-18 18:25:39 +08:00
Tianyi Cui 044cd4fb49 merge: integrate pinned master into session-log-v4 base
Merge master 01d96c168a84227374e3aa741e94ddfbb498e27e, observed when
this integration started, after the published 6de3db41 checkpoint. Keep
master's author history and preserve the release writer, adjacent
migrations, historical Session generations, and parent-owned catalogs.

Combine blank Session writer acquisition and superseded-startup
cancellation with the release branch's projection refresh and panel
preservation. A startup acquisition may finish after a later navigation,
but must not retain a stale main Session or reveal its Conversation.
Update the existing regression's retention expectation while preserving
its panel and active-navigation assertions.

Keep the release catalog loading, known-leaf, and parent availability
behavior alongside master's completed-turn status and aligned sidebar
icons. Regenerate the config locations and pair the combined README
contracts. Preserve upstream's Linux process-state assertion repair.
2026-09-18 18:21:47 +08:00
Yifffan afc27869f7 Merge pull request #4532 from deepseek-harness/feat/plugin-refine
refine(plugin-manager): polish install dialog, wizard states, and plugin artwork
2026-09-18 18:05:11 +08:00
Kaige-Gao 0ed5151d0b Merge pull request #4448 from deepseek-harness/feat/session-header-feedback
feat(web): add feedback to session header menu
2026-09-18 18:01:38 +08:00
Kaige-Gao 1b622ae54c Merge master into feat/session-header-feedback 2026-09-18 17:47:22 +08:00
yixiangihsiang ae1cf3106b fix(web): polish work detail interactions 2026-09-18 17:46:32 +08:00
fz 5c263aaaf6 Merge remote-tracking branch 'origin/master' into feat/workspace-dependencies-package 2026-09-18 17:45:01 +08:00
fz defb9c3736 fix(sdk): shorten bundled Python resource paths on Windows 2026-09-18 17:44:15 +08:00
Yif c3226f93e9 Merge remote-tracking branch 'origin/master' into feat/plugin-refine
# Conflicts:
#	packages/client/ui-primitives/README.i18n.yaml
#	packages/client/ui-primitives/README.md
#	packages/client/ui-primitives/README.zh.md
#	packages/client/ui-primitives/tests/icons.client.spec.tsx
2026-09-18 17:43:20 +08:00
CreatixChu e651d00a9f Merge pull request #4354 from deepseek-harness/worktree/present-changes-dedup
通过提示词减少重复展示
2026-09-18 17:33:28 +08:00
Yif 978e39e94e fix(plugin-manager): align plugin-config e2e and README quotes with revised copy
The plugin-config e2e pinned the old terminal one-liner and its golden still
carried the three pre-revision summaries; the ui-plugin-manager READMEs still
quoted the renamed install-guide button. Refresh the golden, update the
assertion and both READMEs, and re-record the translation pairing.
2026-09-18 17:30:31 +08:00
Kaige-Gao de3f4c24ea fix: address feedback menu review and Linux process assertion 2026-09-18 17:30:19 +08:00
yixiangihsiang 5f90ac119e fix(web): refine live work disclosure presentation 2026-09-18 17:28:59 +08:00
lsdsjy 776efdf321 fix(ui-chat): hide infrastructure rows and retain turn triggers 2026-09-18 17:26:32 +08:00
lsdsjy f2cf7272ef fix(ui-tool,ui-skill): localize Chinese tool step titles 2026-09-18 17:26:31 +08:00
lsdsjy 687ee6e2c7 fix(ui-chat): align work detail modes and disclosure titles 2026-09-18 17:26:31 +08:00
lsdsjy ad1ddfd0b1 fix(ui-chat): hide settled reasoning previews when collapsed 2026-09-18 17:25:57 +08:00
lsdsjy 4216e778f5 fix(ui-chat): place response actions at the turn bottom 2026-09-18 17:25:57 +08:00
creatixchu 6453db05ca Merge remote-tracking branch 'origin/master' into worktree/present-changes-dedup
# Conflicts:
#	packages/client/ui-deliverables/README.i18n.yaml
#	packages/client/ui-deliverables/README.md
#	packages/client/ui-deliverables/README.zh.md
2026-09-18 17:25:53 +08:00
mektpoy c85e1d7b72 fix(client): raise the plugin inventory package identity to 12px
The Plugins settings inventory drew the Loader entry identity (cardIdentity)
at 11px in the code font, one step below the 12px the same identifier gets in
the expanded card's entry row. On Windows the code stack resolves to Consolas,
where 11px reads thin. Raise it to 12px/18px so it matches the sibling
identifier row and the page's other small text.

Fixes #4496
2026-09-18 17:25:40 +08:00
lsdsjy e132da911a fix(ui-chat): hide disclosure arrows for empty turn processes 2026-09-18 17:25:38 +08:00
lsdsjy 0859dca83f fix(ui-chat): show at least one second for completed turns 2026-09-18 17:25:37 +08:00
lsdsjy 55c564f31c feat(ui-chat): show independent notices for non-human turn triggers 2026-09-18 17:25:37 +08:00
lsdsjy fac2a46c02 fix(ui-chat): reset nested disclosures when collapsing a turn 2026-09-18 17:25:36 +08:00
lsdsjy a0c71e9f00 feat(ui-chat): add work details modes and keep retry rows separate 2026-09-18 17:25:36 +08:00
mektpoy 0301a0a062 fix(desktop): place Windows caption tooltips below the titlebar
The Windows caption menu host sits at z-index 1100 in the strip that --dsh-windows-menu-start reserves to the right of the sidebar toggle and New Session, so the sidebar's right-side hover bubbles rendered underneath the Application/Edit text. Render those two bubbles below the 40px caption instead; ordinary Web and macOS keep the default right side.
2026-09-18 17:25:14 +08:00
lsdsjy 0ce6ab6775 feat(ui-chat): reflect turn outcomes in process disclosure titles 2026-09-18 17:24:20 +08:00
lsdsjy b2296f5916 feat(ui-chat): show expanded turn timer from turn start 2026-09-18 17:23:52 +08:00
lsdsjy 97514daa23 feat(ui-chat): refine process titles and expose retry activity 2026-09-18 17:23:51 +08:00
lsdsjy fab6622abb feat(ui-chat): fold consecutive process steps and complete paginated turns 2026-09-18 17:23:51 +08:00
Yichen Jiang 2b168ce1ae fix(client): float the registry options over the install dialog and let its content scroll
With the guide open, unfolding the registry options pushed the dialog
past the viewport, and the card did not scroll. The options now float
from their toggle in a portal, right-aligned to it, folded by Escape
(claimed before the dialog's own Escape) or a pointer outside them,
and by the store when a run starts; useAnchoredPosition gains end
alignment for this. The dialog's content region scrolls under its
footer. The mirror reads as the mainland China mirror without a tag.
2026-09-18 17:20:10 +08:00
ihsiang 669bc2036f Merge pull request #4545 from deepseek-harness/ihsiang/refine-web-review-subagent-status
feat(web): refine review and subagent status UI
2026-09-18 17:19:52 +08:00
Turtle 5c8933fbe3 fix(session): acquire blank Session writers before reuse (#4392)
* fix(session): keep blank session discovery local to each host

* fix(session): acquire blank writers before reusing sessions

* refactor(workspace): separate saved selection restoration from blank lookup

* fix(workspace): create after the selected blank writer is held

* test(web): align blank reload snapshot with current icons
2026-09-18 17:19:40 +08:00
fz 9d66716e50 Merge remote-tracking branch 'origin/master' into feat/workspace-dependencies-package 2026-09-18 17:19:26 +08:00
fz 37983d9298 feat(sdk): bundle shared Office authoring resources 2026-09-18 17:17:46 +08:00
creatixchu ac0a042f08 Merge remote-tracking branch 'origin/master' into worktree/present-changes-dedup 2026-09-18 17:16:34 +08:00
creatixchu ba98bcbf08 test: refresh presentation guidance after review 2026-09-18 17:12:03 +08:00
creatixchu 223385954d fix: recommend at most four files per presentation 2026-09-18 17:10:38 +08:00
creatixchu 63c1e04b93 fix: keep file access available across presentation clients 2026-09-18 17:09:02 +08:00
Yichen Jiang cb2750019a fix(plugin-manager): read pnpm's own registry, its stdout refusals, and lay failures where they belong
Review of #4539. A refusal pnpm view prints as JSON on stdout was read
as unknown, so no registry was ever asked after a dead one; stderr and
stdout are now read alike, and a lookup killed at its bound is a
network refusal. pnpm's own registry joins the fallback plan only once
pnpm config get registry names npm's own or a configured fallback, so
a company registry in .npmrc is never followed by a public one, and a
registry pnpm already names is asked once. The Host lays each failure
at the registry or the spec's own host, reading the host off error
lines only, and reports it as ChangeResult.failedAt; a stop that lands
between attempts is cancelled, not failed. The package publishes its
registry rule through ./registry so the dialog plans with the Host's
own function, starts from the registry the Host asks first until one
is remembered, tags that one as the default, labels pnpm's own by the
host it names, and words and offers a change of registry from the
Host's attribution. ERR_PNPM_NO_OFFLINE_META is no longer a network
failure.
2026-09-18 17:03:04 +08:00
07akioni 6d7a5f9cc3 fix(desktop): verify archived descriptors and harden Office resolution 2026-09-18 17:01:17 +08:00
yixiangihsiang a0c7299f8b feat(web): refine review and subagent status UI 2026-09-18 17:01:04 +08:00
fz 1e3afc63ad Merge remote-tracking branch 'origin/master' into feat/workspace-dependencies-package 2026-09-18 16:58:33 +08:00
winewill f5b0f56ce6 Merge pull request #4528 from deepseek-harness/perf/macos-packaging
perf(desktop): speed up macOS packaging with verified signature caching
2026-09-18 16:56:48 +08:00
Yichen Jiang 6e456264a9 test(web): wait for sidebar collapse before reopening 2026-09-18 16:47:24 +08:00
Tianyi Cui d40f3fb658 test(hooks): synchronize inspection of an unpublished install lock
The Windows coverage job can schedule the test after the old 200 ms
publication delay has elapsed. File existence then no longer proves an
empty lock, so the setup assertion fails even though the installer is
correct. The original implementation reproduces that failure when parent
inspection is held until the record has been published.

Replace the delay-only test hook with two opt-in fixture barriers. Hold the
publisher after exclusive creation, and acknowledge the contender only
after it has observed an incomplete record. The test checks the empty bytes
before releasing both processes and always releases and awaits them during
cleanup. Existing normal-path lock and initialization budgets are unchanged;
no test barrier is entered without its explicit environment setting.

The focused barrier case and all 38 installer tests pass, as do scoped lint
and whitespace checks. This changes test synchronization rather than
loosening lock ownership or increasing production timeouts.
2026-09-18 16:40:25 +08:00
winewill 870b0099c6 fix(desktop): retain packaging errors and restore unconfigured proxies 2026-09-18 16:36:33 +08:00
creatixchu 13692e9179 test: refresh selective presentation prompt snapshots 2026-09-18 16:35:51 +08:00
Turtle de662ee010 fix(boot): skip failed profile bundles and retain recovery controls (#4516) 2026-09-18 16:35:50 +08:00
creatixchu 2e55564e02 fix: specify absolute paths for inline image rendering 2026-09-18 16:32:56 +08:00
creatixchu 28fbe7433d fix: guide selective file presentation through prompts 2026-09-18 16:31:13 +08:00
Yif a6267c8d90 fix(plugin-manager): per-instance artwork ids, detail error feedback, coverage
Give every plugin artwork per-instance SVG def ids via useId so repeats on
one page keep their gradients and clips; drop the dead agent-team-web-profile
artwork row; keep the read-failure alert and retry visible on detail pages;
cover the new artwork components and the default-artwork branch; document the
artwork exports in the ui-primitives READMEs; refresh the plugin-manager and
plugin-install-cancel web expected outputs for the revised copy.
2026-09-18 16:30:25 +08:00
creatixchu c19d1f12fb Merge remote-tracking branch 'origin/master' into worktree/present-changes-dedup 2026-09-18 16:26:45 +08:00
07akioni 54d46ad4d4 fix(desktop): unpack and verify native Office conversion 2026-09-18 16:25:29 +08:00
Tianyi Cui 959164f157 test(headless): align catalog diagnostic status with inactive state
Master's agent-target update reports non-running children as inactive. The
release-only descriptorless-child regression still expected ready, so CI
failed before comparing the persisted result. Update only that status in
the assertion and its expected output; direct catalog discovery and the
absence of corrupt-child diagnostics remain asserted.

The expected log retains version 3 and every other byte. A built assembled
headless run passes in normal comparison mode, preserving the older input
format and its upgrade/reconstruction coverage rather than refreshing the
snapshot generation.
2026-09-18 16:21:57 +08:00
winewill 6ed957ec33 Merge origin/master into perf/macos-packaging 2026-09-18 16:08:38 +08:00
Yichen Jiang 736730c3fd fix(web): allow closing pending plugin installations 2026-09-18 16:00:47 +08:00
Yif 92101e1a5b fix(ui-conversation): render the current crumb as text so the title joins the drag band
The current session's breadcrumb was a disabled button: the global darwin
no-drag rule subtracted it from the window drag band while its disabled state
and button user-select kept it from doing anything else, leaving the title a
dead zone that neither dragged the window nor selected. Plain text keeps the
native titlebar behavior — the title drags the window; ancestor crumbs stay
buttons.
2026-09-18 15:59:27 +08:00
CreatixChu bedcb7b58c Merge pull request #4373 from deepseek-harness/worktree/sidebar-open-in-default-app
为不支持预览的侧边栏文件提供本地打开操作
2026-09-18 15:59:19 +08:00
winewill fa6941971a Merge origin/master into perf/macos-packaging 2026-09-18 15:58:57 +08:00
Tianyi Cui c3cfe83d2e merge: update the session-log-v4 integration base from master
Merge a2acc7d3eb620bb52f09f7ea3b5a3a1c23353e18 after preserving the earlier
master checkpoint and its successful CI. Carry current provider input-history
handling, UI controls, agent targeting, and runner-concurrency changes into
the integration base without changing the Session migration feature layers.

Keep durable direct-child catalog semantics alongside the upstream running /
inactive vocabulary. Resolve the snapshot description conflicts by retaining
that upstream status text and the existing descendants-only diagnostic rule;
the source and all 53 JSON tool descriptions agree. Preserve parent-session
availability handling with the upstream stale-blank and batched-history fixes.

The merged application builds, all 41 documentation gates pass, and focused
provider, tool-control, and Session Controller tests pass (98, 29, and 125).
Recorded input changes come from master: one refreshed v3 list-agents sample
and three new v3 scenarios; no migration bumps an existing snapshot generation.
2026-09-18 15:52:56 +08:00
creatixchu e22005ae10 Sync session controller configuration catalog translation 2026-09-18 15:51:49 +08:00
creatixchu 1902842b6c Merge remote-tracking branch 'origin/master' into worktree/sidebar-open-in-default-app 2026-09-18 15:50:51 +08:00
winewill bba78f0534 Merge pull request #4514 from deepseek-harness/perf/windows-signature-cache
feat(desktop): share Windows runtime signature cache across worktrees
2026-09-18 15:48:55 +08:00
CreatixChu 4960abd402 Merge pull request #4371 from deepseek-harness/worktree/issue-4370-code-diff-sidebar
fix(web): 左右滚动同步
2026-09-18 15:40:22 +08:00
Yichen Jiang 66c1c5fadd fix(client): drop the sync-lag clause from the npmmirror registry line 2026-09-18 15:39:23 +08:00
winewill 545ccdb6f9 test(desktop): load proxy lock fixture from source 2026-09-18 15:37:33 +08:00
winewill 750bf4988e fix(desktop): initialize Windows cache under stage ownership 2026-09-18 15:37:05 +08:00
winewill 1a7308fbaa test(shell): synchronize background process completion 2026-09-18 15:37:05 +08:00
winewill 7754a6fd49 test(desktop): isolate Windows cache path from host platform 2026-09-18 15:37:05 +08:00
winewill d4ed1d93d8 feat(desktop): share Windows runtime signature cache across worktrees 2026-09-18 15:37:05 +08:00
creatixchu 363343fb83 Refresh config locations and satisfy typed fixture lint 2026-09-18 15:36:56 +08:00
Yichen Jiang e303efe017 fix(client): align an offered registry's line under its title
The indent meant for the typed address's field and line also reached
the line under each offered registry, pushing it past the title.
2026-09-18 15:36:41 +08:00
lsdsjyandlsdsjy 3e216c3d64 feat(ui-chat): add performance and usage display preference (#4478)
* feat(ui-chat): add performance and usage display preference

* fix(ui-chat): address performance preference review findings

* test(ui-chat): refresh Chinese turn footer snapshots

* docs(ui-chat): clarify completed-turn timing visibility

* test(ui-chat): capture preference failure screenshots

* fix(ui-chat): align performance preference with current icons

---------

Co-authored-by: lsdsjy <[email protected]>
2026-09-18 15:35:50 +08:00
creatixchu dd8c4b817b Keep fixture slot adapters explicitly typed 2026-09-18 15:33:47 +08:00
creatixchu ed4d95482b Assert the retained file action path in toolbar coverage 2026-09-18 15:32:55 +08:00
creatixchu 04d0235673 Verify local file opening and address sidebar review findings 2026-09-18 15:31:50 +08:00
Yifffan 54b76da126 Merge branch 'master' into feat/plugin-refine 2026-09-18 15:30:26 +08:00
fz 32524892a3 Merge origin/master into feat/workspace-dependencies-package 2026-09-18 15:27:45 +08:00
winewill 8dca1b7713 Merge master to synchronize desktop update dialogs 2026-09-18 15:27:03 +08:00
creatixchu 8f2a183514 fix(web): document paired column ref lifetime 2026-09-18 15:26:22 +08:00
creatixchu 12c0cbb56d Use document preview client config and shorten README summary 2026-09-18 15:24:51 +08:00
Yichen Jiang e06c7852ff Merge remote-tracking branch 'origin/master' into worktree/npm-registry-mirror-config-029a47 2026-09-18 15:24:42 +08:00
creatixchu 2cea45edc6 Remove duplicate guard from custom renderer fixture 2026-09-18 15:24:00 +08:00
creatixchu c538c4ec8f Adapt Office renderer fixture to file action slots 2026-09-18 15:23:13 +08:00
creatixchu 419d7c57c7 fix(web): make scroll frame callback return explicit 2026-09-18 15:23:00 +08:00
winewill 3bd8cfdf20 Merge pull request #4523 from deepseek-harness/fix/modal-backdrop-transition
fix(desktop): keep update dialog backdrop stationary across prompts
2026-09-18 15:22:22 +08:00
creatixchu 9e1635b6b6 Merge master and refresh sidebar file-opening integrations 2026-09-18 15:21:47 +08:00
Yichen Jiang 398a4247ae feat(client): pick and remember the registry a plugin install asks first
The install dialog folds a registry control beside the guide: the
official registry, the mirrors the Host configured, and a typed
address, remembered in this browser and passed to the check and the
run. The installing screen names the registry the Host moved on to,
each pnpm run carries its registry, and a failed run names every
registry asked, or the host a git or tarball spec could not reach,
with a shortcut back to the registry options. The Host answers the
spec's own host and the registries it asked so the dialog can say so.

Also documents the registry plan in the package READMEs, the boot
subsystem page, the catalogs, and an Agent Note; pins the flow through
a real Host/browser e2e and refreshes the tool-schema snapshots.
2026-09-18 15:21:44 +08:00
Yichen Jiang 716e2e2683 feat(plugin-manager): ask configured registries in turn while one is unreachable
Add registry and fallbackRegistries to the manager Config, ask them in
order from inspect and installBundle with --registry, and move on only
for failures another registry can change: unreachable, timed out, or a
copy the registry lacks. A git or tarball spec's own host is never
retried through a registry, and a registry outside the configured set
is asked alone. The lookup runs without pnpm's retries so a dead
registry is reported within inspectTimeoutMs; the install keeps pnpm's
defaults. Results carry the registries asked, the install-state event
names each attempt, and the plugin_manager tool takes a registry.
2026-09-18 15:21:44 +08:00
Yifffan a363f7b5af Merge branch 'master' into feat/plugin-refine 2026-09-18 15:21:20 +08:00
creatixchu cc9caed57b fix(web): type browser regression style cleanup as Node 2026-09-18 15:19:50 +08:00
winewill a051fed540 Merge latest master into perf/macos-packaging 2026-09-18 15:18:13 +08:00
creatixchu b5cbcad854 fix(web): preserve split diff scroll offsets across clamped peers 2026-09-18 15:17:13 +08:00
Yichen Jiang 3e206546fd Merge pull request #4351 from deepseek-harness/worktree/fix-new-session-stale-blank
fix(web): reconcile stale blank session hints from projections
2026-09-18 15:17:11 +08:00
winewill 7313e8dcc7 Merge master and fix desktop packaging proxy locking and checks 2026-09-18 15:17:03 +08:00
Yifffan 0c4f554d6e Merge branch 'master' into feat/plugin-refine 2026-09-18 15:16:13 +08:00
winewill 0c38f6fe43 Merge branch 'master' into fix/modal-backdrop-transition 2026-09-18 15:15:51 +08:00
creatixchu 79677e4824 Merge remote-tracking branch 'origin/master' into worktree/issue-4370-code-diff-sidebar 2026-09-18 15:15:11 +08:00
Yif e67aca993e docs(notes): align window-chrome notes and sidebar README with shipped drag ownership
The seat note claimed a plugin-manager inline clearance formula that never
shipped (entry pages pad by the unconditional --dsh-frame-top-clearance) and
omitted the 84px fullscreen clearance tier. The vibrancy note still called the
sidebar top strip a drag region after that declaration moved to the frame's
.leadingBand, said drag lives in "exactly two places" while the Windows caption
::before is a third, and lacked the dsh-desktop:window-fullscreen relay that
drives html[data-fullscreen]. The ui-sidebar README carried the same stale
top-strip drag claim. Chinese sides follow; pairings re-recorded.
2026-09-18 15:14:59 +08:00
Yichen Jiang ce4810cacb Merge pull request #4533 from deepseek-harness/worktree/custom-provider-url-placeholders
fix(web): match custom provider URL placeholders to protocol
2026-09-18 15:14:28 +08:00
Yif b42bb09420 Merge remote-tracking branch 'origin/master' into feat/plugin-refine 2026-09-18 15:10:18 +08:00
Kaige-Gao fa009c79e4 Merge master into feat/session-header-feedback 2026-09-18 15:10:02 +08:00
winewill 021a3c6065 Merge branch 'master' into fix/modal-backdrop-transition 2026-09-18 15:08:00 +08:00
fz ce09d8a293 Merge origin/master into feat/workspace-dependencies-package 2026-09-18 15:06:49 +08:00
Yichen Jiang 27eec0a247 Merge remote-tracking branch 'origin/master' into worktree/fix-new-session-stale-blank
# Conflicts:
#	packages/api/session-controller/README.i18n.yaml
#	packages/api/session-controller/README.md
#	packages/api/session-controller/README.zh.md
#	packages/api/session-controller/src/client/sessions/manager.ts
#	packages/api/session-controller/tests/manager.client.spec.ts
2026-09-18 15:05:52 +08:00
Turtle 2099andTurtle f83800530c fix(web): recolor the plan chip and align it with the control row (#4399)
* fix(web): make the plan chip a business-accent control at row height

The chip borrowed the warn-state pair for a plain mode flag, so it read as
a warning, and its 2px vertical padding left it 4px shorter than the + and
select controls beside it. It now uses the business accent pair, takes the
row's 28px height, and carries Chinese copy; the unused chip.off.*
dictionary entries had no surface and are dropped.

* docs(web): re-record the plan chip README pair

* docs(web): state the plan chip's close glyph and drop stale copy claims

The READMEs described the chip without the × the component renders, and the
zh side still quoted the English accessibility string after this branch gave
the namespace its own Chinese one.

* test(web): pin the plan chip's accent tokens, height, and Chinese copy

The lifecycle-chrome lane asserted the chip's warn pair; it now asserts the
business pair plus the 28px row height, and a zh plan-active golden records
the 「计划」 label the chip shows under the Chinese locale.

* fix(web): color claimed command tokens with the business accent

The draft's claimed token still used the warn label color while the plan chip
moved to the business pair, so a claimed /计划 read orange above a blue chip.
The claim decoration and the tests asserting its inline style now use
state-business-primary.

* fix(web): keep the composer control row on one line at narrow widths

The fixed-width mode chips and send circle left the row's 12px gaps deciding
between a slack first line and a wrapped trailing group: beside the plan chip,
a long model name moved the model trigger and send circle to a second line
while the first line still had room. Narrow rows now use 8px gaps, which holds
the row together down to the icon-only model trigger.

* fix(web): drop the context ring in the narrow composer row

With plan mode on and a long model name, the 28px occupancy ring plus its gap
was what pushed the trailing group onto a second line. The ring yields at the
same row width that already hides the access-mode label, so the fixed controls
keep one line; the reading stays reachable from a wider window.

---------

Co-authored-by: Turtle <[email protected]>
2026-09-18 15:04:54 +08:00
Yif c52400ad72 test(ui-primitives): count the new check-circle glyph in the icon set 2026-09-18 15:01:43 +08:00
Yichen Jiang 98c083bb31 fix(web): match custom provider URL placeholders to protocol 2026-09-18 15:01:24 +08:00
winewill 0c3eb28276 test(desktop): await mandatory overlay fade after policy clearance 2026-09-18 14:59:30 +08:00
winewill 72d93cbcc2 fix(desktop): reset scroll when replacing update prompts 2026-09-18 14:59:30 +08:00
winewill a666209a59 fix(desktop): fade mandatory update overlays in place 2026-09-18 14:59:30 +08:00
winewill 299101077a fix(desktop): keep update dialog backdrop stationary across prompts 2026-09-18 14:59:30 +08:00
fz b51e64d70e refactor(skill): flatten primary runtime version metadata 2026-09-18 14:57:12 +08:00
07akioni 3f5b1e283b Merge pull request #4464 from deepseek-harness/07akioni/fix-sessionless-sidebar-controls
fix(client): keep sidebar navigation available without a session
2026-09-18 14:55:25 +08:00
Yif 76fa3860c6 fix(plugin-manager): drop guide item background that surfaced in dark mode 2026-09-18 14:54:12 +08:00
Yif 3065e75d08 Merge remote-tracking branch 'origin/master' into feat/plugin-refine
# Conflicts:
#	packages/client/ui-plugin-manager/src/client/locales.ts
#	packages/client/ui-primitives/src/icons/index.tsx
2026-09-18 14:45:13 +08:00
Yichen Jiang b1cf410e0c Merge pull request #4520 from deepseek-harness/worktree/windows-observational-reuse
refactor(ci): reuse Windows builds for observational checks
2026-09-18 14:42:43 +08:00
_Kerman 8fe06cfb24 Merge pull request #3956 from deepseek-harness/xtr/tool-card-details
feat(web): render compact details for recorded tool results
2026-09-18 14:42:13 +08:00
fz b16b116408 Merge origin/master into feat/workspace-dependencies-package 2026-09-18 14:41:15 +08:00
Dudu-0223 eb570e306a Merge pull request #4251 from deepseek-harness/fix/4246-agent-targets
fix: expose Team targets and simplify model agent statuses
2026-09-18 14:39:52 +08:00
07akioni d6002c1018 Merge master and preserve compact conversation header layout 2026-09-18 14:39:32 +08:00
_Kerman 7bd832675a Merge latest master into xtr/tool-card-details 2026-09-18 14:34:46 +08:00
_Kerman 91679f8351 Merge remote-tracking branch 'origin/master' into xtr/fix-snapshot-source-ranges 2026-09-18 14:33:25 +08:00
Dudu-0223 66846bf8c0 test: refresh agent guidance in sidecars added on master
subagent-activation-limit and plugin-manager-mcp landed after this branch
diverged and still pinned the idle/ready descriptions; regenerate both
through their owners so every pinned header carries the shipped text.
2026-09-18 14:29:35 +08:00
Dudu-0223 cda99db4c0 docs: align remaining agent availability prose with the shipped vocabulary
Sync the pinned schedule-catalog tool-schema sidecar with the refreshed
list_agents, send_message, subagent, and subagent_fork descriptions; the
catalog e2e does not compare it, so the refresh missed it. Rewrite the
Agent Teams note's Steer sentence and the ownership-release limitation so
`idle` no longer appears where the roster vocabulary is running/inactive,
and fix the two-clause comma splice and a zh full-width spacing slip.
2026-09-18 14:29:35 +08:00
Dudu-0223 563d040d23 test: refresh subagent guidance in plugin manager snapshot 2026-09-18 14:29:35 +08:00
Dudu-0223 b9e59cbaee refactor: unify Team availability in the service 2026-09-18 14:29:35 +08:00
Dudu-0223 8b6831daa0 test: refresh agent guidance embedded in PTC prompts 2026-09-18 14:29:35 +08:00
Dudu-0223 507e0e6dfe fix: expose Team targets and simplify model agent statuses 2026-09-18 14:29:35 +08:00
Yif e509dd9341 fix(client): pulse an app-region nudge at the right panel's open/close edges
Electron recollects window drag rects only when a style or layout pass
dirties an app-region value, and Blink skips hidden boxes when collecting
them, so the first open of the slid-off panel left the strip swallowed by
the drag band until an unrelated layout ran.
2026-09-18 14:28:12 +08:00
Yif 9ae6f0defa fix(client): mount the shell.leading seat on macOS only and cover the fullscreen relay 2026-09-18 14:28:07 +08:00
Yichen Jiang ed50586256 refactor(ci): remove unused Windows observational entry 2026-09-18 14:27:45 +08:00
imccyu 21e31ac05e Merge pull request #4494 from deepseek-harness/perf/chat-navigation-history
perf(chat): 虚拟化轮次导航并聚合历史跳转
2026-09-18 14:21:32 +08:00
winewill 5ef7023b6c perf(desktop): cache verified macOS runtime signatures 2026-09-18 14:15:47 +08:00
imccyu 462c354792 test(web): await both subagent settings saves 2026-09-18 14:13:48 +08:00
imccyu 6f66437de4 fix(ui-chat): prioritize submissions over pending scroll samples 2026-09-18 14:13:48 +08:00
imccyu d53c305224 fix(ui-chat): preserve turn rail follow and preview behavior 2026-09-18 14:13:48 +08:00
imccyu 8f62fc8ebd test(web): separate rail wheel input from keyboard preview assertions 2026-09-18 14:13:48 +08:00
imccyu 0a8702b216 docs: record chat navigation performance and ownership decisions 2026-09-18 14:13:47 +08:00
imccyu 3d360e7fdb perf(session-controller): publish historical jump pages as one batch 2026-09-18 14:13:47 +08:00
imccyu e22b0d75e8 refactor(ui-chat): separate viewport and scroll navigation policies 2026-09-18 14:13:47 +08:00
imccyu f89cd786aa perf(ui-chat): virtualize fixed-pitch turn navigation 2026-09-18 14:13:47 +08:00
Yichen Jiang 235db2a031 Merge remote-tracking branch 'origin/master' into worktree/windows-observational-reuse
# Conflicts:
#	.github/workflows/ci.yml
2026-09-18 14:13:26 +08:00
Tianyi Cui 1dd0ec6ac5 test(web): retain fork snapshot coverage with decorative icons
The release-only mid-turn fork scenario retained the old accessibility
children after master switched common icons to decorative presentation.
Refresh its expected tree from the built scenario: the same top-level
button names, failure text, parent/child prompts, and response remain.
Only redundant icon and label children disappear.

CI reproduced the mismatch. All four fork cases pass after refresh and
again in normal comparison mode. A mechanical comparison confirms that
removing nested children and their now-unused parent colons is the entire
expected-output change. The recorded Session generations and production
code are untouched.
2026-09-18 14:13:20 +08:00
Yichen Jiang 965e2c2995 Merge pull request #4512 from deepseek-harness/worktree/messages-input-history-compat
fix(llm): tolerate assistant blocks in Messages input history
2026-09-18 14:10:47 +08:00
Turtle 5459be6656 ci: halve core-seconds in static and Windows observational lanes (#4521)
* ci: trial smaller Blacksmith static and observational runners

* ci: measure the 16-vCPU control for runner sizing

* ci: use 4-vCPU static and 8-vCPU observational runners

* ci: retain 8-vCPU static runner for completion time
2026-09-18 14:01:46 +08:00
Yif bd6447479d refine(plugin-manager): polish install dialog guide, input, and wizard states 2026-09-18 14:00:50 +08:00
Yichen Jiang a116bf73c6 Merge master and name combined Windows checks 2026-09-18 13:41:06 +08:00
_Kerman b53fda5961 Merge latest master into xtr/tool-card-details 2026-09-18 13:33:54 +08:00
Yif 02bb08ecd9 fix(client): compensate menu fills for the blur the transparent window disables 2026-09-18 13:33:00 +08:00
Yif 8072511aad fix(client): subtract dockkit strip runs and pane bodies from the macOS drag band 2026-09-18 13:32:55 +08:00
Yif e44345c044 test(desktop): stub syncWindowFullscreen in preload-platform mocks 2026-09-18 13:32:53 +08:00
Yichen Jiang 51d8c45413 Merge pull request #4477 from deepseek-harness/worktree/unify-agent-team-bundle
fix(agent-team): enable tools and Web UI with one bundle
2026-09-18 13:21:45 +08:00
Tianyi Cui 4375f6cd83 test(web): align catalog button snapshot with decorative icons
The latest master hides the catalog chevron from the accessibility tree.
The release-only catalog-push snapshot still expected that image child,
although the button retains its complete "2 subagents" accessible name.
Refresh that expected output after the actual built Web scenario; its
recorded Session input and all interaction assertions remain unchanged.

The old expectation failed against the merged application. The refresh
changed only this three-line snapshot, and the normal read-only rerun
passed all 11 subagent conversation cases with every replay call consumed.
2026-09-18 13:21:30 +08:00
Turtle 9d5fc30632 ci: adapt concurrency to available runner CPUs (#4518)
* ci: adapt worker pools to available runner CPUs

* ci: preserve local snapshot worker defaults

* test: isolate CI worker overrides in gate fixtures

* docs: clarify shared Windows worker limits
2026-09-18 13:17:34 +08:00
Yichen Jiang 46f7ab097f refactor(ci): reuse Windows builds for observational checks 2026-09-18 13:14:35 +08:00
Tianyi Cui 8306ddaa14 merge: refresh session-log-v4 base from master
Merge master 0fdadce5189dcf13a00c2ed8293be95b9bb29c2c into the reviewed
Session V4 integration base. Preserve its V4 persistence and direct-catalog
subagent navigation while carrying forward current UI controls, decorative
icons, Desktop signing/runtime changes, and upstream tests.

Resolve lineage conflicts by retaining interaction-time catalog authority,
known-leaf handling and row order together with the shared status icons.
Keep both Desktop shell-routing regressions and one production route; master
now includes the route repair previously carried by this integration branch.

The full build, 41 documentation gates, two Desktop shell tests and 34 focused
Client lineage tests pass. Further built Web and packaging checks precede
publication. Preserve this pinned merge checkpoint before taking a newer
master tip, and propagate it through the reorganized feature stack.
2026-09-18 13:11:25 +08:00
Yichen Jiang 19e6e7b81b test(agent-team): verify browser activation and document bundle composition 2026-09-18 12:53:31 +08:00
Turtle de742f7f45 Prune active Agent Notes and preserve historical archive formatting (#4431) 2026-09-18 04:50:47 +00:00
_Kerman 9ee0d1257b test(session-snapshot): cover source-reference projection directly 2026-09-18 12:44:35 +08:00
Yichen Jiang 5daaaea3c0 Merge pull request #4457 from deepseek-harness/worktree/plan-tab-text-icon
fix(web): align plan sidebar with file previews
2026-09-18 12:41:32 +08:00
Yichen Jiang ae180d1d24 test(llm): document Messages input compatibility limits 2026-09-18 12:40:44 +08:00
Yudong Han fee816628f Merge pull request #4506 from deepseek-harness/fix/office-font-warning
fix(web): move Office font warnings into the document toolbar
2026-09-18 12:34:15 +08:00
Yichen Jiang 231798102a Merge remote-tracking branch 'origin/master' into worktree/unify-agent-team-bundle 2026-09-18 12:32:38 +08:00
Yichen Jiang 732f198cf4 Merge remote-tracking branch 'origin/master' into worktree/plan-tab-text-icon
# Conflicts:
#	apps/web/tests/expected/plan-review/temporary.expected.md
#	packages/client/ui-plan/src/client/PlanPreview.tsx
#	snapshots/web/plan-review/preview.expected.md
2026-09-18 12:31:05 +08:00
Turtle 3164e3c616 ci: right-size Blacksmith runners by workload (#4489)
* ci: right-size Blacksmith runners by workload

* ci: retain static runner capacity supported by utilization

* ci: retain observational runner for peak CPU and memory demand

* ci: restore Windows build to 16 vCPU after runtime regression

* ci: trial 4-vCPU runtime packaging to reduce CPU time allocation

* ci: trial 2-vCPU Linux packaging and serial Windows tests

* ci: include Windows packaging in 2-vCPU trial
2026-09-18 12:28:51 +08:00
Yif fd04c0c4be feat(client): make the macOS sidebar wordmark a window-drag surface
On darwin the expanded brand renders as a plain span instead of the New
Session shortcut button, so it stops matching the global interactive
no-drag rule and inherits the logo row's drag region; the dedicated New
Session button keeps the action.
2026-09-18 12:25:05 +08:00
winewill 1e390797e8 Merge branch 'refactor/desktop-update-domain-config' into perf/mac-packaging-timing 2026-09-18 12:08:57 +08:00
yudshj da37b69fb9 Merge master into fix/office-font-warning 2026-09-18 12:07:58 +08:00
yudshj 19736552c0 fix(web): align Office font warning with shared toolbar styles 2026-09-18 12:03:54 +08:00
Yif b9bc99b675 Merge origin/master (#4451 developer tools setting)
The view tab strip keeps the data-conversation-tabs marker on upstream's
showTabs condition, so the deepened drag band follows the compact header.
2026-09-18 12:01:55 +08:00
std4453 215226753d Merge pull request #4451 from deepseek-harness/feat/developer-tools-preference
feat(client): add shared developer tools setting
2026-09-18 12:00:10 +08:00
Yif 994e998b7d feat(client): deepen the conversation drag band to the header block
While the Conversation is selected and its header shows the view tab strip,
the macOS window drag band grows from 52px to the 76px header block (title
row plus tab strip), so blank header space drags. The band matches the
strip's data-conversation-tabs marker via :has, so a compact header (single
view, per #4451's developer-tools setting) falls back to the 52px strip.
2026-09-18 11:59:45 +08:00
winewill 76a592e3ab refactor(desktop): configure test policy login origins 2026-09-18 11:57:56 +08:00
winewill e0264148b0 perf(desktop): configure macOS packaging concurrency and retain phase logs 2026-09-18 11:54:54 +08:00
fz 4d34cd87dd fix(skill): address shared runtime review findings 2026-09-18 11:52:04 +08:00
_Kerman 449c679892 fix(session-snapshot): expand source references before writing fixtures 2026-09-18 11:47:09 +08:00
Yichen Jiang 82c6a5e4f4 fix(llm): tolerate assistant blocks in Messages input history 2026-09-18 11:44:40 +08:00
fz 3ebd70020c Merge origin/master into feat/workspace-dependencies-package 2026-09-18 11:42:26 +08:00
Yif c8b17dd4d0 refine(plugin-manager): per-plugin artwork, trailing load-failure notice, friendlier copy 2026-09-18 11:38:45 +08:00
Yif d66b9c81b1 fix(desktop): drop superseded preload.ts after merge
Upstream removed the standalone plugin manager and replaced this bridge
with preload-app.ts; the fullscreen sync it carried already lives there.
2026-09-18 11:36:21 +08:00
Yichen Jiang dd8d39c843 Merge remote-tracking branch 'origin/master' into worktree/unify-agent-team-bundle
# Conflicts:
#	packages/experimental/agent-team-profile/package.json
#	packages/experimental/agent-team-web-profile/package.json
2026-09-18 11:34:21 +08:00
07akioni 21c347913d fix(client): preserve sessionless header geometry across platforms 2026-09-18 11:30:37 +08:00
Yif e9def243e2 Merge origin/master into feat/macos-sidebar-ui-and-drag-refine
Keeps both window-chrome features: this branch's macOS shell.leading seat,
single drag owner, and fullscreen sync alongside upstream's Windows desktop
titlebar (#4407). ipc.ts and preload-app.ts merge both platforms' channels
and preload wiring; Tooltip merges upstream's suppression context with this
branch's pointer-focus suppression; slot-catalog regenerated.
2026-09-18 11:27:06 +08:00
07akioni e0c8b1dfca Merge remote-tracking branch 'origin/master' into 07akioni/fix-sessionless-sidebar-controls
# Conflicts:
#	packages/client/ui-conversation/README.i18n.yaml
#	packages/client/ui-conversation/README.md
#	packages/client/ui-conversation/README.zh.md
2026-09-18 11:23:23 +08:00
Yif 8019d37d3f feat(client): frame-owned macOS window chrome with single drag owner
One 52px leadingBand in the AppFrame (frame's first child) plus the sidebar
logoRow are the only -webkit-app-region drag surfaces; everything interactive
subtracts itself through two global base.css rules (interactive elements and
body > :not(#root) overlay portals), so header controls, breadcrumb chips, and
strip tabs stay clickable under the band. A ui-theme stylesheet gate pins the
two-file drag allowlist. The frame publishes --dsh-frame-top-clearance (48px)
for entry pages (plugin manager list and detail) beside the existing
--dsh-frame-leading-clearance, replacing per-page hardcoded insets. The
shell.leading window-chrome seat moves the collapsed-sidebar reopen controls
from the conversation header to the frame. Sidebar vibrancy tint drops the
blue cast in dark mode and halves its fill opacity, light mode softens the
blue mix, and the logo row tightens 4px above New Session.
2026-09-18 11:08:13 +08:00
yudshj 9b27cc0587 fix(web): move Office font warnings into the document toolbar 2026-09-18 11:06:09 +08:00
zhc fb2be52104 fix(client): compact conversation headers without view tabs 2026-09-18 11:03:07 +08:00
zhc 4429a672b7 refactor(client): keep developer preference in settings scope 2026-09-18 11:03:06 +08:00
zhc caaaf89b1e fix(client): separate developer tool policy and harden static previews 2026-09-18 11:03:06 +08:00
zhc 54e6aabf0c test(client): cover developer tools row settings injection 2026-09-18 11:03:06 +08:00
zhc 07e3619ec3 test(web): enable developer tools in trajectory image fixture 2026-09-18 11:03:06 +08:00
zhc 012f993ed4 fix: address developer tools preference review 2026-09-18 11:03:06 +08:00
zhc cdaafbc456 feat(client): add shared developer tools setting 2026-09-18 11:03:05 +08:00
winewill 831ea36f2c Merge pull request #4461 from deepseek-harness/ihsiang/unify-web-ui-visual-language
Unify the Web UI visual language
2026-09-18 11:00:11 +08:00
winewill bf34d10e23 Merge pull request #4455 from deepseek-harness/fix/windows-native-dependency-signing
fix(desktop): sign native dependencies, cache signatures and restore startup dialogs
2026-09-18 09:26:22 +08:00
Tianyi Cui 863ffe83a6 test(desktop): type the application path mock as string
The shell asset regression substitutes a real package path through getAppPath, but the shared test harness inferred only the literal desktop-test-app. Vitest executed the test successfully while the repository TypeScript build rejected the path override.

Declare the mock return type as string to match Electron getAppPath and permit both fixture and real package paths. This changes only test typing; it adds no casts or production behavior. The focused production-routing regression passes after the correction.
2026-09-18 02:00:08 +08:00
Tianyi Cui 64c669fc79 fix(plan): open embedded plan previews in the visible sidebar
A child conversation rendered in a parent Sidebar keeps the child Session identity for reading its plan. Routing navigation through openResourceIn(child) confused that document identity with the layout owner: a child with no adopted Sidebar store silently opened nothing, and an existing child store was invisible.

Use the mounted Sidebar navigation service for both logged and temporary plan previews. Keep the complete child Session resource address, so reads and reload still target the correct document. A real-controller regression adopts only the visible parent store and proves both child entry points populate it without creating child layout state; it fails before the fix.

All 47 ui-plan owner tests and all four built Web plan-review cases pass, along with the full build, scoped typed lint, and bilingual pairing. No Session event, stored document, fixture generation, or resource-address format changed.
2026-09-18 01:56:04 +08:00
Tianyi Cui 13cbe34fc0 docs(subagent): use the registered sidebar chat address
The imported conversation note names a chat resource host that the current parser rejects. The provider is registered under subagentchat and its existing helpers and reference documentation use that host. Correct both language examples without changing addresses, parsing, or persisted layouts.
2026-09-18 01:53:37 +08:00
Tianyi Cui 12e3d45cdc fix(desktop): stop copying the retired management preload
The workspace-update qualification runner still copied lib/preload.cjs after the standalone management preload and its build entry were removed. A clean Desktop build emits only the application, mandatory-update, and update-dialog preloads, so qualification failed with ENOENT before Electron could start; stale outputs could conceal that failure.

Remove only the obsolete copy-list entry. Keep all three current preloads required and retain ordinary copy failures. Executing the actual copy loop against artifacts from a fresh isolated Desktop build now copies exactly those three files byte-for-byte while lib/preload.cjs is absent; the same loop failed at that missing file before this change.
2026-09-18 01:53:37 +08:00
Tianyi Cui 8ae136f402 fix(desktop): serve shell-owned update assets
Ordinary and mandatory update windows still load dsh-app://shell documents after the standalone plugin manager was removed. The production protocol handler handled only the app hostname, so their existing HTML, scripts, and styles returned 404; mandatory-load failure deliberately retains the modal block.

Route the shell hostname to the packaged renderer directory through the existing static-asset helper. Retain its GET/HEAD, path-containment, MIME, and missing-file rules; keep isolated preloads and owned-window IPC authorization unchanged.

The regression imports the real main entry, captures its registered protocol callback, and reads all seven actual update assets through the real static helper. It fails with 404 before the fix and passes afterward, including HEAD, rejected methods, malformed paths, traversal, and unknown resources. Sixteen adjacent static/dialog/overlay tests pass, and the Desktop package builds from a fresh worktree.
2026-09-18 01:53:37 +08:00
Tianyi Cui 371c804336 test(cli): run the headless profile through source and built launchers
The expected-output CI lane sets DSH_EXAMPLE_MODE=lib. Selecting tsx/esm
alone did not exercise the source bin there, and the compatibility test
only checked --help, so removing source profile link resolution could
silently restore duplicate source/lib Tools instances in a green CI run.

Run the existing real headless-profile tool round trip explicitly in both
src and lib modes. The explicit mode overrides the CI environment default;
the source case uses the supported tsx/esm entry. Both cases retain the
real profile, shell tool, persisted Session assertions, stdout, and stderr
expectations, with isolated temporary directories. No recorded generation
or output normalization changes are needed.

After a full build, both cases pass with DSH_EXAMPLE_MODE=lib. Negative
control: removing only the source bin link-resolution selection makes the
source case fail at scheduler.prepare while the built case still passes.
Restoring the implementation makes both cases pass again. Scoped lint
passes. This puts the regression in the existing post-build expected-output
CI lane without adding artifact requirements to compatibility-only jobs.
2026-09-18 01:53:37 +08:00
Tianyi Cui 8b5a78e9ac test(loader-smoke): observe the forwarded source hook in the child
The success fixture can run under either tsx hook, so its previous output
assertions stayed green when runLoaderSmoke stopped forwarding sourceImport.
Line coverage therefore did not protect the supported ESM-only launcher.

Include the actual child process execArgv in the fixture output and assert
the resolved tsx/esm hook requested by the caller. The regression observes
Node launch arguments rather than reproducing the helper implementation.
Align the optional sourceImport spread with the adjacent mode spread;
this keeps the same behavior for defined and absent values.

Negative control: removing forwarding passed the old test but failed the
new assertion because the child loaded tsx/dist/loader.mjs instead of
esm/index.mjs. With forwarding restored, all 25 loader-smoke tests pass
and src/index.ts retains 100% statement, branch, function, and line coverage.
Scoped lint passes.
2026-09-18 01:53:37 +08:00
Tianyi Cui 6205c2be70 test(web): wait for both Subagent settings saves
The shared Subagent card submits the model-selection and limits namespaces
independently. The model-selection write can finish first, so its presence
in settings.yaml does not establish that maxDepth has reached disk. CI
observed subagent: {} with the completed model allowlist at that point.

Move the existing exact-label disabled Save wait before the persistence
assertions. The form uses a distinct Saving label until every namespace
save finishes, so this observes the shared operation's completion before
checking both results. Retain every assertion, timeout, and expected file.

A diagnostic barrier on the real subagent settings mutation reproduced the
original missing-maxDepth failure with both correct requests recorded and
the form still saving. With the relocated wait, releasing that same write
persisted maxDepth without another user action. The barrier run passed all
10 cases, and the unchanged whole plugin-config Web suite also passed 10/10.
2026-09-18 01:41:08 +08:00
winewill 6088ac7a85 perf(desktop): cache verified Windows signatures and isolate timestamps 2026-09-18 01:15:59 +08:00
Tianyi Cui 335c5cbe26 fix(cli): keep source profile imports in one module graph
The runtime profile default is correct for installed applications, but the
supported TypeScript CLI also runs tsx workspace aliases. In that launch,
profile resolution loaded both packages/core/tools/src/index.ts and
packages/core/tools/lib/index.js. The ToolRuntime instance and agent-loop
consumer therefore held different private TOOL_RUNTIME_SCHEDULER symbols;
the first tool call failed while reading scheduler.prepare.

The source bin now explicitly selects linked profile resolution using its
own .ts module identity. Built and packaged launches keep runtime resolution,
and explicit runProfile overrides remain unchanged. No service or symbol
identity is widened to conceal duplicate module instances.

Forward the existing sourceImport option through runLoaderSmoke and select
tsx/esm for headless source snapshots. This exercises the actual supported
source-launch vector while retaining the same recorded Session and expected
outputs. The loader-smoke subprocess test covers forwarding that hook.

Evidence: the official tsx/esm compaction replay failed before the fix while
loading both Tools module URLs. After the fix it loads only src/index.ts and
prints COMPACTION RECOVERED. The same focused replay passes under both
DSH_EXAMPLE_MODE=src and lib after rebuilding Host artifacts. The 43 CLI and
loader tests passed, including runtime defaults and explicit link/dual/runtime
overrides. Loader-smoke coverage passed 25 tests with 100% statements,
branches, functions, and lines. Host build, full lint, and all 41 doc-sync
gates passed. Recorded JSONL generations and snapshot normalization are
unchanged; temporary diagnostics were removed from built artifacts.
2026-09-18 00:06:26 +08:00
Tianyi Cui 7e42e8aa65 fix(release): align V4 migration package with master version
The pinned master merge updates the DSH package family to 0.1.6-alpha.2, but the release-only V3-to-V4 migration package has no master counterpart to carry that manifest change. Match the family package version so installed artifacts and hygiene describe one release identity. Session format remains V4; migration schemas, committed generations, and snapshot input versions do not change.

The combined source passes the full build, 93 Client tests, 631 persistence tests with one skip, all 41 documentation gates, all 16 hygiene gates, and full lint. The original pinned-master merge checkpoint remains in history.
2026-09-17 23:28:32 +08:00
Tianyi Cui d66b21894c merge: integrate latest master into session-log-v4
Merge master 62b700bba7bb71e66d2d5a3b850ba8f4dd0fd6b4 into release/session-log-v4 at 4825bc01a999f564e79172a6af28e34fc757c540. Preserve this pinned integration checkpoint before any later upstream movement so the dependent session-format repairs have an immutable rebase base.

Keep release projection-backed child catalogs, independent Session references, exact fork behavior, V4 writer, and existing migration data. Adapt the incoming subagent sidebar chat to refreshProjections rather than the removed dedicated catalog API, preserve title projections in complete projection fixtures, and retain both sidebar actions and release lineage/status behavior. Remove the duplicate SessionProjectionMap import introduced by automatic merging.

Preserve both disjoint archive seals without changing any existing hash. Regenerate current Cordis and documentation catalogs from the combined source, retaining release session methods plus master slot factories. The frozen V0-V3 runtime paths are unchanged. Initial validation passed the full build, 93 focused Client tests, and all 1905 archived artifact seals; remaining integration and snapshot checks follow on this checkpoint.
2026-09-17 23:23:48 +08:00
Tianyi Cui b6156b90fe Merge pull request #4389 from deepseek-harness/worktree/merge-master-session-v4-20260917-2
merge: sync master into session-log-v4
2026-09-17 23:03:02 +08:00
yixiangihsiang 0f7a507fbd test(web): assert stopped summary tone 2026-09-17 21:41:12 +08:00
winewill aa181c66e5 fix(desktop): preserve unpacked PE files and target runtime descriptors 2026-09-17 21:31:34 +08:00
winewill 702f9e4ff1 Restore desktop shell dialogs and guard destroyed windows on exit 2026-09-17 21:31:33 +08:00
winewill 3d2876f046 Run packaged smoke with production profile resolution 2026-09-17 21:31:32 +08:00
winewill 4d46bff152 Normalize dependency metadata before sealing desktop runtime 2026-09-17 21:31:32 +08:00
winewill 5a409fc771 fix(desktop): sign all Windows native dependencies before packaging 2026-09-17 21:31:31 +08:00
yixiangihsiang 938436e7bc fix(web): preserve tool-specific stopped summaries 2026-09-17 21:28:44 +08:00
yixiangihsiang 870388e2c0 test(web): refresh file link snapshot 2026-09-17 21:17:02 +08:00
fz be8040d042 test(sdk): align Office checks with runtime profile resolution 2026-09-17 21:15:55 +08:00
fz ef4a7efa91 Merge origin/master into feat/workspace-dependencies-package 2026-09-17 21:11:58 +08:00
fz 8fad9ccd8c feat(skill): complete shared Office runtime builds for SDK carriers 2026-09-17 21:10:12 +08:00
yixiangihsiang 488e438277 fix(web): address review feedback 2026-09-17 21:04:42 +08:00
Yichen Yan e699b482fe Merge branch 'yyc/web-trust-domain-reviewed' into yyc/web-relative-routes-reviewed 2026-09-17 20:56:41 +08:00
Yichen Yan 8256c84630 chore(docs): regenerate the Cordis catalog after merging master 2026-09-17 20:48:53 +08:00
Yichen Jiang 9f21d7842a fix(agent-team): enable tools and Web UI with one bundle 2026-09-17 20:41:39 +08:00
Yichen Yan 8e93da68f3 test(web): pin route-gate violations by line and match feature routes exactly
The gate spec asserts line and rule per violation instead of counts; the
open-in-app fake carriers match the relative route exactly so a re-absolutized
route cannot pass; the root and mount cases of the markdown file route share
one table; a fixture echo of its own icon URL is dropped.
2026-09-17 20:24:49 +08:00
Yichen Yan d607e185ad Merge branch 'yyc/web-trust-domain-reviewed' into yyc/web-relative-routes-reviewed 2026-09-17 20:21:54 +08:00
Yichen Jiang 02dbcc3ef3 Merge remote-tracking branch 'origin/master' into feat/plugin-detail-slots 2026-09-17 20:17:19 +08:00
Yichen Yan 9ed3d642e1 test(web): tighten the public-mount fence and drop unused fixture branches
Any request outside the mount is an escape, not only same-origin ones; the
socket presence check is superseded by the frame polls; the redirect landing
is asserted by pathname. The scaffold's unused mount host option, hostFetch
mount routing, and the proxy's bare-mount redirect had no caller.
2026-09-17 20:16:58 +08:00
Yichen Jiang caa0c99be1 docs(web): follow up the review on the companion move and detail slots
The maintained docs now name the four companion packages where they
still credited `ui-settings-plugins`: the web-app row comment, the
Plugins page README, the ui-settings README, and the client group
README, which gains a row per companion. The ui-settings README states
the `whileServed` lifetime and disposer ownership beside `bind`, and the
JSDoc says the caller owns the disposer. The settings-page cookbook
keeps the one home of the lazy-CJS build limitation for packages outside
this repository, its JSX example is a tsx fence, and the architecture
index and website label follow the page's title. The Agent Note records
the shell page's `plugins.item` id, the two companions that use
`ctx.remote` list api-remotes in `dsh.client.inject`, and the moved form
model test speaks of the form.
2026-09-17 20:13:26 +08:00
Yichen Jiang fd01a407b2 test(web): update plan preview snapshots after removing copy button 2026-09-17 20:12:42 +08:00
Yichen Yan b62a9f5c27 Merge branch 'master' into yyc/web-trust-domain-reviewed 2026-09-17 20:09:23 +08:00
Yichen Jiang 29126c0a32 fix(web): align plan preview copy controls with Markdown files 2026-09-17 19:57:18 +08:00
07akioni 1628e911c8 fix(client): keep sidebar navigation available without a session 2026-09-17 19:38:57 +08:00
yixiangihsiang 4937343a5e feat(web): unify the client visual language 2026-09-17 19:14:41 +08:00
fz 6e49ccad18 feat(skill): extract the workspace-dependencies tool into a package
Move `load_workspace_dependencies` and the primary-runtime payload logic out
of `apps/desktop-host` into `@deepseek-ai/dsh-tool-workspace-dependencies`
so profiles other than Desktop can mount the same tool. Desktop keeps its
behavior through the package.

Two additions for carriers that are not Electron applications:

- `root` is optional: without it the payload is validated and used in place
  (no copy under the Harness home), which suits read-only container layers.
- `runtime.json` accepts `linux`, and the `node` / `pnpm` components are
  optional; the returned paths and the tool output omit what the payload
  does not ship.

The `sdk` profile mounts the tool and `@deepseek-ai/dsh-skill-office` when
`DSH_PRIMARY_RUNTIME` names a payload directory (its sibling `office-skills/`
carries the skill resources); both rows stay disabled otherwise. Both
packages join the Python runtime closure.
2026-09-17 18:16:47 +08:00
Yichen Jiang 93e67201dd refactor(web): move the official settings pages into companion packages
Each official settings page now lives in a client package of its own:
`ui-settings-shell`, `ui-settings-agent-loop`, `ui-settings-subagent`
and `ui-settings-web-search` register into `plugins.item` while the Host
serves their namespaces, through the new `settingsScope.whileServed`
watch, each with its own dictionary and styles. The form model, the
fields and the form move to `ui-primitives` as the shared settings kit,
so a community package's browser half builds a page the same way.
`ui-settings-plugins` keeps only the Built-in plugins section shell
around the inventory tab.

The companions hold Loader rows in `web-app` behind the section; the
client catalog, config catalog and module graph regenerate; the
settings-card cookbook walks through a companion package.
2026-09-17 17:59:12 +08:00
Yichen Jiang a815a7f2a9 feat(web): open action, badge and section slots on plugin detail pages
The Plugins page declares three list slots as children of its main
entry: `plugins.detail.actions` renders at the head of a detail page
before its switch and uninstall controls, `plugins.detail.badge` after
the tags beside the title, and `plugins.detail.section` below the page's
own content. Every entry receives a `subject` owner prop naming what the
open page is about — a bundle, one row of a bundle, or an official item —
so one entry serves any page and renders nothing elsewhere. A row's page
still opens only for rows with a `plugins.row.config` entry.

The `plugin-config` web lane drives the three slots through the live
fixture on its bundle page and its row page, records a new bundle
golden, and shows the fixture contributing nothing to an official page.
2026-09-17 17:58:29 +08:00
Yichen Jiang 0495ad9846 fix(web): use the plain text file icon for plan tabs 2026-09-17 17:52:08 +08:00
Yichen Jiang 3d345d27bc Merge branch 'worktree/jobs-api-review-3052' into worktree/jobs-api-review-3127 2026-09-17 16:52:55 +08:00
Yichen Jiang 0d734a8fe7 fix(jobs): adapt desktop update checks to session callers 2026-09-17 16:52:09 +08:00
Yichen Jiang de617e2d0b Merge branch 'worktree/jobs-api-review-3052' into worktree/jobs-api-review-3127 2026-09-17 16:50:14 +08:00
Yichen Jiang 4b299845e0 Merge remote-tracking branch 'origin/master' into worktree/jobs-api-review-3052 2026-09-17 16:49:48 +08:00
Yichen Jiang c2fb2bd4e8 Merge branch 'worktree/jobs-api-review-3052' into worktree/jobs-api-review-3127
# Conflicts:
#	.agents/notes/implemented/feature/2026-08-08-web-background-job-display.i18n.yaml
#	docs/config-catalog.i18n.yaml
#	docs/event-producer-consumer.i18n.yaml
#	docs/module-graph.i18n.yaml
#	docs/subsystems/jobs.i18n.yaml
#	docs/subsystems/shell.i18n.yaml
#	docs/subsystems/shell.md
#	docs/subsystems/shell.zh.md
#	docs/tool-catalog.i18n.yaml
#	packages/api/session-controller/README.i18n.yaml
#	packages/context/tmux-context/tests/tmux-context.spec.ts
#	packages/e2b/e2b/tests/fixtures/composition/bin.ts
#	packages/extensions/tool-cordis/src/api-catalog.ts
#	packages/shell/bash-local/README.i18n.yaml
#	packages/shell/bash-local/README.md
#	packages/shell/bash-local/README.zh.md
#	packages/shell/bash-local/src/index.ts
#	packages/shell/bash-local/tests/executor.spec.ts
#	packages/shell/bash-sandbox/README.i18n.yaml
#	packages/shell/bash-sandbox/README.md
#	packages/shell/bash-sandbox/README.zh.md
#	packages/shell/bash-sandbox/src/index.ts
#	packages/shell/bash-sandbox/tests/bwrap.e2e.ts
#	packages/shell/bash-sandbox/tests/landlock.e2e.ts
#	packages/shell/bash-sandbox/tests/partial-landlock.spec.ts
#	packages/shell/bash-sandbox/tests/sandbox.spec.ts
#	packages/shell/bash-sandbox/tests/seatbelt.e2e.ts
#	packages/shell/pwsh-local/README.i18n.yaml
#	packages/shell/pwsh-local/README.md
#	packages/shell/pwsh-local/README.zh.md
#	packages/shell/pwsh-local/src/index.ts
#	packages/shell/pwsh-local/tests/executor.spec.ts
#	packages/shell/pwsh-sandbox/README.i18n.yaml
#	packages/shell/pwsh-sandbox/README.md
#	packages/shell/pwsh-sandbox/README.zh.md
#	packages/shell/pwsh-sandbox/src/index.ts
#	packages/shell/pwsh-sandbox/tests/sandbox.spec.ts
#	packages/shell/shell/README.i18n.yaml
#	packages/shell/shell/README.md
#	packages/shell/shell/README.zh.md
#	packages/shell/shell/src/index.ts
#	packages/shell/shell/tests/service.spec.ts
#	packages/shell/tool-bash/README.i18n.yaml
#	packages/shell/tool-bash/src/index.ts
#	packages/shell/tool-bash/tests/tools.spec.ts
#	packages/shell/tool-pwsh/README.i18n.yaml
#	packages/shell/tool-pwsh/src/index.ts
#	packages/shell/tool-pwsh/tests/background.spec.ts
#	packages/shell/tool-pwsh/tests/tools.spec.ts
#	packages/workflow/tool-workflow/README.i18n.yaml
#	packages/workflow/tool-workflow/package.json
#	packages/workflow/tool-workflow/tests/tool-workflow.spec.ts
#	snapshots/session/ptc-python-turn/system-prompt.expected.md
2026-09-17 16:48:06 +08:00
Kaige-Gao 9ce43f925c feat(web): add feedback to session header menu 2026-09-17 16:40:13 +08:00
Yichen Jiang 5ad77a9532 Merge remote-tracking branch 'origin/master' into worktree/jobs-api-review-3052
# Conflicts:
#	packages/api/remotes/src/client/index.ts
2026-09-17 16:30:09 +08:00
Yichen Jiang d2b277286f refactor(jobs): drop unrelated client refactors 2026-09-17 16:24:12 +08:00
Yichen Jiang 80c0a5c1e8 test(voice-input): complete waking state fixture 2026-09-17 16:05:13 +08:00
Yichen Jiang 9644155c4e fix(voice-input): handle capture failures and queued wakeups 2026-09-17 16:03:02 +08:00
Yichen Yan 29182514ed fix(web): resolve feature routes from the document directory and gate browser routes 2026-09-17 16:00:36 +08:00
Yichen Yan eeb9b03465 feat(web): serve the shell, API and plugin resources from the document directory 2026-09-17 16:00:15 +08:00
Yichen Jiang f3644dcdf3 fix(voice-input): keep waveform below recording controls 2026-09-17 15:37:07 +08:00
Yichen Jiang 0055d993fc docs(voice-input): clarify setup and cover microphone permissions 2026-09-17 15:24:49 +08:00
Yichen Jiang 5ccf1c57d2 fix(voice-input): align CI fixtures and source-only coverage 2026-09-17 14:23:25 +08:00
Yichen Jiang 7e12403d84 fix(voice-input): reduce recording action icon sizes 2026-09-17 14:11:11 +08:00
Yichen Jiang 018c0e5156 fix(voice-input): align dictation toolbar controls 2026-09-17 13:57:51 +08:00
Yichen Jiang 8d37a43316 Merge remote-tracking branch 'origin/master' into worktree/experimental-voice-input 2026-09-17 13:45:00 +08:00
Yichen Jiang 9d0f475a47 fix(voice-input): keep preparation status in plugin details 2026-09-17 13:33:38 +08:00
Yichen Jiang 23d21e2fde fix(voice-input): restore cached readiness and preserve composer controls 2026-09-17 13:28:26 +08:00
Yichen Jiang 8d62caf7fa Merge remote-tracking branch 'origin/master' into worktree/experimental-voice-input 2026-09-17 12:40:38 +08:00
Yichen Jiang 47ae64ee68 feat(voice-input): add optional local SenseVoice dictation 2026-09-17 12:39:53 +08:00
Tianyi Cui 85022b5c46 merge: sync Office conversion from latest master 2026-09-17 12:33:52 +08:00
_Kerman 83b2ff7aa1 fix(client): preserve tool detail navigation and layout 2026-09-17 12:04:19 +08:00
_Kerman f32c0cc3d9 refactor(client): simplify recorded tool detail models 2026-09-17 12:03:41 +08:00
Tianyi Cui 490a79c693 fix(client): release replaced sidebar store adoptions 2026-09-17 11:57:25 +08:00
_Kerman 566be5ae1b Merge latest origin/master into xtr/tool-card-details 2026-09-17 11:35:39 +08:00
_Kerman 68825ecc57 Merge origin/master into xtr/tool-card-details 2026-09-17 11:32:23 +08:00
Tianyi Cui 2fb2a5cc65 merge: sync desktop menus and startup diagnostics from master 2026-09-17 11:31:06 +08:00
_Kerman b91c96c5ee fix(client): avoid duplicate expanded tool receipt statuses 2026-09-17 11:21:33 +08:00
_Kerman cba87e84d5 fix(client): distinguish recorded tool status colors 2026-09-17 11:21:11 +08:00
Tianyi Cui 27389e0899 fix(client): preserve unmounted settings and composer ownership 2026-09-17 11:19:07 +08:00
Yichen Jiang 321bce2627 Merge remote-tracking branch 'origin/master' into worktree/jobs-api-review-3052
# Conflicts:
#	packages/api/session-controller/src/client/sessions/manager.ts
#	packages/api/session-controller/src/client/sessions/service.ts
#	packages/api/session-controller/tests/manager.client.spec.ts
#	packages/api/session-controller/tests/sessions-service.client.spec.ts
#	packages/client/locale/tests/language-row.client.spec.tsx
#	packages/client/ui-chat/tests/chat-view.client.spec.tsx
#	packages/client/ui-chat/tests/transcript-view-row.client.spec.tsx
#	packages/client/ui-conversation/tests/conversation-registry.client.spec.ts
#	packages/client/ui-conversation/tests/enter-behavior-row.client.spec.tsx
#	packages/client/ui-conversation/tests/input-bar.client.spec.tsx
#	packages/client/ui-conversation/tests/skeleton.client.spec.tsx
#	packages/client/ui-deliverables/tests/deliverables.client.spec.tsx
#	packages/client/ui-jobs/tests/job-list-action.client.spec.tsx
#	packages/client/ui-layout/tests/app-frame.client.spec.tsx
#	packages/client/ui-layout/tests/document-title.client.spec.tsx
#	packages/client/ui-session/tests/ui-session.client.spec.ts
#	packages/client/ui-settings-unarchive-sessions/tests/components.client.spec.tsx
#	packages/client/ui-subagent/tests/conversation-ui.client.spec.tsx
#	packages/client/ui-theme/tests/appearance-row.client.spec.tsx
#	packages/client/ui-theme/tests/font-size-row.client.spec.tsx
#	packages/client/ui-tool/tests/coverage-tails.client.spec.tsx
#	packages/client/ui-tool/tests/diff-card.client.spec.tsx
#	packages/client/ui-tool/tests/read-card.client.spec.tsx
#	packages/client/ui-tool/tests/terminal-card.client.spec.tsx
#	packages/client/ui-trajectory/tests/views.client.spec.tsx
#	packages/client/ui-user-questions/tests/plan-review-panel.client.spec.tsx
#	packages/client/ui-user-questions/tests/user-questions-composer.client.spec.tsx
#	packages/client/ui-workflow-run/tests/workflow-run.client.spec.tsx
#	packages/client/ui-workspace/tests/tree.client.spec.ts
#	packages/client/ui-workspace/tests/workspace-browser.client.spec.tsx
#	packages/client/ui-workspace/tests/workspace-picker.client.spec.tsx
#	packages/client/ui-workspace/tests/workspaces-service.client.spec.ts
#	packages/test-support/client-runtime/src/sessions.ts
2026-09-17 11:11:25 +08:00
Tianyi Cui c840ac431d fix(client): settle preset selections and isolate generation stores 2026-09-17 10:43:21 +08:00
Tianyi Cui fa3e5cbaf6 fix(client): preserve state across Session reference updates 2026-09-17 09:38:24 +08:00
Tianyi Cui 5c25efba91 merge: integrate Client Session references into session-log-v4 2026-09-17 08:59:42 +08:00
Tianyi Cui a23331ba17 Merge pull request #4382 from deepseek-harness/worktree/session-log-v4-master-4304-20260917
merge: integrate subagent capacity settings into session-log-v4
2026-09-17 01:23:33 +08:00
Tianyi Cui 2d94201ef1 merge: integrate subagent capacity settings into session-log-v4 2026-09-17 01:14:07 +08:00
Tianyi Cui 5872c51d2b Merge pull request #4381 from deepseek-harness/worktree/merge-master-into-session-log-v4-20260916
merge: sync master into session-log-v4
2026-09-17 00:50:45 +08:00
Tianyi Cui 038c36b138 merge: update master integration with released fork support 2026-09-17 00:40:09 +08:00
Dudu-0223 22b942bb19 Merge pull request #2687 from deepseek-harness/feat/session-fork-arbitrary-seq
feat(session): fork exact event prefixes with synthetic tail results
2026-09-17 00:31:22 +08:00
Tianyi Cui d97a2e858e test(snapshot): align imported MCP fixture with release tool prose 2026-09-17 00:26:47 +08:00
Tianyi Cui 0a1e2691ab merge: integrate master into session-log-v4 2026-09-17 00:12:27 +08:00
Tianyi Cui 8696ec6cef feat(session): fork exact event prefixes with synthetic tail results 2026-09-17 00:00:44 +08:00
Tianyi Cui 88229fac33 Merge pull request #4378 from deepseek-harness/worktree/fix-release-windows-coverage-20260916
test(windows): avoid taskkill after descendant exit
2026-09-16 23:56:09 +08:00
Tianyi Cui 1ccef293fa fix(tests): keep Windows Job fixture cleanup with its owner 2026-09-16 23:29:08 +08:00
Dudu-0223 e2107a0394 Merge pull request #4311 from deepseek-harness/feat/parent-catalog-consumers-rebuilt
feat(subagent): migrate direct catalog consumers to parent projections
2026-09-16 23:12:01 +08:00
Yichen Jiang 3c394440ab Merge remote-tracking branch 'origin/master' into worktree/jobs-api-review-3052
# Conflicts:
#	docs/event-producer-consumer.i18n.yaml
#	docs/event-producer-consumer.md
#	docs/event-producer-consumer.zh.md
#	docs/persistence-catalog.i18n.yaml
#	docs/persistence-catalog.md
#	docs/persistence-catalog.zh.md
#	docs/persistence-schema.json
2026-09-16 22:05:51 +08:00
Dudu-0223 e55093b47d feat(subagent): migrate direct catalog consumers to parent projections 2026-09-16 22:05:17 +08:00
Dudu-0223 1d1ae1a5af fix(session): migrate historical subagent catalogs to V4 2026-09-16 22:00:38 +08:00
creatixchu dcf2b67896 feat(web): open Sidebar-previewed files in the default application
The document preview declares two child slots handing the previewed file
and its Host path to other plugins: the header actions after its own
controls, and the empty state of a file it cannot render, where Retry stood.
Empty-state read failures now name their recourse: not-text and too-large
offer the handoff, not-found and non-regular paths offer nothing, and the
rest keep Retry. The unsupported-suffix empty state offers the same seat.

ui-open-in-app fills both seats with an "Open" split button (default
application, or the file manager reveal from its menu) and an empty-state
"Open in default app" button. Both call the Session Remote the Host already
publishes: canOpenWorkspacePath once per page decides whether they render,
openWorkspacePath runs each gesture. Only the pressed control disables while
its gesture settles; a failure announces once through a transient toast.

The delivery cards keep their own opener this iteration. The design, slot
pair, failure classification, and toast rule come from Yifffan's PR #4120.
2026-09-16 21:00:57 +08:00
Yichen Jiang 55d57d33b6 fix(session): initialize blankness from retained metadata 2026-09-16 20:22:14 +08:00
creatixchu a4493ceb39 fix(deliverables): scroll the split review columns together on both axes
Each unwrapped split column is its own scroll container, and only the
horizontal offset was mirrored onto the other side, so wheel-scrolling one
column left the other behind and the paired rows drifted apart. Mirror the
vertical offset the same way.
2026-09-16 20:15:40 +08:00
Yichen Jiang f65799a236 docs(session): clarify blank-state projection guarantees 2026-09-16 20:05:48 +08:00
creatixchu f92f984d3d docs(notes): propose one card per delivered changed file 2026-09-16 19:48:48 +08:00
Yichen Jiang fa2abdd6f1 fix(web): reconcile stale blank session hints from projections 2026-09-16 16:39:53 +08:00
Yichen Jiang 6e41857b2e Merge remote-tracking branch 'origin/master' into worktree/jobs-api-review-3052
# Conflicts:
#	docs/event-producer-consumer.i18n.yaml
#	docs/event-producer-consumer.md
#	docs/event-producer-consumer.zh.md
#	packages/api/remotes/src/client/index.ts
#	packages/api/session-controller/README.i18n.yaml
#	packages/api/session-controller/README.md
#	packages/api/session-controller/README.zh.md
#	packages/api/session-controller/src/client/sessions/manager.ts
#	packages/api/session-controller/src/control.ts
#	packages/api/session-controller/src/types.ts
#	packages/api/session-controller/tests/client-apply.client.spec.ts
#	packages/api/session-controller/tests/manager.client.spec.ts
#	packages/api/session-controller/tests/projection-store.client.spec.ts
#	packages/api/session-controller/tests/queue-store.client.spec.ts
#	packages/api/session-controller/tests/sessions-service.client.spec.ts
#	packages/api/session-controller/tests/transport.client.spec.ts
#	packages/client/ui-jobs/package.json
#	packages/client/ui-primitives/src/TerminalBlock.module.css
#	packages/client/ui-primitives/src/TerminalBlock.tsx
#	packages/client/ui-primitives/tests/terminal-block.client.spec.tsx
#	packages/extensions/tool-cordis/src/api-catalog.ts
#	packages/test-support/client-runtime/src/assembly/remote-default-responses.ts
2026-09-16 16:05:59 +08:00
Yichen Jiang 1596097147 Merge remote-tracking branch 'origin/master' into worktree/jobs-api-review-3052
# Conflicts:
#	apps/web/tsconfig.json
#	docs/event-producer-consumer.i18n.yaml
#	docs/event-producer-consumer.md
#	docs/event-producer-consumer.zh.md
#	docs/module-graph.i18n.yaml
#	docs/module-graph.md
#	docs/module-graph.zh.md
#	packages/api/remotes/package.json
#	packages/api/remotes/src/client/index.ts
#	packages/api/session-controller/tests/client-apply.client.spec.ts
#	packages/api/session-controller/tests/control-jobs.host.spec.ts
#	packages/api/session-controller/tests/fake-api.client.ts
#	packages/api/session-controller/tests/manager.client.spec.ts
#	packages/client/ui-jobs/package.json
#	packages/extensions/cordis-client-runner/src/client/slot-catalog.ts
#	packages/jobs/jobs-local/README.i18n.yaml
#	packages/jobs/jobs-local/README.md
#	packages/jobs/jobs-local/README.zh.md
#	packages/jobs/jobs-local/src/index.ts
#	packages/jobs/jobs-local/tests/jobs.spec.ts
#	packages/jobs/tool-jobs/tests/tool-jobs.spec.ts
#	packages/shell/shell/README.i18n.yaml
#	packages/shell/shell/README.md
#	packages/shell/shell/README.zh.md
#	packages/shell/tool-bash/README.i18n.yaml
#	packages/shell/tool-bash/README.md
#	packages/shell/tool-bash/README.zh.md
#	packages/shell/tool-bash/src/background.ts
#	packages/shell/tool-bash/src/index.ts
#	packages/shell/tool-pwsh/src/background.ts
#	packages/shell/tool-pwsh/src/index.ts
2026-09-16 15:53:02 +08:00
Tianyi Cui 3bde1a56e3 fix(test): prepare preview generations and preserve embedded stream timing 2026-09-16 13:46:05 +08:00
Tianyi Cui 8a46149577 fix(test): validate retained V3 layouts and current reference captures 2026-09-16 13:21:19 +08:00
Tianyi Cui 9c10de50a6 fix(test): compare native Python delivery generations without rewriting goldens 2026-09-16 13:02:13 +08:00
Tianyi Cui d3dbcfe710 Merge master into release/session-log-v4 2026-09-16 12:58:34 +08:00
Tianyi Cui 669b724a78 feat(session): add the V4 integration format and retain V3 replay inputs 2026-09-16 12:50:34 +08:00
Tianyi Cui 5e4cfe670f chore(session): initialize V4 release integration PR 2026-09-16 12:16:01 +08:00
_Kerman 2832077b2c test(web): refresh tool details golden 2026-09-11 11:32:52 +08:00
_Kerman 6744f21a43 test(client): cover invalid detail locales 2026-09-11 11:20:25 +08:00
_Kerman 27e4529d15 fix(client): guard detail date localization 2026-09-11 11:18:01 +08:00
_Kerman bd16c3c89e fix(client): complete compact tool details review 2026-09-11 11:14:32 +08:00
_Kerman b59d3605b7 fix(client): address compact tool details review 2026-09-10 18:29:12 +08:00
_Kerman 10029b995f feat(web): add compact details to recorded tool rows 2026-09-10 17:18:49 +08:00
Yichen Jiang 72e6329267 Merge branch 'worktree/jobs-api-review-3052' into worktree/jobs-api-review-3127
# Conflicts:
#	packages/api/session-controller/README.i18n.yaml
#	packages/api/session-controller/README.zh.md
#	packages/shell/bash-sandbox/README.i18n.yaml
#	packages/shell/bash-sandbox/README.zh.md
#	packages/shell/pwsh-local/README.i18n.yaml
#	packages/shell/pwsh-local/README.zh.md
#	packages/shell/pwsh-sandbox/README.i18n.yaml
#	packages/shell/pwsh-sandbox/README.zh.md
#	packages/shell/shell/README.i18n.yaml
#	packages/shell/shell/README.zh.md
#	packages/shell/tool-bash/README.i18n.yaml
#	packages/shell/tool-bash/README.zh.md
#	packages/workflow/tool-workflow/README.i18n.yaml
#	packages/workflow/tool-workflow/README.zh.md
2026-09-10 14:09:55 +08:00
Yichen Jiang 1bd194ac17 Merge remote-tracking branch 'origin/master' into worktree/jobs-api-review-3052
# Conflicts:
#	docs/event-producer-consumer.i18n.yaml
#	docs/event-producer-consumer.md
#	packages/api/README.i18n.yaml
#	packages/api/README.zh.md
#	packages/api/remotes/src/client/index.ts
#	packages/api/session-controller/README.i18n.yaml
#	packages/api/session-controller/README.zh.md
#	packages/client/README.i18n.yaml
#	packages/client/README.zh.md
#	packages/client/ui-jobs/README.i18n.yaml
#	packages/client/ui-jobs/README.md
#	packages/client/ui-jobs/README.zh.md
#	packages/client/ui-jobs/package.json
#	packages/jobs/jobs-local/README.i18n.yaml
#	packages/jobs/jobs-local/README.md
#	packages/jobs/jobs-local/README.zh.md
#	packages/jobs/jobs/README.i18n.yaml
#	packages/jobs/jobs/README.md
#	packages/jobs/jobs/README.zh.md
#	packages/jobs/tool-jobs/README.i18n.yaml
#	packages/jobs/tool-jobs/README.zh.md
#	packages/shell/bash-local/README.i18n.yaml
#	packages/shell/bash-local/README.zh.md
#	packages/shell/pwsh-local/README.i18n.yaml
#	packages/shell/pwsh-local/README.zh.md
#	packages/shell/shell/README.i18n.yaml
#	packages/shell/shell/README.zh.md
#	packages/shell/tool-pwsh/README.i18n.yaml
#	packages/shell/tool-pwsh/README.zh.md
2026-09-10 14:06:59 +08:00
Yichen Jiang 2e0106498e Merge branch 'worktree/jobs-api-review-3052' into worktree/jobs-api-review-3127 2026-09-09 14:53:12 +08:00
Yichen Jiang a8fc15ec85 test(jobs): align built codec smoke with remote method names 2026-09-09 14:52:55 +08:00
Yichen Jiang 4b1db738f5 test(bash): control promotion deadline after output readiness 2026-09-09 14:50:59 +08:00
Yichen Jiang dd7ea38578 fix(tests): adapt spill replay to unified shell execution 2026-09-09 14:46:41 +08:00
Yichen Jiang ab6dacc80e Merge branch 'worktree/jobs-api-review-3052' into worktree/jobs-api-review-3127
# Conflicts:
#	packages/shell/pwsh-local/tests/executor.spec.ts
#	packages/shell/shell/README.i18n.yaml
#	packages/shell/shell/README.md
#	packages/shell/shell/README.zh.md
#	packages/shell/tool-bash/README.i18n.yaml
#	packages/shell/tool-bash/README.md
#	packages/shell/tool-bash/README.zh.md
#	packages/workflow/tool-workflow/README.i18n.yaml
#	packages/workflow/tool-workflow/README.md
#	packages/workflow/tool-workflow/README.zh.md
2026-09-09 14:44:14 +08:00
Yichen Jiang b211c87150 Merge remote-tracking branch 'origin/master' into worktree/jobs-api-review-3052
# Conflicts:
#	apps/web/tests/agent-preset-selection.e2e.ts
#	apps/web/tests/expected/agent-preset-selection/header.expected.md
#	docs/event-producer-consumer.i18n.yaml
#	docs/event-producer-consumer.md
#	docs/module-graph.i18n.yaml
#	docs/module-graph.md
#	docs/module-graph.zh.md
#	docs/persistence-catalog.i18n.yaml
#	docs/persistence-catalog.zh.md
#	packages/api/session-controller/README.i18n.yaml
#	packages/api/session-controller/README.md
#	packages/api/session-controller/README.zh.md
#	packages/api/session-controller/package.json
#	packages/client/ui-jobs/package.json
#	packages/jobs/jobs/README.i18n.yaml
#	packages/jobs/jobs/README.md
#	packages/jobs/jobs/README.zh.md
#	pnpm-lock.yaml
2026-09-09 14:42:11 +08:00
Yichen Jiang 9addbf63e6 merge: adapt job controls to the parent registry API 2026-09-08 15:48:59 +08:00
Yichen Jiang 3df99217dc refactor(jobs): restore direct calls and align remote names 2026-09-08 15:44:48 +08:00
Yichen Jiang 29418faf62 fix(shell): settle a live handle's post-termination rejection as its outcome
The subprocess runtime rejects a handle's done with the cancellation reason
when it terminated the managed range before the target started, which the
Windows runner does whenever an abort or kill() races the launch. Both
executors filed that rejection as a provider failure, so result() re-threw
the caller's own AbortError instead of resolving the aborted or killed
classification. A live handle whose rejection follows this execution's own
kill() or spawn-signal abort now settles as that terminal outcome; a
synchronous spawn throw, which never produced a handle, stays a failure.
2026-09-08 14:45:41 +08:00
Yichen Jiang 9a8ea5eaf1 Merge branch 'worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery 2026-09-08 14:39:29 +08:00
Yichen Jiang f2f696c358 test(bash-sandbox): expect the merged provider-failure note for a synchronous ENOEXEC
The background branch of the synchronous ENOEXEC case still looked for the
pre-merge `spawn failed:` note; the merged providers render master's
stage-neutral `subprocess failed before reporting an outcome:` on that
path.
2026-09-08 14:38:35 +08:00
Yichen Jiang fe24dddea8 Merge remote-tracking branch 'origin/master' into worktree/process-plugin-streaming-72f9b3 2026-09-08 14:36:54 +08:00
Yichen Jiang f3cf2efc97 Merge branch 'worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery 2026-09-08 13:53:00 +08:00
Yichen Jiang 80fa0bee05 Merge branch 'worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery
# Conflicts:
#	packages/shell/bash-local/README.i18n.yaml
#	packages/shell/bash-local/README.md
#	packages/shell/bash-local/README.zh.md
#	packages/shell/bash-local/src/index.ts
#	packages/shell/bash-local/tests/executor.spec.ts
#	packages/shell/pwsh-local/README.i18n.yaml
#	packages/shell/pwsh-local/README.md
#	packages/shell/pwsh-local/README.zh.md
#	packages/shell/pwsh-local/src/index.ts
#	packages/shell/shell/README.i18n.yaml
#	packages/shell/shell/README.md
#	packages/shell/shell/README.zh.md
#	snapshots/session/pwsh-tool-turn/tool-schemas.expected.json
2026-09-08 13:52:08 +08:00
Yichen Jiang 1f4d165aa2 test(snapshot): release the ACP diagnostic hold from the caller-bound wait
The scenario's barrier plugin patched ctx.jobs.wait, which the consolidated
seam no longer has: waits live on the operations forCaller hands out, so
the hold file was never removed and the mock ACP server never released the
prompt. The barrier now wraps forCaller and releases on that view's wait.
2026-09-08 13:51:16 +08:00
Yichen Jiang 1242fdfdef Merge remote-tracking branch 'origin/master' into worktree/process-plugin-streaming-72f9b3
# Conflicts:
#	docs/event-producer-consumer.i18n.yaml
#	docs/event-producer-consumer.md
#	docs/event-producer-consumer.zh.md
#	docs/module-graph.i18n.yaml
#	docs/module-graph.md
#	docs/module-graph.zh.md
#	docs/subsystems/shell.i18n.yaml
#	docs/subsystems/shell.md
#	docs/subsystems/shell.zh.md
#	packages/api/remotes/package.json
#	packages/api/remotes/src/client/index.ts
#	packages/api/session-controller/src/control.ts
#	packages/api/session-controller/tests/control-jobs.host.spec.ts
#	packages/bundle/web-app/cordis.patch.yml
#	packages/bundle/web-app/package.json
#	packages/client/ui-chat/src/client/details/DetailsPanel.tsx
#	packages/client/ui-chat/src/client/details/tool-node-reader.ts
#	packages/client/ui-chat/tests/gate-branch-tails.client.spec.tsx
#	packages/client/ui-jobs/package.json
#	packages/client/ui-tool/tests/diff-card.client.spec.tsx
#	packages/client/ui-tool/tests/read-card.client.spec.tsx
#	packages/client/ui-tool/tests/search-card.client.spec.tsx
#	packages/client/ui-tool/tests/terminal-card.client.spec.tsx
#	packages/client/ui-tool/tests/web-card.client.spec.tsx
#	packages/shell/bash-local/README.i18n.yaml
#	packages/shell/bash-local/README.md
#	packages/shell/bash-local/README.zh.md
#	packages/shell/bash-local/src/index.ts
#	packages/shell/bash-local/tests/executor.spec.ts
#	packages/shell/pwsh-local/README.i18n.yaml
#	packages/shell/pwsh-local/README.md
#	packages/shell/pwsh-local/README.zh.md
#	packages/shell/pwsh-local/src/index.ts
#	packages/shell/shell/README.i18n.yaml
#	packages/shell/shell/README.md
#	packages/shell/shell/README.zh.md
#	packages/shell/shell/src/types.ts
#	packages/terminal/tool-terminal/tests/tools.spec.ts
#	tsconfig.client.json
2026-09-08 13:34:53 +08:00
Yichen Jiang 6371bc4a41 Merge branch 'worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery
# Conflicts:
#	docs/config-catalog.i18n.yaml
#	docs/config-catalog.zh.md
2026-09-07 10:44:53 +08:00
Yichen Jiang bf91cd855e Merge remote-tracking branch 'origin/master' into worktree/process-plugin-streaming-72f9b3
# Conflicts:
#	.agents/notes/archived/manifest.json
#	.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml
#	.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md
#	.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md
#	.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.i18n.yaml
#	.agents/notes/implemented/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.i18n.yaml
#	.agents/notes/implemented/testing/2026-09-06-standard-hosted-benchmark-runner.i18n.yaml
#	docs/architecture.i18n.yaml
#	docs/capability-seams.i18n.yaml
#	docs/config-catalog.i18n.yaml
#	docs/cookbook/adding-a-tool.i18n.yaml
#	docs/event-producer-consumer.i18n.yaml
#	docs/module-graph.i18n.yaml
#	docs/persistence-catalog.i18n.yaml
#	docs/subsystems/shell.i18n.yaml
#	packages/client/ui-primitives/README.i18n.yaml
#	packages/jobs/jobs-local/README.i18n.yaml
#	packages/jobs/jobs/README.i18n.yaml
#	packages/jobs/jobs/README.md
#	packages/jobs/jobs/README.zh.md
#	packages/jobs/tool-jobs/README.i18n.yaml
#	packages/shell/tool-bash/README.i18n.yaml
#	packages/shell/tool-pwsh/README.i18n.yaml
2026-09-07 10:39:56 +08:00
Yichen Jiang 51d1df5c4d test(pwsh-local): expect the spawn-failure note on the observed stderr stream
The Windows-only synchronous spawn-throw case still expected empty observed
readers, the behavior before a rejected spawn started serving its note as the
whole observed stderr stream. It now mirrors the bash-local case: the note is
the stream at every offset, stdout stays empty, and the consuming read folds
the note in exactly once.
2026-09-05 14:53:50 +08:00
Yichen Jiang 57d51b3802 Merge branch 'worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery 2026-09-05 14:34:41 +08:00
Yichen Jiang f3b538cf8f Merge remote-tracking branch 'origin/master' into worktree/process-plugin-streaming-72f9b3 2026-09-05 14:32:14 +08:00
Yichen Jiang 3be4761178 Merge branch 'worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery
# Conflicts:
#	packages/api/job-controller/README.i18n.yaml
#	packages/api/job-controller/README.md
#	packages/api/job-controller/README.zh.md
2026-09-05 14:31:23 +08:00
Yichen Jiang 4baea3bb83 fix(jobs): spill paths as source metadata, live wait claims, narrower entries
Three review follow-ups on the consolidated jobs seam.

The spill file a pull source keeps is job metadata now: the pump reports it
to the registry on every read (present or withdrawn), the projection lists
it under output.spillPaths, and the model's dropped-output notice names it
from there. Attached to a gap chunk it was lost whenever the ring itself
evicted (a pump that kept up never produced a gap) or whenever the gap chunk
was evicted later, and it could not follow a source that withdrew its file.

A job_output wait's claim on the settlement now carries a live flag that
the caller's abort clears synchronously from the abort event. The registry
rejects an aborted wait on a later microtask, so a settlement announced in
between used to find the claim counted and skip the notice the tool result
was not going to carry either.

The job-controller client entry and the tool-jobs entry stop exporting
implementation classes and render helpers nothing outside the packages
consumes; the client entry keeps IJobs, which also carries the ctx.jobs
Context augmentation to consumers.
2026-09-05 14:28:28 +08:00
Yichen Jiang 206e5c6545 Merge branch 'worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery
# Conflicts:
#	apps/web/tests/background-job-list.e2e.ts
#	docs/config-catalog.i18n.yaml
#	docs/config-catalog.zh.md
#	packages/api/job-controller/README.i18n.yaml
#	packages/api/job-controller/README.md
#	packages/api/job-controller/README.zh.md
#	packages/api/session-controller/README.i18n.yaml
#	packages/api/session-controller/README.md
#	packages/api/session-controller/README.zh.md
2026-09-05 14:06:00 +08:00
Yichen Jiang 8bcd6f7519 refactor(jobs): rename visibleTo/VisibleJobs to forCaller/CallerJobs
The caller-bound operation set now says what it is: the jobs one caller
may operate on. The caller argument is required (SessionId | undefined),
so an anonymous view has to be requested explicitly instead of falling
out of a forgotten argument.
2026-09-05 14:00:47 +08:00
Yichen Jiang e001a9d2e4 Merge remote-tracking branch 'origin/master' into worktree/process-plugin-streaming-72f9b3
# Conflicts:
#	packages/client/ui-jobs/package.json
2026-09-04 17:33:19 +08:00
Yichen Jiang d3b1f2d1bc Merge remote-tracking branch 'origin/master' into worktree/process-plugin-streaming-72f9b3
# Conflicts:
#	docs/event-producer-consumer.i18n.yaml
#	docs/event-producer-consumer.md
#	docs/event-producer-consumer.zh.md
#	docs/module-graph.i18n.yaml
#	docs/module-graph.md
#	docs/module-graph.zh.md
#	packages/api/remotes/package.json
#	packages/api/remotes/src/client/index.ts
#	packages/api/session-controller/README.i18n.yaml
#	packages/api/session-controller/README.md
#	packages/api/session-controller/README.zh.md
#	packages/api/session-controller/tsconfig.host.json
#	packages/client/ui-approval/src/client/contract/slots.ts
#	packages/client/ui-chat/src/client/chat/MessageItem.tsx
#	packages/client/ui-chat/src/client/conversation-nodes/assistant.ts
#	packages/client/ui-chat/src/client/conversation-nodes/partial.ts
#	packages/client/ui-chat/src/client/conversation-nodes/tool.ts
#	packages/client/ui-chat/src/client/model/tool-call-tree.ts
2026-09-04 17:28:12 +08:00
Yichen Jiang a444460ad4 fix(shell): never offer cancelled work from the offer arm
The offer arm relayed the caller signal only through an abort listener, which a signal that was already aborted never fires, so a command cancelled before execute() spawned anyway, ran to its deadline, and was offered for promotion. The deadline callback also offered regardless of an earlier caller abort, so a process still in its termination grace could be promoted after the tool call was aborted. Both providers now relay an already-aborted signal immediately and settle the promotion undefined at the deadline once the caller aborted; the seam JSDoc states that an already-aborted signal counts as fired. Real-process and hanging-backend cases cover both providers.
2026-09-04 14:03:24 +08:00
Yichen Jiang b0cf8cc1d9 fix(tool-workflow): narrate background runs on the log channel
The record mirror appended phase, log, and member lines without a channel, and the model's job_output renders channel-less chunks as stdout, so a job_output before settlement returned observer narration the README promised it would not. The four appends now carry channel: 'log', which the registry keeps out of model reads while observers still stream it; the spec pins the channels and the status-only early read.
2026-09-04 14:03:23 +08:00
Yichen Jiang 58d4050cb6 docs(tool-workflow): state why the background branch has no pre-abort check
ToolRuntime re-reads the caller signal immediately before execute(), and the workflow tool reaches jobs.start synchronously from there, so an in-tool check would be unreachable; bash and pwsh need theirs because they await a sandbox escalation approval before registering.
2026-09-04 13:52:24 +08:00
Yichen Jiang 600b780b97 chore(shell): align the pwsh mirror comments and the promotion read JSDoc
renderProcessRead and renderPwshProcessRead described themselves as the job_output renderer; their only caller is the timeout promotion embedding one consuming read, while job_output reads come from the registry ring. The pwsh presenter comment gains the promotions clause bash already has, and pwsh-sandbox drops a stray blank line.
2026-09-04 13:52:23 +08:00
Yichen Jiang 15cbcad1eb docs(notes): rewrite the three delivery notes to the shipped mechanisms
The timeout-promotion, workflow run_in_background, and human-job-kill notes kept their bodies in the present tense over ctx.activities, RunningJob/updateDetail, and the registry's reported bit, with Update headers doing the correcting. Their mechanism sentences now state the shipped facts (observed streams as pull sources, JobHandle/updateProgress, the tool-jobs delivery ledger), the Update headers shrink to cross-links, and the testing paragraph names the current specs.
2026-09-04 13:52:22 +08:00
Yichen Jiang 9fe106693c docs(shell): describe timeout promotion through the job ring, not ctx.activities
The tool-bash and tool-pwsh READMEs still said a promoted run is mirrored into ctx.activities, a seam the jobs consolidation deleted, and the shell README still called the observed readers optional and named the activity plane. The promoted run now feeds the registry's ring from the observed streams' current offsets and the Web list streams it through job.rows and job.observe; observed readers are required and carry the spawn-failure note.
2026-09-04 13:52:22 +08:00
Yichen Jiang ca3d9af0a7 Merge remote-tracking branch 'origin/worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery
# Conflicts:
#	packages/shell/bash-sandbox/tests/sandbox.spec.ts
#	packages/shell/pwsh-local/tests/executor.spec.ts
2026-09-04 13:42:20 +08:00
Yichen Jiang 7e5627f8e3 Merge remote-tracking branch 'origin/master' into worktree/process-plugin-streaming-72f9b3 2026-09-04 13:39:42 +08:00
Yichen Jiang 3d5431a862 Merge remote-tracking branch 'origin/worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery 2026-09-04 13:30:34 +08:00
Yichen Jiang 063ef188a6 chore(jobs): regenerate the catalogs after the review fixes
The settled-retention JSDoc, the required observed streams, and the session-controller type shifts changed what the config, persistence, Cordis, and event catalogs embed; CI's static and coverage lanes compared the stale copies. Regenerated, with the zh mirrors updated in step.
2026-09-04 13:29:54 +08:00
Yichen Jiang 42858723a6 Merge remote-tracking branch 'origin/worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery
# Conflicts:
#	packages/api/job-controller/README.i18n.yaml
#	packages/api/job-controller/README.md
#	packages/api/job-controller/README.zh.md
#	packages/api/session-controller/README.i18n.yaml
#	packages/api/session-controller/README.md
#	packages/api/session-controller/README.zh.md
#	packages/shell/bash-local/src/index.ts
#	packages/shell/pwsh-local/src/index.ts
#	packages/shell/tool-bash/src/background.ts
#	packages/shell/tool-bash/tests/tools.spec.ts
#	packages/shell/tool-pwsh/src/background.ts
#	packages/shell/tool-pwsh/tests/background.spec.ts
#	snapshots/web/approval-composer/session.v2.jsonl
#	snapshots/web/feedback-command/session.v2.jsonl
#	snapshots/web/fresh-round-trip/session.v2.jsonl
#	snapshots/web/goal-multi-turn-actions/session.jsonl
#	snapshots/web/lifecycle-chrome/session.v2.jsonl
#	snapshots/web/live-interactions/session.v2.jsonl
#	snapshots/web/minimal-preset/session.v2.jsonl
#	snapshots/web/permission-policy-context/session.v2.jsonl
#	snapshots/web/plan-review/session.jsonl
#	snapshots/web/ptc-round/session.v2.jsonl
#	snapshots/web/question-composer/session.v2.jsonl
#	snapshots/web/steering/session.v2.jsonl
#	snapshots/web/turn-tail-actions/session.v2.jsonl
#	snapshots/web/web-search-round/session.v2.jsonl
2026-09-04 13:27:34 +08:00
Yichen Jiang 3a03d68891 fix(shell): serve a background spawn failure on the observed stderr stream
A rejected background spawn produces no process output, so the observed stderr reader the job registry pumps was empty and the model's job_output after such a failure showed only killed before exit, where master's consuming read rendered [stderr] followed by spawn failed: … with the cause. The local providers now serve the note as the whole observed stderr stream once the spawn rejected, and the consuming read still folds it in exactly once, so the model's first read after the failure is byte-identical to master's. ShellProcess.observed and both of its readers are required: every background process exposes offset readers, and the shell tools' pull sources no longer carry a no-readers branch.
2026-09-04 12:59:41 +08:00
Yichen Jiang 1c2d7771aa fix(job-controller): close an observation on the job's removal instead of a failed read
An owner's teardown settles and removes the record within one microtask chain, while the observation generation sleeps its flush window after the wake and then reads an id the registry no longer returns, failing the stream and flashing an error in the panel until the roster frame drops the row. The subscription now records a removed event for the observed id and the generation closes with that terminal projection.
2026-09-04 12:47:22 +08:00
Yichen Jiang 80bbdba617 fix(jobs): restore the dropped-output notice with its spill files for the model
The consuming shell read on master told the model where a truncated stream's complete text lived: [some output was dropped from memory; full output: <paths>]. The ring-backed read rendered a notice only for the ring's own eviction and ignored the gapBefore mark the pump puts on a chunk whose source lost bytes between two polls, so a fast producer's tail read as continuous output and no path survived. A lossy source read now carries the source's spillPath onto its gap chunk (JobSourceRead, JobAppendOptions, JobChunk), the pump forwards it, the ring retains it, and the model renderer emits master's notice for a producer-side gap or a ring eviction, listing the distinct spill files in offset order. The bash and pwsh sources already returned the subprocess reader's spillPath; the type now admits it.
2026-09-04 12:47:21 +08:00
Yichen Jiang c673796496 docs(jobs): record the review follow-ups in the consolidation note 2026-09-04 12:27:17 +08:00
Yichen Jiang e680fbc3a0 refactor(jobs): check the announced event protocol in the invariant companion
The companion validated fields of single JobView projections, which the invariant rule rejects as a check without independent observations. It now tracks each job's announced protocol (registered first, one settlement, removal last, no progress after settlement), adopting a job first seen late, and compares every announced projection with the registry's own read of the same job: identity, output total, and the settled status and time. An output event announced before registration, which the registry emitted until the previous commit, is one of the cases it rejects.
2026-09-04 12:27:16 +08:00
Yichen Jiang 5f1d53640b fix(job-controller): show an observation failure that precedes the anchor
An observation stream that failed before its opened frame left no observed state, so the expanded row rendered nothing: no output, no notice. The client model now records such a failure as an errored, non-streaming view with no resume cursor, and the panel renders the interruption notice it promises.
2026-09-04 12:25:16 +08:00
Yichen Jiang 9f0600d11b fix(tool-jobs): count wait claims per call
Two concurrent job_output waits on one job shared a single ledger entry, so the first wait's timeout withdrew the claim the second wait still held and the settlement injected a completion notice the model had already received through that wait. Claims are counted per call now; a withdraw releases only its own.
2026-09-04 12:25:15 +08:00
Yichen Jiang 0712a4cc68 fix(jobs-local): keep unconsumed settled output for the model and announce registration first
Settlement trimmed the ring to settledRetainBytes before releasing waiters, so a job that finished before its first job_output handed the model only the last 16 KiB plus a dropped-output notice where master's consuming read kept the executor's whole tail. The ring now keeps every byte the model cursor has not consumed on top of the settled cap, and the first terminal model read trims to the cap.

The registered event was emitted after startPump, whose first drain runs synchronously and may announce output, so a source readable at once produced an output event for an id no subscriber had seen registered. The announcement now precedes the pump; the events JSDoc states that a job's first event is registered.
2026-09-04 12:25:14 +08:00
Yichen Jiang 2c0ca45f76 fix(jobs): carry the projection kind as an open string on the wire
JobView.kind was typed as the merge-extensible JobKind, so the job-controller program, which compiles only the merges it can see, generated a strict codec of bash | subagent for job.rows and job.observe. The first pwsh, pty-send, or workflow job failed frame validation on both ends and the browser dropped the whole roster. The projection now carries kind as a string; JobSpec.kind keeps the Host-side JobKind. A built-artifact smoke parses a pty-send row through the generated schemas, and the seam spec pins the open type.
2026-09-04 12:10:22 +08:00
Yichen Jiang b76bfbcc1f docs(notes): align the job runtime notes with the consolidated seam
The registry seam, runtime, admission, wake, and web display notes named JobStart, JobSnapshot, onJobDone, onJobsChanged, and the registry-owned reported bit as current facts. Rewrite those sentences to the shipped JobSpec/JobView contract, the events stream, and the tool-jobs delivery ledger; the wake note gains an Update header pointing at the consolidation note, and the display note's disposal paragraph now states the per-job removed event the registry emits.
2026-09-04 12:05:44 +08:00
Yichen Jiang 70a37b6cc1 chore(jobs): drop the formatting-only hunks left by the seam refactor
Revert the two brace-only rewrites in tool-workflow to master's one-line form and collapse the blank lines left where SessionJob was removed from the session-controller types.
2026-09-04 12:05:43 +08:00
Yichen Jiang 424c8457d9 Merge remote-tracking branch 'origin/master' into worktree/process-plugin-streaming-72f9b3
# Conflicts:
#	docs/event-producer-consumer.i18n.yaml
#	docs/event-producer-consumer.md
#	docs/event-producer-consumer.zh.md
#	packages/api/session-controller/tests/control-jobs.host.spec.ts
#	snapshots/web/fresh-round-trip/session.v2.jsonl
#	snapshots/web/ptc-round/session.v2.jsonl
2026-09-04 12:02:02 +08:00
Yichen Jiang b7632168cc test(tool-pwsh): pin the integration suite to the executor's timeout kill
With owners resolved by session id, the suite's id-less fake agent no
longer makes the timeout promotion fail admission, so a per-call timeout
promoted the run and the timed-out-marker assertion broke. Promotion over
a scripted executor stays covered by background.spec.
2026-09-03 18:20:33 +08:00
Yichen Jiang 59b16ed0e3 Merge remote-tracking branch 'origin/worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery 2026-09-03 18:01:12 +08:00
Yichen Jiang 14dc38b2fd test(session-controller): cover the cold-session baseline queue
The roster spec that exercised a session without a live agent left with
the roster; the queue baseline's empty arm needs its own case.
2026-09-03 18:00:42 +08:00
Yichen Jiang d81c5048db chore(jobs): regenerate the catalogs and re-record the job goldens after the seam merge
The merged tree carries promoteOnTimeout, the workflow run_in_background
config, and tool-jobs' pre-refactor requirement set; the settled job
goldens show the folded finished section, a static row for a job with no
retained output, and the human kill's reason in the detail.
2026-09-03 17:48:21 +08:00
Yichen Jiang ae28d2cc6d Merge remote-tracking branch 'origin/worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery 2026-09-03 17:46:07 +08:00
Yichen Jiang cd3a9ac881 Merge remote-tracking branch 'origin/worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery
# Conflicts:
#	apps/web/tests/background-job-list.e2e.ts
#	apps/web/tests/live-job-stream.e2e.ts
#	docs/config-catalog.i18n.yaml
#	docs/config-catalog.md
#	docs/config-catalog.zh.md
#	docs/subsystems/jobs.i18n.yaml
#	docs/subsystems/jobs.md
#	docs/subsystems/jobs.zh.md
#	packages/api/job-controller/README.i18n.yaml
#	packages/api/job-controller/README.md
#	packages/api/job-controller/README.zh.md
#	packages/api/job-controller/src/client/index.ts
#	packages/api/job-controller/src/client/service.ts
#	packages/api/job-controller/src/index.ts
#	packages/api/job-controller/src/types.ts
#	packages/api/job-controller/tests/client-apply.client.spec.ts
#	packages/api/job-controller/tests/model.client.spec.ts
#	packages/api/job-controller/tsconfig.host.json
#	packages/api/session-controller/tests/control-jobs.host.spec.ts
#	packages/client/ui-jobs/README.i18n.yaml
#	packages/client/ui-jobs/README.md
#	packages/client/ui-jobs/README.zh.md
#	packages/client/ui-jobs/src/client/JobListAction.tsx
#	packages/client/ui-jobs/src/client/index.ts
#	packages/client/ui-jobs/tests/browser-plugin.client.spec.ts
#	packages/client/ui-jobs/tests/job-list-action.client.spec.tsx
#	packages/extensions/tool-cordis/src/api-catalog.ts
#	packages/jobs/jobs-local/src/index.ts
#	packages/jobs/jobs-local/tests/jobs.spec.ts
#	packages/jobs/jobs/README.i18n.yaml
#	packages/jobs/jobs/README.md
#	packages/jobs/jobs/README.zh.md
#	packages/jobs/jobs/src/index.ts
#	packages/jobs/jobs/src/types.ts
#	packages/jobs/tool-jobs/src/index.ts
#	packages/shell/tool-bash/src/index.ts
#	packages/shell/tool-bash/src/render.ts
#	packages/shell/tool-bash/tests/background.spec.ts
#	packages/shell/tool-bash/tests/tools.spec.ts
#	packages/shell/tool-pwsh/src/index.ts
#	packages/shell/tool-pwsh/src/render.ts
#	packages/subagent/tool-subagent/tests/tool-subagent.spec.ts
#	snapshots/web/background-job-list/settled.expected.md
2026-09-03 17:45:44 +08:00
Yichen Jiang 08a51f6d88 fix(tool-jobs): keep the agent registry optional
An owned job cannot start without the agent registry, so the plugin only
needs it to resolve a notice destination; requiring it left compositions
that run unowned jobs — the PTC harness, the pwsh loader and integration
suites — without an attached controller. The inject list, catalogs, and
README return to the pre-refactor requirement set.
2026-09-03 17:45:27 +08:00
Yichen Jiang fec81b7295 test(web): re-record the job goldens for the ring-backed rows
A settled job that retained no output is a static row, and a killed row
shows the kill reason in its detail. The live-stream scenario's idle
locator now matches the settled trigger exactly, so its settled capture
cannot race the roster refresh.
2026-09-03 17:20:06 +08:00
Yichen Jiang 9e8f3ce987 fix(tool-jobs): restore the background-job guidance text
The rewrite carried an older wording of the system-prompt section; the
recorded-session snapshots pin the current one. Model-visible text is
byte-identical to the pre-refactor plugin again.
2026-09-03 17:17:30 +08:00
Yichen Jiang 7b607ae6a2 fix(tool-jobs): build the notice status line from the public projection
The notice and its summary passed an optional detail as string | undefined,
which the build's exactOptionalPropertyTypes rejects while the test lane's
transform does not; publicJob() already carries the conditional.
2026-09-03 17:16:32 +08:00
Yichen Jiang cbb0ec0296 docs(jobs): describe the consolidated seam
- docs/subsystems/jobs.md (+zh) records JobSpec, JobHandle, JobHooks,
  JobOutcome, the pull-source types, JobChunk, JobOutputRead, JobView,
  JobRead, VisibleJobs, and the event types as type-equivalence blocks; the
  manifest and the cordis catalog link map follow
- the jobs, jobs-local, and tool-jobs READMEs (+zh) describe the ring, the
  caller-bound view, the event stream, and the notice ledger; the
  architecture table and the adding-a-tool cookbook drop record: true and
  readOutput
- Agent Note 2026-09-03-jobs-seam-consolidation owns the decision; the
  2026-09-01 record note and the 2026-08-08 web display note carry
  Superseded pointers and the display note links the rows spec
- regenerated catalogs (cordis, api, inspect, client, tool, config,
  persistence, doc graphs, module graph) with their Chinese mirrors; the
  cordis catalog drops the jobOutput exemption and the tool catalog mounts
  the agent registry for tool-jobs; the client model imports JobView from
  its leaf so the typert analyzer resolves it
2026-09-03 17:15:07 +08:00
Yichen Jiang 84efac2c42 refactor(ui-jobs): read rows and observations from ctx.jobs
The header list watches its session's roster through ctx.jobs.watchRows
while mounted and reads rows and observed views from one jobs snapshot. A
row is expandable while the job is live or once it left retained output
behind; it shows the live progress line or the terminal detail. The web
e2e drivers kill through visibleTo(), and the client fixtures the roster
removal missed drop their jobs baselines.
2026-09-03 17:00:51 +08:00
Yichen Jiang b7ce934b29 refactor(job-controller): own the roster stream and the browser jobs service
- job.rows({ sessionId }) streams the jobs one session can see as whole-set
  frames: once on open, then after each coalesced burst of lifecycle
  commits; output appends never refresh it
- job.observe reads through visibleTo(sessionId) and carries JobView in its
  opened and status frames; the wire chunk is the registry's JobChunk
- the client installs ctx.jobs (ClientJobs over ClientJobsModel): watchRows
  and observe are reference-counted streams, rows drop with the last
  watcher or a terminal failure, observations keep their render tail
- the controller no longer needs the agent registry
2026-09-03 17:00:49 +08:00
Yichen Jiang 2132b7beeb refactor(session-controller): drop the job roster from the control stream
The control baseline and frames carry queues and projections only; the
SessionJob row type, the jobs frame, the registry subscription, and the
client jobsBySession mirror are gone, and every client fixture drops the
empty mirror. The roster moves to dsh-api-job-controller's job.rows
stream in the next change.
2026-09-03 16:43:15 +08:00
Yichen Jiang 10d76fbd03 refactor(producers): subagent and terminal jobs use session owners, results, and pull sources
- tool-subagent registers the parent session id as the owner and settleRun
  reports the child's final text as the job result, which the first read
  after settlement carries once
- tool-terminal hands the registry one pull source over the backend's
  consuming send reader (sendSource in background.ts) instead of a
  readOutput hook; the truncation marker stays in the delivered text
- the model's kill reason now shows in a killed job's status line, as the
  registry records it as the terminal detail
- test call sites move from jobs.list(owner) to visibleTo(owner).list()
2026-09-03 16:37:35 +08:00
Yichen Jiang 053d302210 refactor(shell tools): hand the job registry pull sources and terminal outcomes
tool-bash and tool-pwsh no longer render their own background reads.
processSources() wraps the process's observed offset readers as registry
pull sources, pumped at dsh-jobs-local's pumpPollMs, and processOutcome()
folds the settled sandbox facts into the terminal detail. observe.ts and
the recordPollMs config field are gone; READMEs and pairings follow.
2026-09-03 16:30:57 +08:00
Yichen Jiang 0e35952802 refactor(tool-jobs): render model reads from the ring and own the notice ledger
- job_output reads the per-job ring through visibleTo(): stdout, then one
  [stderr] section, log narration excluded, a dropped-output notice on a
  lossy read, and the result exactly once after settlement
- completion notices come from the registry event stream subscribed with
  { owners: 'scope' }; a wait claims its job when it starts and withdraws
  on timeout or abort, job_kill claims on 'requested', teardown settlements
  and unowned jobs are skipped, and a removal drops the ledger entry
- the notice destination is the agent registered for the owner session at
  settlement, so tool-jobs now injects the agent registry
- JobView exposes the producer's outputLimitBytes for the tool-result bound
- lint: hub disposer identity comment, unknown-typed matchers in seam tests
2026-09-03 16:30:57 +08:00
Yichen Jiang 6626c74dbc refactor(jobs-local): contain a throwing pull source instead of freezing the job 2026-09-03 16:09:07 +08:00
Yichen Jiang f55e9805c7 refactor(jobs-local): one ring, two cursors, and event routing
Every job owns an `OutputRing`; the model's `read` consumes from a
registry-kept cursor while `readAt` reads at absolute offsets. Pull
sources named by the spec are pumped by the registry and drained once
more before settlement. `visibleTo(caller)` binds the session fence
once; the three listener families become one event hub with explicit
filters; `reported` is gone and settlements announce their cause.
2026-09-03 16:03:16 +08:00
Yichen Jiang 1b97207150 refactor(jobs-local): extract the output ring and the registry-owned pump 2026-09-03 15:53:59 +08:00
Yichen Jiang 07941fe5e2 refactor(jobs): consolidate the seam into JobSpec, VisibleJobs, and one event stream
The registry seam keeps three members plus the controller handshake:
`start(spec)`, `visibleTo(caller)` (one authorization, then list / get /
read / readAt / kill / wait without a caller argument), and `events`
(one typed stream with an explicit filter instead of three listeners).
Every job owns an output ring: `JobSpec.output` names pull sources the
registry pumps, `JobHandle.append` pushes, and the model's consuming
`read` and observers' `readAt` read the same bytes through separate
cursors. `reported`, the `record` declaration, the producer `readOutput`
hook, and the `JobStart`/`RunningJob` split are gone; `JobView` is the
one projection, split into `progress` and terminal `detail`. The
client-safe `./view` leaf carries the projection vocabulary.
2026-09-03 15:51:39 +08:00
Yichen Jiang 1307d66e70 Merge branch 'worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery 2026-09-03 13:45:24 +08:00
Yichen Jiang 09bd6bcf54 chore(http-proxy): align the package version with the 0.1.2-rc.1 bump 2026-09-03 13:45:09 +08:00
Yichen Jiang a2b9b8b03b Merge remote-tracking branch 'origin/master' into worktree/process-plugin-streaming-72f9b3 2026-09-03 13:42:07 +08:00
Yichen Jiang d0b9167dba Merge branch 'worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery
# Conflicts:
#	.agents/notes/implemented/feature/2026-08-25-unified-task-list.i18n.yaml
#	packages/jobs/jobs-local/src/index.ts
#	snapshots/web/cordis-tool-round/session.jsonl
2026-09-03 13:19:22 +08:00
Yichen Jiang b48c05d117 refactor(jobs): type the producer face by the record declaration, tidy review residue
- `JobStart` is a union discriminated by `record`: `RecordingJobStart.run`
  receives `RecordingJob` (with `append`), `PlainJobStart.run` receives the
  plain `RunningJob`, and the registry builds the plain face without the
  method, so the former warn-and-drop path is unrepresentable.
- Remove the duplicated JSDoc opener in the registry seam and correct the
  job-list slot comment (order 20 sits before the subagent catalog's 30).
- Restore the `cordis-tool-round` session fixture whose only change was a
  re-recorded `createdAt`.
- Update the 2026-08-08 job-display note's stale service-disposal and slot
  ordering facts; archive the superseded 2026-08-25 unified-task-list note
  and point its remaining inbound links at the archived snapshot.
2026-09-03 13:15:23 +08:00
Yichen Jiang 07d9d5b5cd Merge remote-tracking branch 'origin/worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery 2026-09-03 11:33:34 +08:00
Yichen Jiang 6a4d7ee242 chore(job-controller): align the package version with the 0.1.2-rc.1 bump 2026-09-03 11:33:05 +08:00
Yichen Jiang 6d65c12ef2 Merge remote-tracking branch 'origin/master' into worktree/process-plugin-streaming-72f9b3
# Conflicts:
#	packages/client/ui-jobs/package.json
2026-09-03 11:30:41 +08:00
Yichen Jiang dd3ac9f7bf test(web): re-record the job goldens for the barrier-held scenarios after the merge 2026-09-03 11:28:13 +08:00
Yichen Jiang 5295b25073 Merge remote-tracking branch 'origin/worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery
# Conflicts:
#	apps/web/tests/background-job-list.e2e.ts
#	apps/web/tests/live-job-stream.e2e.ts
#	apps/web/tests/snapshots/live-job-stream/settled.expected.md
#	apps/web/tests/snapshots/live-job-stream/streaming.expected.md
#	packages/api/job-controller/README.i18n.yaml
#	packages/api/job-controller/README.md
#	packages/api/job-controller/README.zh.md
#	snapshots/web/background-job-list/running.expected.md
#	snapshots/web/background-job-list/settled.expected.md
2026-09-03 11:25:17 +08:00
Yichen Jiang 761282724b fix(jobs): constant pump waits, quiescent client disposal, record flag on the roster
Review corrections on the observation record:

- `pumpJobOutput` subscribes to `done` once and keeps one pending timer;
  racing the same pending promise every poll round retained a reaction
  per round for the life of the job.
- The `ctx.jobOutput` effect disposer is async and awaits every open
  carrier's `dispose()`, so the fiber reports unloaded only once the old
  iterators are quiescent.
- `SessionJob.record: true` replaces `outputTotal`: `append` never commits
  a roster change, so the mirrored count was stale while output flowed;
  the live offsets ride the observation stream's `opened` anchor.
- The record is documented as a best-effort live preview whose
  cross-stream reordering is bounded by the poll interval.
- The web e2e scenarios hold their background job on a test-owned barrier
  file instead of a wall-clock sleep, and each is one test rather than a
  chain sharing a job id.
2026-09-03 11:23:41 +08:00
Yichen Jiang e8469d6f6c refactor(jobs): move the human kill to the job namespace
`session.killJob` becomes `job.kill` on `dsh-api-job-controller`, beside
`job.observe`, so the Session Controller carries only session control and
the roster while every per-job Remote lives on the job namespace. The
client `ctx.jobOutput` gains `kill(sessionId, jobId)`; `dsh-client-ui-jobs`
routes its stop control through it and no longer requires `sessions`.
Unknown or foreign jobs reject with `job/not-found`; the controller
requires `ctx.jobs` to load, so `jobs-unavailable` disappears. The two
subagent ownership-fence helpers are classified duplicate-install-safe
Host exports of the Session Controller for the cross-controller import.
2026-09-02 23:18:08 +08:00
Yichen Jiang 121db081a4 Merge remote-tracking branch 'origin/worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery 2026-09-02 22:59:43 +08:00
Yichen Jiang 294c11715e Merge remote-tracking branch 'origin/worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery
# Conflicts:
#	docs/config-catalog.i18n.yaml
#	docs/config-catalog.md
#	docs/config-catalog.zh.md
#	docs/event-producer-consumer.i18n.yaml
#	docs/event-producer-consumer.md
#	docs/event-producer-consumer.zh.md
#	packages/extensions/tool-cordis/src/api-catalog.ts
#	scripts/gen-cordis-catalog.ts
#	snapshots/web/cordis-tool-round/session.jsonl
#	snapshots/web/fresh-round-trip/session.jsonl
#	snapshots/web/ptc-round/session.jsonl
2026-09-02 22:59:20 +08:00
Yichen Jiang e42eb13655 test(web): drop the stray slash after the sourceRoot token in three prompt pins 2026-09-02 22:58:24 +08:00
Yichen Jiang 7ec142a325 refactor(jobs): move job observation into api-job-controller
Review asked for the observation wire and its client service to leave
session-controller: a job should reach the Host only through its own
Remote namespace, and the session plugin's apply had to materialize
{ $stream, session } for a service that is not session state.

@deepseek-ai/dsh-api-job-controller now owns the Host JobController
(job.observe over ctx.jobs, requiring the agent and job registries) and
the client ClientJobOutput (ctx.jobOutput) over ClientJobOutputModel,
resolving both Remote faces in its own apply. The remotes assembly
mounts the job namespace; ui-jobs injects the new package; the wire
types drop their Session prefix and spell the status union out so the
client face pulls no Host declaration merges into browser programs.
session-controller keeps the jobsBySession roster and loses observe-job,
job-output, and the observe cadence config.

ui-chat's slots and snapshot-builder import CommandNode and friends from
the declaring package: the typert analyzer's cross-package rule trips on
the snapshot re-export as soon as another client face joins the root
Client program (the new controller does), which is why this branch
carried the change before. JobPumpSource documents why it is an offset
snapshot reader rather than a Node.js Readable.
2026-09-02 22:53:36 +08:00
Yichen Jiang 4db24c642d Merge remote-tracking branch 'origin/master' into worktree/process-plugin-streaming-72f9b3
# Conflicts:
#	docs/config-catalog.i18n.yaml
#	docs/config-catalog.md
#	docs/config-catalog.zh.md
#	packages/client/ui-chat/src/client/contract/slots.ts
#	packages/client/ui-chat/src/client/conversation-nodes/chat-snapshot-builder.ts
#	packages/client/ui-jobs/package.json
#	packages/extensions/cordis-client-runner/src/client/slot-catalog.ts
2026-09-02 22:04:03 +08:00
Yichen Jiang d1ec8baaf5 Merge remote-tracking branch 'origin/master' into worktree/job-kill-delivery
# Conflicts:
#	docs/config-catalog.i18n.yaml
#	docs/config-catalog.md
#	docs/config-catalog.zh.md
#	packages/client/ui-chat/src/client/contract/slots.ts
#	packages/client/ui-chat/src/client/conversation-nodes/chat-snapshot-builder.ts
#	packages/client/ui-jobs/package.json
#	packages/extensions/cordis-client-runner/src/client/slot-catalog.ts
#	snapshots/web/ptc-round/session.jsonl
#	snapshots/web/ptc-round/system-prompt.expected.md
2026-09-02 21:40:24 +08:00
Yichen Jiang 374baa2621 Merge branch 'worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery
Carries the session seq-brand master sync forward; unions the seq/offset
imports with this branch's jobs augmentation in commands.ts and
regenerates the catalogs and zh ports over the merged source.
2026-09-01 22:20:21 +08:00
Yichen Jiang c1e6d09305 Merge branch 'master' into worktree/process-plugin-streaming-72f9b3
Absorbs the session seq-brand refactor and the turn-rail preview layer;
regenerates the catalogs and graph docs from the merged source and
re-ports the zh config/event rows this branch owns.
2026-09-01 22:13:35 +08:00
Yichen Jiang a5af861041 fix(ci): finish the background-workflow snapshot fallout
Regenerates the module graph for tool-workflow's new dsh-jobs edge (the
doc-sync aggregate does not run that gate), refreshes the web lane's own
schema/prompt pins for the run_in_background tool text, re-packs every
session fixture the keyless refresh had left off the canonical packed
layout, covers pwsh-local's never-spawned empty observed readers, and
rewrites the pwsh-sandbox sync-rejection start() test to the seam's
actual containment contract — execute() never throws; the sync spawn
throw settles the handle killed and result() carries the attribution
split. That test sits behind the pwsh probe, so only the Windows lane
had ever executed it.
2026-09-01 22:03:37 +08:00
Yichen Jiang 12957a106e feat(workflow): run_in_background registers the run as an observed job
The workflow tool gains run_in_background (enableRunInBackground config,
default on): the run registers on ctx.jobs as an owned 'workflow' record
job and the call returns { kind: 'background', jobId, runId } immediately.
The run belongs to the job — job_kill, the list's stop control, and owner
teardown cancel it; no tool-step signal is bridged. Settlement maps the
stop reason onto the job outcome, with a completed run's rendered return
value riding output to the completion notice and job_output. A record
mirror streams workflow/phase, workflow/log, and member lifecycle lines
into the job record, replacing the removed activity mirror; the output
schema becomes a kind-discriminated union and 'workflow' joins JobKindMap.
Snapshot expected outputs are refreshed for the schema, prompt, and PTC
stub changes (the keyless refresh also migrates fixtures to the current
session write format: range sourceEventSeqs and reasoning-delta chunks).
2026-09-01 21:24:37 +08:00
Yichen Jiang d23fdf8ab7 test(ci): port the offer-expiry executor suite to pwsh-local
The promotion machinery was copied from bash-local without its tests;
runners without pwsh skip and exempt the file, so only the Windows
coverage lane surfaced the gap.
2026-09-01 21:06:34 +08:00
Yichen Jiang e578d07354 Merge remote-tracking branch 'origin/worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery 2026-09-01 20:53:38 +08:00
Yichen Jiang 592dfd8854 test(ci): cover the owned promotion paths
An agent-attributed timeout promotion fences the job to the calling
session in both shell tools, and the pwsh promoted job's cancel hook is
exercised through a registry kill.
2026-09-01 20:53:32 +08:00
Yichen Jiang 1cec452f09 test(ci): close the observe-job coverage gaps
Covers the woken-wait fast path (a wake landing while the generator is
suspended on a yield), the opened frame's live-detail arm, and the
sessionId-less caller resolution over an unowned job.
2026-09-01 20:51:18 +08:00
Yichen Jiang da48f65160 Merge branch 'worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery
Carries the hairline-elevation sweep forward; the settled-section heading,
chevron box, and stop control move to 0.5px borders while keeping this
branch's padding.
2026-09-01 20:36:55 +08:00
Yichen Jiang 2540fd8b78 Merge branch 'master' into worktree/process-plugin-streaming-72f9b3
Absorbs the superellipse-corner and hairline-elevation sweep; keeps the
data-with-body header divider on TerminalBlock at the new 0.5px width and
brings the job-list settled-section rule onto the hairline standard.
2026-09-01 20:34:59 +08:00
Yichen Jiang 4ee1e1af25 Merge branch 'worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery
Carries the master sync forward (invariant-companion cleanup, alpha.3 bump);
regenerates the client api catalog so ISession lists both killJob and the new
loadThrough, and refreshes the new ptc-python-turn scenario's system-prompt
pin for this branch's bash timeout-promotion schema text.
2026-09-01 20:30:36 +08:00
Yichen Jiang 891b45bf7f Merge branch 'master' into worktree/process-plugin-streaming-72f9b3
Absorbs the repo-wide no-op invariant companion removal (ui-jobs loses its
placeholder companion, export, and devDep), the 0.1.2-alpha.3 release bump,
and the tool-subagent-report package retirement; regenerates the client slot
catalog for the shifted contract line numbers.
2026-09-01 20:22:52 +08:00
Yichen Jiang b5c29ece22 fix(ci): regenerate catalogs and settle the merged job-list evidence
Catalog line references and the re-landed promoteOnTimeout blocks follow
the merge in both languages; the ordering test narrows getAttribute's null
before startsWith so the client face compiles.
2026-09-01 19:42:18 +08:00
Yichen Jiang a2fb67d06c Merge branch 'worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery
# Conflicts:
#	apps/web/tests/background-job-list.e2e.ts
#	apps/web/tests/live-job-stream.e2e.ts
#	docs/config-catalog.i18n.yaml
#	docs/config-catalog.md
#	docs/config-catalog.zh.md
#	docs/event-producer-consumer.i18n.yaml
#	docs/event-producer-consumer.md
#	packages/client/ui-activity/README.md
#	packages/client/ui-activity/README.zh.md
#	packages/client/ui-activity/src/client/index.ts
#	packages/client/ui-jobs/README.i18n.yaml
#	packages/client/ui-jobs/src/client/JobListAction.tsx
#	packages/client/ui-jobs/tests/browser-plugin.client.spec.ts
#	packages/client/ui-jobs/tests/job-list-action.client.spec.tsx
#	packages/jobs/jobs-local/src/index.ts
#	packages/jobs/jobs/README.i18n.yaml
#	packages/jobs/jobs/README.md
#	packages/jobs/jobs/README.zh.md
#	packages/jobs/jobs/src/index.ts
#	packages/shell/tool-bash/src/index.ts
#	packages/shell/tool-bash/tests/observe.spec.ts
#	packages/shell/tool-pwsh/src/index.ts
#	packages/shell/tool-pwsh/tests/observe.spec.ts
#	packages/workflow/tool-workflow/tests/activity-mirror.spec.ts
#	pnpm-lock.yaml
2026-09-01 19:35:23 +08:00
Yichen Jiang 419a985fc9 fix(api): capture the session namespace for job observation at apply time
Observation (re)opens run on caller stacks — a React expand click, a
carrier retry — whose dynamic context has not declared remote.session, so
the ClientJobOutput wiring resolves the namespace to a concrete value while
the plugin's own context is current, exactly as the activity client did.
Caught by the live-job-stream web e2e, whose goldens (with the
background-job-list pair) now carry the ui-jobs list name; the client-apply
spec gains a jobOutput wiring smoke.
2026-09-01 19:04:25 +08:00
Yichen Jiang d2e9be4086 test(api): stub session.observeJob on the fake client namespace
The regenerated session namespace requires the new stream; the fake serves
a never-yielding generator, and job-output.client.spec exercises the real
consumer.
2026-09-01 18:36:29 +08:00
Yichen Jiang 3c79e71cdc docs(i18n): mirror the record refactor across the bilingual pairs
Chinese sides of the jobs, jobs-local, tool-bash, tool-pwsh, tool-workflow,
session-controller, and ui-jobs pages follow their English edits; the
generated en-only catalogs (config catalog, capability seams, event
producer/consumer) get the same section surgery by hand; the activity
subsystem page triplet is fully removed and the jobs page carries the
record contract in both languages with fresh type-equiv manifest entries.
The three prior activity notes point at the superseding note. Web e2e label
assertions follow the ui-jobs copy, and the three test call sites that
invoked run() bare pass the producer face.
2026-09-01 18:34:36 +08:00
Yichen Jiang 0dca00b425 refactor(api,client)!: delete the activity plane; one job roster with record observation
packages/activity/* and packages/api/activity-controller are removed. The
web roster is the session control stream's jobs frames alone; per-job output
observation rides session.observeJob and the client ctx.jobOutput service.
ui-activity returns to its upstream name ui-jobs and renders the single
roster — a row is expandable exactly when its job declared a record
(outputTotal present). Composition rows, dependency-policy exemptions,
tsconfig aliases and references, generated catalogs, and the docs pages
follow; the activity subsystem page is deleted and the jobs page gains the
record contract. The superseding Agent Note records the decision and points
the three prior notes at it.
2026-09-01 18:22:04 +08:00
Yichen Jiang 6e8a7a50ea feat(api): serve job-record observation from the session namespace
SessionJob rows carry outputTotal exactly for record-declaring jobs, and a
new session.observeJob stream serves one job's retained record from an
absolute offset (anchor, coalesced output frames, terminal status) with the
fenced read resolved from the request's session — the same authorization
shape as the jobs roster. The client half gains ctx.jobOutput: the
reference-counted per-job observation service and its accumulated view
model, ported from the activity controller this replaces.
2026-09-01 18:03:09 +08:00
Yichen Jiang 8537b8d5bb refactor(shell,workflow): produce into the job record instead of ctx.activities
bash and pwsh background runs declare record: true and pump their observed
streams straight into the job through observeProcessRecord — the open/end
pairing, outcome mapping, and registry-absent degradation branches are gone,
and the pump's final drain folds into hooks.done so the record holds its
last bytes before settlement closes it. activityPollMs becomes recordPollMs.

tool-workflow drops its foreground activity mirror deliberately: a
foreground run's live phase/log narration had the mirror as its only
carrier, and the transcript keeps the recorded run/member lifecycle events.
Live narration returns with backgrounded runs when workflow gains
run_in_background.
2026-09-01 17:52:42 +08:00
Yichen Jiang e580c0b333 feat(jobs): fold the streaming observation record into the job registry
A job can now declare record: true and stream raw output into a bounded
per-job ring through the RunningJob face its starter receives: readRecord
serves any number of observers at absolute byte offsets without touching
the consuming model cursor or notice state, onOutput signals advancement,
and updateDetail publishes a live progress line into every snapshot. Job
settlement is the record's only end: it trims retention to the settled cap
and fires the final output signal, so producers no longer reconcile two
terminal states. pumpJobOutput ports the offset-reader pump beside the
seam. This is the first step of merging the standalone dsh-activity
observation plane into jobs.
2026-09-01 17:45:43 +08:00
Yichen Jiang b684ae215f Merge branch 'worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery 2026-08-31 11:49:23 +08:00
Yichen Jiang 48939ba742 test(ci): pin the activity controller in the Host relay roster
The roster spec mirrors CLIENT_FACE_EXCLUDE; the activity controller
joined the excluded Host relays in the previous commit.
2026-08-31 11:49:00 +08:00
Yichen Jiang 8f031b9a71 fix(ci): declare ui-activity's dsh-session dev dependency under the flattened policy
ActivityListAction imports dsh-session types; the flattened client-face
policy wants that relationship recorded as a devDependency, and the
peer-slimming merge round dropped the entry.
2026-08-31 11:42:51 +08:00
Yichen Jiang f26eb7844e fix(ci): follow the merged kill-detail rendering and pack the refreshed fixtures
- tool-subagent's two job_kill scenarios now see the model's kill reason
  in the killed detail — the registry has merged kill reasons into the
  killed outcome since the human-kill work, and the master merge routed
  subagent aborts through that same settle path; the specs pin the new
  text.
- 112 session fixtures re-recorded by the earlier snapshot refresh
  predate master's canonical packed layout; migrate:packed-session-fixtures
  rewrote them mechanically and the keyless replay stays green.
- ActivityListAction's clear-while-expanded arm gets its missing client
  spec case (per-file coverage gate).
2026-08-31 11:41:17 +08:00
Yichen Jiang 8479c73102 Merge branch 'worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery 2026-08-31 11:41:16 +08:00
Yichen Jiang 8aa2dfa3a5 fix(ci): exempt the activity controller from flattened client-face dependencies
The master merge brought the published dependency policy: a package
declaring dsh.client flattens its non-Cordis peers unless excluded.
api-activity-controller is the same species as the excluded session and
workspace controllers — a Host Remote controller whose host half relays
real runtime peers (dsh-activity for the registry service and its brand
constructor at the wire boundary, dsh-invariants for the companion) —
so it joins CLIENT_FACE_EXCLUDE rather than dropping those peers.
2026-08-31 11:40:11 +08:00
Yichen Jiang 922c80ce66 Merge branch 'worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery
# Conflicts:
#	docs/event-producer-consumer.i18n.yaml
#	docs/event-producer-consumer.md
#	docs/module-graph.i18n.yaml
#	docs/module-graph.md
#	docs/module-graph.zh.md
#	packages/api/session-controller/src/commands.ts
#	packages/api/session-controller/tests/session.client.spec.ts
#	packages/client/ui-activity/package.json
#	packages/shell/tool-bash/src/index.ts
#	packages/shell/tool-bash/tests/tools.spec.ts
#	packages/shell/tool-pwsh/src/index.ts
2026-08-31 11:22:11 +08:00
Yichen Jiang 6b2cc74984 Merge remote-tracking branch 'origin/master' into worktree/process-plugin-streaming-72f9b3
# Conflicts:
#	docs/config-catalog.i18n.yaml
#	docs/config-catalog.md
#	docs/config-catalog.zh.md
#	docs/event-producer-consumer.i18n.yaml
#	docs/module-graph.i18n.yaml
#	docs/module-graph.md
#	docs/module-graph.zh.md
#	packages/api/remotes/package.json
#	packages/client/ui-activity/package.json
#	packages/workflow/tool-workflow/src/index.ts
2026-08-31 11:18:10 +08:00
Yichen Jiang 74fa4be0a3 Merge branch 'worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery
# Conflicts:
#	docs/event-producer-consumer.i18n.yaml
#	docs/event-producer-consumer.md
#	docs/event-producer-consumer.zh.md
#	packages/api/session-controller/src/types.ts
#	packages/api/session-controller/tests/fake-api.client.ts
#	packages/client/connection/src/rpc.ts
#	packages/extensions/cordis-client-runner/src/client/api-catalog.ts
#	packages/extensions/tool-cordis/src/api-catalog.ts
2026-08-29 14:23:22 +08:00
Yichen Jiang e47580f604 Merge remote-tracking branch 'origin/master' into worktree/process-plugin-streaming-72f9b3
# Conflicts:
#	docs/config-catalog.i18n.yaml
#	docs/event-producer-consumer.i18n.yaml
#	docs/module-graph.i18n.yaml
#	docs/module-graph.md
#	docs/module-graph.zh.md
#	packages/client/ui-primitives/README.i18n.yaml
#	packages/client/ui-primitives/tests/use-dismiss-on-outside-pointer.client.spec.tsx
2026-08-29 14:13:36 +08:00
Yichen Jiang 02ef5e408f Merge branch 'worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery
# Conflicts:
#	docs/module-graph.i18n.yaml
#	docs/module-graph.md
#	docs/module-graph.zh.md
#	packages/shell/bash-sandbox/tests/sandbox.spec.ts
#	packages/shell/tool-pwsh/tests/tools.spec.ts
2026-08-28 17:33:17 +08:00
Yichen Jiang 70311bc517 Merge remote-tracking branch 'origin/master' into worktree/process-plugin-streaming-72f9b3
# Conflicts:
#	docs/architecture.i18n.yaml
#	docs/config-catalog.i18n.yaml
#	docs/event-producer-consumer.i18n.yaml
#	docs/module-graph.i18n.yaml
#	docs/module-graph.md
#	docs/module-graph.zh.md
#	knip.json
#	packages/bundle/web-app/cordis.patch.yml
#	packages/bundle/web-app/package.json
#	packages/client/README.i18n.yaml
#	packages/client/README.md
#	packages/client/README.zh.md
#	packages/extensions/cordis-client-runner/src/client/slot-catalog.ts
#	packages/shell/tool-bash/package.json
#	packages/shell/tool-pwsh/package.json
#	packages/workflow/tool-workflow/package.json
#	scripts/verify-package-readme-model-experience.ts
#	tsconfig.base.json
#	tsconfig.client.json
2026-08-28 17:29:41 +08:00
Yichen Jiang 48bbaa3e31 Merge branch 'worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery
# Conflicts:
#	docs/config-catalog.i18n.yaml
#	docs/config-catalog.md
#	docs/config-catalog.zh.md
#	docs/event-producer-consumer.i18n.yaml
#	docs/event-producer-consumer.md
#	docs/event-producer-consumer.zh.md
#	packages/api/session-controller/README.i18n.yaml
#	packages/api/session-controller/README.md
#	packages/api/session-controller/README.zh.md
#	packages/api/session-controller/src/types.ts
#	packages/api/session-controller/tests/fake-api.client.ts
#	packages/api/session-controller/tests/test-remote.ts
#	packages/extensions/tool-cordis/src/api-catalog.ts
#	packages/host/apiproxy/src/api/rpc.schema.ts
#	packages/host/apiproxy/src/api/rpc.ts
2026-08-28 10:27:00 +08:00
Yichen Jiang d4683d5552 Merge remote-tracking branch 'origin/master' into worktree/process-plugin-streaming-72f9b3
# Conflicts:
#	docs/capability-seams.i18n.yaml
#	docs/capability-seams.md
#	docs/capability-seams.zh.md
#	docs/config-catalog.i18n.yaml
#	docs/event-producer-consumer.i18n.yaml
#	docs/module-graph.i18n.yaml
#	docs/module-graph.md
#	docs/module-graph.zh.md
#	packages/bundle/web-app/cordis.patch.yml
#	packages/bundle/web-app/package.json
#	packages/client/ui-activity/package.json
#	packages/workflow/tool-workflow/README.i18n.yaml
2026-08-28 10:14:49 +08:00
Yichen Jiang 72efe7f15a Merge branch 'worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery 2026-08-27 21:48:33 +08:00
Yichen Jiang 5dcba9f760 Merge remote-tracking branch 'origin/master' into worktree/process-plugin-streaming-72f9b3
# Conflicts:
#	tsconfig.base.json
2026-08-27 21:48:02 +08:00
Yichen Jiang 9f24670323 test(session-controller): follow the two-argument Session constructor in the killJob specs
The master merge slimmed the client Session constructor to (id, remote,
options); the killJob specs from this branch still passed the FakeApiClient
as a second argument and stopped compiling.
2026-08-27 21:43:34 +08:00
Yichen Jiang df7e779a58 Merge branch 'worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery
# Conflicts:
#	packages/shell/tool-bash/tests/observe.spec.ts
#	packages/shell/tool-pwsh/tests/observe.spec.ts
#	packages/workflow/tool-workflow/tests/activity-mirror.spec.ts
2026-08-27 21:41:57 +08:00
Yichen Jiang e5d5d16dd2 Merge branch 'worktree/process-plugin-streaming-72f9b3' into worktree/job-kill-delivery
# Conflicts:
#	docs/event-producer-consumer.i18n.yaml
#	docs/event-producer-consumer.md
#	docs/event-producer-consumer.zh.md
#	packages/api/session-controller/src/client/sessions/session.ts
#	packages/extensions/cordis-client-runner/src/client/api-catalog.ts
#	packages/extensions/tool-cordis/src/api-catalog.ts
#	packages/host/apiproxy/src/api/rpc.schema.ts
#	packages/session/session-projection-cache/tests/cache.spec.ts
#	packages/shell/tool-bash/tests/observe.spec.ts
#	packages/shell/tool-pwsh/tests/observe.spec.ts
#	snapshots/web/approval-composer/session.jsonl
#	snapshots/web/code-mode-round/session.jsonl
#	snapshots/web/cordis-tool-round/session.jsonl
#	snapshots/web/feedback-command/session.jsonl
#	snapshots/web/fresh-round-trip/session.jsonl
#	snapshots/web/goal-multi-turn-actions/session.jsonl
#	snapshots/web/lifecycle-chrome/session.jsonl
#	snapshots/web/live-interactions/session.jsonl
#	snapshots/web/minimal-preset/session.jsonl
#	snapshots/web/permission-policy-context/session.jsonl
#	snapshots/web/plan-review/session.jsonl
#	snapshots/web/question-composer/session.jsonl
#	snapshots/web/steering/session.jsonl
#	snapshots/web/turn-tail-actions/session.jsonl
#	snapshots/web/web-search-round/session.jsonl
2026-08-27 21:41:16 +08:00
Yichen Jiang 053979cdec test(activity): cover the owner arm and the pwsh outer observation catch
The coverage lanes flag four uncovered locations after the master merge:
the spec.owner spread in tool-bash/tool-pwsh observe, the workflow mirror's
owner spread, and tool-pwsh's outer observation catch. The covering tests
existed only on the stacked job-kill branch; give this branch its own:
an owned background run per shell asserting the fenced ownerSession row,
the owned workflow mirror run, and a throwing open() through the pwsh
outer catch. The windows-only file-reference-local gap reproduces on
master's own windows lane at the merge point and stays upstream.
2026-08-27 21:40:44 +08:00
Yichen Jiang ab75fe2bb6 Merge remote-tracking branch 'origin/master' into worktree/process-plugin-streaming-72f9b3
# Conflicts:
#	docs/capability-seams.i18n.yaml
#	docs/capability-seams.md
#	docs/capability-seams.zh.md
#	docs/config-catalog.i18n.yaml
#	docs/config-catalog.md
#	docs/config-catalog.zh.md
#	docs/event-producer-consumer.i18n.yaml
#	docs/module-graph.i18n.yaml
#	docs/module-graph.md
#	docs/module-graph.zh.md
#	knip.json
#	packages/api/remotes/src/client/index.ts
#	packages/api/remotes/tsconfig.client.json
#	packages/bundle/web-app/package.json
#	packages/client/ui-chat/src/client/contract/slots.ts
#	packages/client/ui-chat/src/client/conversation-nodes/chat-snapshot-builder.ts
#	packages/client/ui-primitives/README.i18n.yaml
#	packages/client/ui-subagent/README.i18n.yaml
#	packages/extensions/cordis-client-runner/src/client/slot-catalog.ts
#	scripts/gen-doc-graphs.ts
#	tsconfig.client.json
#	tsconfig.host.json
2026-08-27 20:03:20 +08:00
Yichen Jiang 32e9f33fd2 feat(web): tighten task-list controls, fold the settled tail, merge the live meta line
Review of the first hierarchy pass in the browser: the 26px bordered controls
read oversized and the card fill visually stopped short of the stop button.
The controls drop to 20px squares, the live card's inner padding now frames
the dot and both controls on the fill, and the hover wash moves onto the card
itself so nothing escapes it. A live row's second line becomes kind + ticking
duration (the redundant 'running' word only returns through a real detail).
The settled tail folds behind a 'Finished N' toggle while live work exists
(auto-expanded otherwise) with a client-side Clear that hides the current
tail without touching the registry; goldens re-recorded for the new tree.
2026-08-26 17:10:26 +08:00
Yichen Jiang 4edf279ed6 feat(web): raise the task list's control affordance and row hierarchy
Live rows read as filled cards (fill-l2, the kind badge pops on the menu
surface instead), the expand chevron and the stop control become persistent
bordered squares (the stop turns danger on hover), and the popover widens to
500px. The settled tail keeps its flat rows, so running work now outranks it
visually — previously the colored settled dots were the loudest thing in the
list and both controls were bare tertiary glyphs invisible before hover.
2026-08-26 17:00:25 +08:00
Yichen Jiang bb1d472e94 fix(web): make the armed stop press legible without hover
The armed state relied on a color swap through --dsw-alias-label-error, a
token the themes never define, so the confirm step was effectively invisible.
The first press now widens the button into a labeled danger pill (确认停止 /
Confirm stop) tinted with the real state-error token StateDot uses; the
stream-failure notice moves off the same phantom token. Component spec pins
the inline label.
2026-08-26 16:35:11 +08:00
Yichen Jiang 8f3103b511 fix(jobs): address review — error scoping, subagent fence, pending kill
- session.killJob maps job-not-found from the lookup only: jobs.get gates the
  same synchronous span, so a producer-cancel throw now propagates per the
  registry contract (job state unchanged) instead of masquerading as a lookup
  failure (threads r3860226021, r3860226047).
- session.killJob applies the service-wide subagent ownership fence exactly as
  cancel does; a subagent-owned live session rejects with the ownership error
  and the child-owned job stays running (thread r3860226041).
- The stop control stays pending on an admitted kill: the unary response and
  the jobs frames have no cross-carrier ordering, so only the authoritative
  frame (the row leaving the killable set) releases the button, closing the
  duplicate-kill window (thread r3860226028).
- One-line comment on the killReason assignment stating last-writer-wins is
  intended, beside the first-claim-wins reported handling (thread r3860225999).

Host spec pins the producer-throw propagation and the fence; the component
spec pins pending-until-frame; the Agent Note records both refinements.
2026-08-26 16:19:34 +08:00
Yichen Jiang d6bebc5783 feat(shell): converge on execute() and promote timed-out commands to jobs
The executor seam collapses run()/start() (and the planned begin()) into one
resolve() + execute(): the handle IS the live process, and the foreground
result, the background cursor, and the deadline's promotion offer are
projections over the same spawn. ShellExecSpec.onExpiry ('kill' default /
'offer' / 'none') replaces the implicit method split; one stdout budget applies
everywhere, the 'start ignores timeoutMs' wart dies, and sync spawn throws are
contained into the same settled-killed handle as async rejections (result()
rejects with the original error, identity preserved for the sandbox
classification).

The promotion offer settles exactly once and never rejects — offer at an
'offer' deadline while running, undefined when the process settles first — so
'await execution.promotion' alone distinguishes the outcomes. An offer must be
answered synchronously; an unanswered one auto-declines back to the
kill-on-timeout behavior. accept() ends the deadline and detaches the caller's
abort signal; decline() kills and classifies timedOut.

tool-bash and tool-pwsh promote on timeout by default (promoteOnTimeout: true,
inert without run_in_background or a job registry): at the offer the running
handle registers as a job with the same hooks as run_in_background, mirrors
into ctx.activities (the web task list streams it, stop control included), and
the call returns { kind: 'promoted', jobId, timeoutMs, output } carrying the
output so far — the job cursor continues exactly after it. Any promotion
failure declines and falls back to the plain timeout kill. Descriptions and
the timeoutMs schema text advertise the semantics only when they hold;
recorded-session and web snapshot fixtures re-recorded with them.

Also repairs the four CI failures from the human-kill push: ui-activity's
missing dsh-session peer declaration (static + windows observational), the
observe owner-arm/outer-catch and killJob-stub coverage gaps, tool-workflow's
owned-run arm, and the timing-dependent projection-cache interval flush test
(now poll-based for coarse Windows timers).

Peer evidence and rejected alternatives (Codex's session model, a third seam
method, a second threshold knob) in the Agent Note
2026-08-26-shell-execute-timeout-promotion.
2026-08-26 16:11:01 +08:00
Yichen Jiang 51f74910ae feat(jobs): human job kill with an unclaimed terminal report
The web task list gains a two-press stop control on running job rows, built on
one seam change: JobRegistry.kill now takes { reason?, reported? } instead of a
positional reason. reported defaults to true (the model's job_kill claims the
terminal report exactly as before); the web path passes reported: false because
a human kill has no model-visible channel, so the settlement flows through the
existing tool-jobs completion notice (wakeup/inject, budgets, truncation all
unchanged). A recorded kill reason merges into a killed settlement's detail
(producer facts first: 'signal: SIGTERM; cancelled by the user'), so the notice
and the web row both say who stopped the work.

- session-controller: session.killJob Remote beside cancel, live-only Agent
  lookup, job-not-found/jobs-unavailable rejections joined to the closed RPC
  error table; ClientSession.killJob and the ISession face carry it to the UI.
- ui-activity: arm/confirm stop button (3s disarm, pending disable, failure
  hint), rendered only on running job rows; standalone activities keep no kill
  handle and the README limitation now says so.
- The background-job-list web e2e now drives the whole human path through the
  button; goldens show the merged killed detail end to end.
- Agent Note 2026-08-26-human-job-kill records the decision against Kimi's
  suppress-own-notification precedent and Claude Code's silent-kill failure;
  the 2026-08-08 blocker note and the unified-task-list note point at it.
2026-08-26 14:15:44 +08:00
Yichen Jiang 47ee13dd74 fix(ci): repair the four failures the master merge left behind
- live-activity-stream.e2e.ts still seeded from the old
  apps/web/tests/snapshots path and navigated to the unauthenticated
  baseUrl; the merge moved session recordings to snapshots/web/ and put
  browser trust behind scaffold.authenticatedUrl, and both updates reached
  the pre-existing background-job lane but not this branch-new file.
- tool-bash/tool-pwsh observe: the settlement-mapping catch introduced by
  the review fix had no covering test; both specs now drive a throwing
  end() through it.
- useDismissOnOutsidePointer lost its only branch coverage when the
  ui-jobs suite was deleted; the hook now has its own unit spec (outside
  close, inside ignore, closed detach, non-Node target).
- THIRD_PARTY_NOTICES.md regenerated at the merged lockfile's
  claude-agent-sdk 0.3.241: stale 0.3.220 store directories made the local
  generator resolve the old payload, so the committed file disagreed with
  a clean install.
2026-08-26 10:54:23 +08:00
Yichen Jiang c8c3540f15 Merge remote-tracking branch 'origin/master' into worktree/process-plugin-streaming-72f9b3
# Conflicts:
#	docs/capability-seams.i18n.yaml
#	docs/capability-seams.md
#	docs/capability-seams.zh.md
#	packages/bundle/web-app/package.json
#	packages/client/README.i18n.yaml
#	packages/client/README.md
#	packages/client/README.zh.md
#	packages/client/ui-activity/README.i18n.yaml
#	packages/client/ui-chat/src/client/conversation-nodes/retry.ts
#	packages/client/ui-jobs/README.md
#	packages/client/ui-jobs/README.zh.md
#	packages/client/ui-primitives/README.i18n.yaml
#	packages/client/ui-primitives/README.md
#	packages/client/ui-primitives/README.zh.md
#	packages/client/ui-subagent/README.i18n.yaml
#	packages/client/ui-subagent/README.md
#	packages/client/ui-subagent/README.zh.md
#	packages/jobs/jobs/README.i18n.yaml
#	packages/jobs/jobs/README.md
#	packages/jobs/jobs/README.zh.md
#	packages/shell/shell/README.i18n.yaml
#	packages/shell/shell/README.md
#	packages/shell/shell/README.zh.md
#	packages/shell/tool-bash/README.i18n.yaml
#	packages/shell/tool-bash/README.md
#	packages/shell/tool-bash/README.zh.md
#	packages/shell/tool-pwsh/README.i18n.yaml
#	packages/shell/tool-pwsh/README.md
#	packages/shell/tool-pwsh/README.zh.md
#	packages/workflow/tool-workflow/README.i18n.yaml
#	packages/workflow/tool-workflow/README.md
#	packages/workflow/tool-workflow/README.zh.md
2026-08-26 10:17:30 +08:00
Yichen Jiang 5ee10b3ebd Merge remote-tracking branch 'origin/master' into worktree/process-plugin-streaming-72f9b3
# Conflicts:
#	packages/client/ui-chat/src/client/chat/ContextInjectionRow.tsx
2026-08-25 16:50:24 +08:00
Yichen Jiang 69d8b6e7ea fix(activity): bind observation cleanup to its generation and settle before mapping the outcome
Both ds-review-bot criticals verified and fixed:

- ClientActivityFeed release closures resolved the entries map at release
  time, not the entry they were minted for. A late release of a superseded
  observation decremented (and could tear down) the successor's stream, and
  the post-dispose model clear could land after a re-observation inside the
  dispose round-trip re-anchored the view — blanking the panel until the
  popover reopened (React StrictMode's double effect hits the same window).
  Releasers now capture their entry, reconcile the map only when it still
  holds that entry, and skip the view clear whenever a successor owns the
  key. Both regressions fail on the previous code.

- The background observation tap mapped the process outcome as soon as the
  pump settled — including its failure path, where the process is usually
  still running: processOutcome requires a settled handle, so an early
  mapping froze 'exit code: 0' onto a live task and first-wins end discarded
  the real terminal state. The tap now awaits proc.done before mapping (the
  promise never rejects) and swallows mapping failures per the module's
  contract, in bash and its pwsh mirror.

Also applied from the same review: the Remote tier's cross-session
visibility is now stated in the controller README, ActivityRead.next
documents its chunk-boundary nature that client concatenation relies on,
mergeRows records the one-activity-per-job assumption, and the 08-24 Agent
Note now reflects the shipped unified task list.
2026-08-25 15:46:15 +08:00
Yichen Jiang f9c3a6c753 fix(activity): banner divider on live output and headings for both task sections
The TerminalBlock banner divider used to key off the running flag from the
days when a running card was banner-only; a running card that streams live
output now needs the same separation, so the divider follows the rendered
body (data-with-body) instead. The task list labels each non-empty section
(Running / Finished) rather than only dividing the settled tail, and the
leading section drops the divider line above its heading.
2026-08-25 14:58:26 +08:00
Yichen Jiang f10572a85d chore(client): regenerate the slot catalog after the master merge 2026-08-25 14:42:20 +08:00
Yichen Jiang 5cd435e917 Merge remote-tracking branch 'origin/master' into worktree/process-plugin-streaming-72f9b3
# Conflicts:
#	docs/config-catalog.i18n.yaml
#	docs/config-catalog.md
#	docs/module-graph.i18n.yaml
#	docs/module-graph.md
#	docs/module-graph.zh.md
#	packages/client/ui-chat/src/client/contract/slots.ts
#	packages/shell/tool-pwsh/package.json
#	packages/workflow/tool-workflow/src/index.ts
#	python/sdk-runtime/package.json
2026-08-25 14:36:33 +08:00
Yichen Jiang e200eadd73 fix(activity): acceptance refinements — header band order and panel presentation
Interactive acceptance produced five presentation decisions:

- Header band order is preset → tasks → subagent catalog. The root-session
  descendant count moves out of the breadcrumb (header.lineage) into the
  actions band (order 30) as SubagentCatalogAction and drops its '/'
  separator — a root session has no hierarchy to breadcrumb. Child
  sessions keep their root/child switcher unchanged.
- The panel's copy control copies the command (TerminalBlock.copyText),
  not the output: a long command ellipsizes in the row, so the control is
  its only complete source, and it stays rendered before any output.
- Commands and output lines wrap in full (--dsl-terminal-command-whitespace
  / --dsl-terminal-line-whitespace: pre-wrap), so the output region never
  scrolls sideways; the region caps at a fixed height and scrolls
  (--dsl-terminal-output-max-height) with maxLines: Infinity disabling the
  head/tail fold.
- The panel draws no run-state dot (TerminalBlock.runStateDot: false) —
  the row above carries the same state — and reclaims the dot gutter via
  --dsl-terminal-gutter, which must target the card element itself because
  the variable is declared on it. The panel's left spine is gone with it.

All TerminalBlock changes are opt-ins (props or --dsl-terminal-* variables);
tool cards keep their shipped fold/ellipsis/dot behavior.
2026-08-25 14:23:12 +08:00
Yichen Jiang 61aadb3abf feat(activity): one session-header task list joining jobs with their activities
Browser acceptance of the activity panel surfaced the product problem: a
background bash registers in both planes, so the header grew two near-
identical popovers — "background jobs" with lifecycle and duration,
"activities" with live output — listing the same work twice.

The service planes stay orthogonal (jobs: model-visible control with a
consuming read cursor; activities: transient non-consuming observation).
The join happens per row in the UI projection instead:

- dsh-client-ui-activity renders the single merged "tasks" list. Each
  jobsBySession row joins the activity carrying its correlation.jobId —
  the job supplies identity, lifecycle (stopping exists only there),
  duration, and the model-visible detail; the activity supplies the
  expandable live-output panel. Standalone activities (workflow runs)
  keep their own rows; without the activity registry, job rows still
  render and the control degrades to the old job list exactly.
- dsh-client-ui-jobs is deleted (pre-release, no shim); the merged entry
  takes its slot order. The job locale namespace folds into activity.
- User-facing copy says "task" (任务): N tasks running / N 个任务进行中.
- Hierarchy: live rows first as two lines (label over kind badge and
  status, ticking duration), a Finished divider when both sections
  exist, settled rows compacted to one de-emphasized line, and the
  output panel hangs behind a spine under its row.

Deliberately not joined: subagent rows stay bare (the subagent panel is
their surface) and foreground commands stay in tool cards.

Both web e2e scenarios now assert one Tasks list; the background-job
scenario's row gained a live-output panel, proving the join end to end.
Agent Note: 2026-08-25-unified-task-list.
2026-08-25 11:58:43 +08:00
Yichen Jiang ddd8baa93a fix(activity): mark evicted heads on fresh observation and fit the popover to the viewport
Browser acceptance against a live scaffold surfaced two defects:

- A fresh observation anchors at the registry's earliest retained byte, so
  the client's gap predicate (anchor behind earliest) never fired for it: a
  viewer who watched eviction live saw the gap notice, while a reload of the
  same activity silently hid that the head was discarded. A fresh view (no
  accumulated text) anchored past offset zero now carries the gap mark;
  resumes keep their recorded state.

- The trigger-anchored popover (left: 0, width 440px) overflowed the right
  viewport edge on narrow windows — max-width alone cannot see the anchor
  offset. The open popover now measures its anchor and shifts left to keep
  the Menu primitive's 12px viewport margin, re-fitting on window resize and
  falling back to the pure CSS anchor when layout reports no width (jsdom).

JobListAction shares the latent overflow; tracked separately.
2026-08-24 23:07:17 +08:00
Yichen Jiang 4a79310339 feat(activity): optional live-output observation seam with a web viewer
Add the activity capability seam: ctx.activities is a pure observation
plane where long-running work (background bash/pwsh, workflow runs) opens a
handle, appends transient output into a byte-offset ring buffer, and settles
exactly once. Nothing model-visible changes: reads are non-consuming and
lossy-by-design (absolute UTF-8 offsets survive eviction and report gaps),
correlation carries {callId, jobId}, and visibility is owner-fenced like jobs.

The seam is strictly optional. Producers tap it through ctx.get('activities')
and contain every observation failure, so an unregistered or broken registry
never affects the underlying job (proven by broken-registry tests).

Ship the full path in one change:
- @deepseek-ai/dsh-activity: Service Definition, pumpActivityOutput pull
  substrate, invariant companion; dsh-activity-local: ring-buffer provider
  with retention config and owner cleanup.
- shell: ShellProcess.observed exposes non-consuming stdout/stderr readers;
  tool-bash/tool-pwsh mirror background jobs into activities.
- tool-workflow: phase/log/agent lines mirrored as a workflow activity.
- api-activity-controller: Typert Remote control (roster baseline+buckets)
  and observe (opened/output/status frames, coalesced and byte-capped)
  streams plus the client feed service with reference-counted observation.
- ui-activity: session-header action rendering live output through
  TerminalBlock, which now shows output while a command is still running.

Docs: activity subsystem page, architecture row, regenerated catalogs, and
the Agent Note (2026-08-24-activity-observation-seam), all bilingual. A
keyless web e2e replays a real run_in_background stream to ARIA goldens.
2026-08-24 22:40:14 +08:00
Yichen Jiang ff5f94d3fb refactor(client): import conversation node types from their declaring package
Adding another client-face project to the client aggregate reshuffles the
typert analyzer's batches, which exposed latent provenance violations in
ui-chat and ui-approval: types consumed through ui-conversation's re-export
path instead of their declaring package, and one slot-contract literal whose
type the analyzer could not name. Import the types from
@deepseek-ai/dsh-client-ui-conversation/client directly and give the approval
interaction kind a named alias so the explicit annotation survives both the
analyzer and lint.
2026-08-24 22:40:00 +08:00
5142 changed files with 382935 additions and 119004 deletions

No files matched your search

+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/README.md
README.md: 9fdb4039b525bbc4051ab3c8058b117e4de5f2f4
README.zh.md: 2d7e07b963d3c4d77da9148b011e1478fa2ae5da
README.md: 6872de0a1390fa5a1707b9f464c9e0edeb2da4be
README.zh.md: ced81f77fa904ad0b5cb5219383ef651d87392b8
+3 -3
View File
@@ -16,7 +16,7 @@ Every Agent Note has two axes, both encoded in its **path** — `{lifecycle}/{cl
The date in the filename is when the topic was **first proposed** (per git history). Cross-references between Agent Notes use relative markdown links (`[topic](../../implemented/architecture/2026-…-….md)`) — never bare prose or numbers — so they are mechanically checkable and survive moves between folders.
The active lifecycle tree is the working inventory: browse its lifecycle/class folders or search the repository. Do not add a centralized `INDEX.md`; the [no-index Agent Note](implemented/process/2026-07-19-remove-generated-agent-note-index.md) owns the rationale. Low-future-value implemented records move to the separate frozen [`archived/`](archived/AGENTS.md) tree described below.
The active lifecycle tree is the working inventory: browse its lifecycle/class folders or search the repository. Do not add a centralized `INDEX.md`; the [no-index Agent Note](implemented/process/2026-07-19-remove-generated-agent-note-index.md) owns the rationale. Implemented records with historical decision value but little future guidance move to the separate frozen [`archived/`](archived/AGENTS.md) tree described below.
## Classification
@@ -35,9 +35,9 @@ The `architecture` / `process` line: **architecture** is about the source we shi
## Archiving and deletion
Archive an implemented Agent Note when the shipped decision is complete and its rationale is unlikely to guide future work. Keep it active when its alternatives, ownership boundary, negative guarantee, durable or wire semantics, security rule, or reintroduction condition remains useful. Never archive a proposed note: reject an obsolete proposal. Keep a rejected note only while it prevents a plausible mistake; otherwise delete its English, Chinese, and sidecar files together. Use the calibrated [`dsh-archive-agent-notes`](../skills/dsh-archive-agent-notes/SKILL.md) workflow rather than word count, age, or a target quota.
Delete an implemented Agent Note that only describes small UI adjustments or purely mechanical changes; delete its English, Chinese, and sidecar files together and repair or remove inbound links. A local bug fix, performance change, new capability, or substantive behavior decision does not qualify merely because its implementation is small. Archive other implemented notes only when the shipped decision is complete, its rationale is unlikely to guide future work, and the record still has historical decision value. Keep it active when its alternatives, ownership boundary, negative guarantee, durable or wire semantics, security rule, or reintroduction condition remains useful. Never archive a proposed note: reject an obsolete proposal. Keep a rejected note only while it prevents a plausible mistake; otherwise delete its English, Chinese, and sidecar files together. Use the calibrated [`dsh-archive-agent-notes`](../skills/dsh-archive-agent-notes/SKILL.md) workflow rather than word count, age, or a target quota.
The archive is path-encoded as `archived/{class}/yyyy-mm-dd-topic-title.md`; `implemented` is deliberately absent because only implemented notes can enter it. An archival change moves the complete English/Chinese/sidecar triplet, retains `Status: implemented`, inserts the same `Archived: YYYY-MM-DD` line immediately below that status in both language files, re-records the sidecar, and repairs or deletes inbound links. These are the only permitted content changes during archival.
The archive is path-encoded as `archived/{class}/yyyy-mm-dd-topic-title.md`; `implemented` is deliberately absent because only implemented notes can enter it. An archival change moves the complete English/Chinese/sidecar triplet, retains `Status: implemented`, inserts the same `Archived: YYYY-MM-DD` line immediately below that status in both language files, re-records the sidecar, and repairs or deletes inbound links. These are the only permitted content changes during archival. Existing title punctuation, blank-line layout, and language-switcher wording do not block archival; preserve them with the body.
Once sealed, every archived triplet is permanently frozen. Do not edit, translate, reformat, update, move, or delete it, and do not treat it as authority for current behavior. Documentation gates skip archived sources, including their outbound links; active prose may still link into an archived note when it intentionally cites history. [`verify-archived-agent-notes`](../../scripts/verify-archived-agent-notes.ts) enforces the closed class tree, complete triplets, archive metadata, sidecar hashes, and the append-only frozen-content manifest. The [archive-policy Agent Note](implemented/process/2026-07-26-frozen-agent-note-archive.md) owns the rationale.
+3 -3
View File
@@ -16,7 +16,7 @@
文件名中的日期是该主题**首次提出**的时间(以 git 历史为准)。Agent Note 之间的交叉引用使用相对 Markdown 链接(`[topic](../../implemented/architecture/2026-…-….md)`),从不使用纯文字或编号,这样既可机械检查,也能在文件夹间移动时保持有效。
活跃生命周期目录树就是工作清单:浏览其生命周期/类别文件夹,或搜索仓库即可。请勿添加集中式 `INDEX.md`;设计理由见[不设索引的 Agent Note](implemented/process/2026-07-19-remove-generated-agent-note-index.zh.md)。未来指导价值较低的已实施记录会移至下文所述、单独冻结的 [`archived/`](archived/AGENTS.md) 目录树。
活跃生命周期目录树就是工作清单:浏览其生命周期/类别文件夹,或搜索仓库即可。请勿添加集中式 `INDEX.md`;设计理由见[不设索引的 Agent Note](implemented/process/2026-07-19-remove-generated-agent-note-index.zh.md)。具有历史决策价值但未来指导价值较低的已实施记录会移至下文所述、单独冻结的 [`archived/`](archived/AGENTS.md) 目录树。
<a id="classification"></a>
@@ -37,9 +37,9 @@
## 归档与删除
当一份 implemented Agent Note 记录的交付决策已经完整落地,且其决策依据不太可能再指导未来工作时,将其归档。如果其中的备选方案、归属边界、否定性保证、持久化语义或协议语义、安全规则,或者重新引入条件仍有价值,则继续作为活跃记录保留。绝不归档 proposed Agent Note:过时的提案应转为 rejected。仅当 rejected Agent Note 仍能避免一种可能发生的错误时保留;否则一并删除其英文、中文和伴随记录文件。请使用经过校准的 [`dsh-archive-agent-notes`](../skills/dsh-archive-agent-notes/SKILL.md) 工作流,不要根据字数、存续时间或目标配额来判断。
仅描述小型 UI 调整或纯机械变更的 implemented Agent Note 直接删除;一并删除英文、中文和伴随记录文件,并修复或移除入站链接。局部 bug 修复、性能变更、新能力或实质行为决策,不会仅因实现规模小就符合删除条件。其他已实施记录只有在交付决策完整落地、其依据不太可能再指导未来工作,但仍具有历史决策价值时才归档。如果其中的备选方案、归属边界、否定性保证、持久化语义或协议语义、安全规则,或者重新引入条件仍有价值,则继续作为活跃记录保留。绝不归档 proposed Agent Note:过时的提案应转为 rejected。仅当 rejected Agent Note 仍能避免一种可能发生的错误时保留;否则一并删除其英文、中文和伴随记录文件。请使用经过校准的 [`dsh-archive-agent-notes`](../skills/dsh-archive-agent-notes/SKILL.md) 工作流,不要根据字数、存续时间或目标配额来判断。
归档路径编码为 `archived/{class}/yyyy-mm-dd-topic-title.md`;其中有意省略 `implemented`,因为只有 implemented Agent Note 可以进入归档。归档变更会移动完整的英文、中文和伴随记录三个文件,保留 `Status: implemented`,在两种语言的文件中紧接该状态行插入相同的 `Archived: YYYY-MM-DD` 行,重新记录伴随记录,并修复或删除入站链接。归档时只允许对内容做这些更改。
归档路径编码为 `archived/{class}/yyyy-mm-dd-topic-title.md`;其中有意省略 `implemented`,因为只有 implemented Agent Note 可以进入归档。归档变更会移动完整的英文、中文和伴随记录三个文件,保留 `Status: implemented`,在两种语言的文件中紧接该状态行插入相同的 `Archived: YYYY-MM-DD` 行,重新记录伴随记录,并修复或删除入站链接。归档时只允许对内容做这些更改。 既有标题标点、空行布局和语言切换栏措辞不阻止归档;与正文一起保留。
封存后,每组归档文件都永久冻结。禁止编辑、翻译、重新格式化、更新、移动或删除,也不得将其视为当前行为的权威依据。文档门禁会跳过归档源文件,包括其中的出站链接;当活跃文档有意引用历史时,仍可链接到归档 Agent Note。[`verify-archived-agent-notes`](../../scripts/verify-archived-agent-notes.ts) 强制执行封闭的类别目录树、完整的三文件配对、归档元数据、伴随记录 hash,以及仅追加的冻结内容 manifest(元数据清单)。[归档政策 Agent Note](implemented/process/2026-07-26-frozen-agent-note-archive.zh.md) 记录了设计依据。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.md
2026-08-03-per-session-agent-presets.md: 47f2e28c2aac583ce4ca810157e9c7abe58256d5
2026-08-03-per-session-agent-presets.zh.md: 88b643ea710364f18ff7c73a8ca0e10de0fbb44c
2026-08-03-per-session-agent-presets.md: a1f2ad294edfdaaf2dab2d02e858e4dcb0a0c746
2026-08-03-per-session-agent-presets.zh.md: b745c22c91b4017131a32796c085a869a2eabaf0
@@ -1,6 +1,7 @@
# Agent Note: A session's agent is composed from a preset cordis.yml
Status: implemented
Archived: 2026-09-18
English | [中文](2026-08-03-per-session-agent-presets.zh.md)
@@ -1,6 +1,7 @@
# Agent Note: 会话的 agent 由一份 preset cordis.yml 组装而成
Status: implemented
Archived: 2026-09-18
[English](2026-08-03-per-session-agent-presets.md) | 中文
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-11-session-controller-fork-turn-cut.md
2026-09-11-session-controller-fork-turn-cut.md: 0c8d45c861df964a62586827e80c613704ed17f0
2026-09-11-session-controller-fork-turn-cut.zh.md: 729a42a596264ac618608d26cd52a1ad836b58db
2026-09-11-session-controller-fork-turn-cut.md: 9223acd4c0c7bbcc84a0bfeb72f3b57d4bf828e2
2026-09-11-session-controller-fork-turn-cut.zh.md: cae022ae97896c0b43633edb310afb305d690b4a
@@ -1,6 +1,7 @@
# Agent Note: Session Controller forks stop at the selected turn end
Status: implemented
Archived: 2026-09-16
English | [中文](2026-09-11-session-controller-fork-turn-cut.zh.md)
@@ -1,8 +1,9 @@
# Agent Note: Session Controller 分叉截到选中轮次的结束事件
Status: implemented
Archived: 2026-09-16
English | [中文](2026-09-11-session-controller-fork-turn-cut.md)
[English](2026-09-11-session-controller-fork-turn-cut.md) | 中文
## Problem
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-16-movable-mandatory-update-window.md
2026-09-16-movable-mandatory-update-window.md: 4695074429b76b467e6ec90201632dbb5a007e68
2026-09-16-movable-mandatory-update-window.zh.md: 3b416c53dc02abce1276bb70a13bb45b4c1afa44
2026-09-16-movable-mandatory-update-window.md: da996e5b37f990ff773e3ec88de0baed3c4b54f3
2026-09-16-movable-mandatory-update-window.zh.md: 15fd4eb2520a39d8259321a747b33c833b56cbb1
@@ -1,6 +1,7 @@
# Agent Note: Movable mandatory-update window
Status: implemented
Archived: 2026-09-18
English | [中文](2026-09-16-movable-mandatory-update-window.zh.md)
@@ -1,6 +1,7 @@
# Agent Note: 可移动的强更窗口
Status: implemented
Archived: 2026-09-18
[English](2026-09-16-movable-mandatory-update-window.md) | 中文
@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/feature/2026-08-25-unified-task-list.md
2026-08-25-unified-task-list.md: f2f955e36a78eee8fa8ff8a804293bcc37a44acd
2026-08-25-unified-task-list.zh.md: 5a4c51a6cc3d0f331d2559debb73dc02e9cf12c5
@@ -0,0 +1,49 @@
# Agent Note: One session-header task list — jobs joined with activities in the UI projection only
Status: implemented
Archived: 2026-09-03
English | [中文](2026-08-25-unified-task-list.zh.md)
Superseded: the two-roster join described here was removed with the activity seam — the job rows are now the whole roster; see [jobs absorb the record](../architecture/2026-09-01-jobs-absorb-activity-record.md).
## Problem
The [activity observation seam](2026-08-24-activity-observation-seam.md) shipped its Web surface as a second session-header button beside the [background-job list](2026-08-08-web-background-job-display.md). A background bash run registers in both planes — `ctx.jobs` for control and `ctx.activities` for observation — so the same work rendered twice: once in "background jobs" with lifecycle and duration, once in "activities" with expandable live output. Two near-identical popovers with different names for overlapping row sets read as a product accident, and the flat activity rows (live and settled interleaved, single-line, no duration) had no visual hierarchy.
## Decision
The service planes stay orthogonal; the join happens once, per row, in the UI projection.
- **`ctx.jobs` and `ctx.activities` remain separate services.** Jobs are the control plane: model-visible ids, a consuming `readOutput` cursor the model drains, `stopping`, `kill`, and the `reported` completion-notice contract. Activities are the transient observation plane: non-consuming absolute offsets, invisible to the model, gone on restart. Merging the services would force one registry to carry both a consuming and a non-consuming cursor and would blur the "model-visible ⟺ logged" line for a plane designed to stay off the session log.
- **`dsh-client-ui-activity` renders the single merged list; `dsh-client-ui-jobs` is deleted** (pre-release, no compatibility shim), and the merged entry takes the job list's slot order. Each `jobsBySession` row is joined with the activity carrying its `correlation.jobId`: the job supplies identity, lifecycle (`stopping` exists only there), duration, and the model-visible `detail`; the activity supplies the expandable output panel. An activity without a job (a workflow run) keeps its own row; an activity whose job is not in the projection is not dropped. Without the activity registry, job rows still render — they just offer no panel, so the merged control degrades to exactly the old job list.
- **User-facing vocabulary is "task" (任务)** — `N tasks running` / `N 个任务进行中` — because every row is running work to the user, while "background job" excludes workflow runs and "activity" names the internal plane, not the user concept. The dictionary namespace and package names stay `activity`: internal names follow the naming ledger, not display copy.
- **Hierarchy**: live rows first (label over a kind-badge-and-status second line, ticking duration), then settled rows compacted to one de-emphasized line; each non-empty section carries its own heading (Running / Finished). Rows without an observable activity render as static rows with no expansion affordance.
Deliberately not joined: subagent delegation rows stay bare job rows (the subagent panel is their surface), and foreground commands stay in their tool cards — neither gains an activity producer.
## Alternatives considered
- **Merge the services** — rejected for the cursor-semantics and model-visibility reasons above.
- **Keep the job list and bolt output onto it** — leaves workflow runs (activities without jobs) homeless, forcing a second list to survive anyway.
## Refinements from browser acceptance
Driving the merged list in the real app produced four presentation decisions, all scoped to the expanded panel and the header band:
- **The header band order is preset → tasks → subagent catalog.** The root-session descendant count moved out of the breadcrumb (`header.lineage`) into the actions band (order 30) and dropped its `/` separator — a root session has no hierarchy to breadcrumb; child sessions keep their `root / child` switcher where the `/` is real. `SubagentCatalogAction` owns the moved seat; the lineage renderer now renders nothing on roots.
- **The panel's copy control copies the command** (`TerminalBlock.copyText`), not the output: a long command ellipsizes in the row, so the control is its only complete source, while output is selectable text below. Supplying `copyText` also keeps the control rendered before any output exists.
- **The panel scrolls instead of folding**: commands and output lines wrap in full (`--dsl-terminal-command-whitespace` / `--dsl-terminal-line-whitespace: pre-wrap`, so the output region never scrolls sideways) and the output region caps at a fixed height (`--dsl-terminal-output-max-height`) with `maxLines: Infinity` disabling the head/tail fold.
- **The panel draws no run-state dot** (`TerminalBlock.runStateDot: false`) — the row above it already carries the same state — and reclaims the dot gutter via `--dsl-terminal-gutter`. The panel's left spine was dropped with it.
All four land as opt-ins on `TerminalBlock` (props or `--dsl-terminal-*` variables), so tool cards keep their shipped fold/ellipsis/dot behavior unchanged.
## Testing
Component suites cover the join (job lifecycle wins, activity supplies the panel, bare-job rows, standalone activities), sections, durations, and the panel options; `pnpm vitest run packages/client/ui-activity packages/client/ui-primitives packages/client/ui-subagent` runs them. The keyless web e2e pair (`background-job-list`, `live-activity-stream`) replays the merged list end to end against ARIA goldens; `agent-preset-selection` pins the header band order.
## Consequences
- The two web e2e scenarios (`background-job-list`, `live-activity-stream`) now assert one "Tasks" list; the job-list scenario's row gained a live-output panel, which is the join working end to end.
- A human kill control on live rows is still blocked on the jobs `reported` contract question recorded in the job-display note.
- The `job` locale namespace is gone with its package; `activity` owns the merged copy including the duration vocabulary.
@@ -0,0 +1,49 @@
# Agent Note: 会话头部单一任务列表——jobs 与 activity 只在 UI 投影层合并
Status: implemented
Archived: 2026-09-03
[English](2026-08-25-unified-task-list.md) | 中文
Superseded: 本文描述的双名册 join 已随 activity seam 一并移除——job 行现在就是完整名册;见 [jobs 吸收 record](../architecture/2026-09-01-jobs-absorb-activity-record.zh.md)。
## 问题
[activity 观察面](2026-08-24-activity-observation-seam.zh.md)的 Web 呈现落地为[后台任务列表](2026-08-08-web-background-job-display.zh.md)旁边的第二个会话头部按钮。一次后台 bash 会同时登记在两个面里——`ctx.jobs` 负责控制、`ctx.activities` 负责观察——于是同一份工作渲染了两次:一次在"后台任务"里带生命周期与时长,一次在"活动"里带可展开的实时输出。两个行集合高度重叠、名字却不同的近似弹层,读起来像产品事故;而活动列表的扁平行(live 与 settled 混排、单行、无时长)也缺乏视觉层次。
## 决策
服务面保持正交;合并只在 UI 投影层逐行发生一次。
- **`ctx.jobs` 与 `ctx.activities` 仍是两个服务。** jobs 是控制面:模型可见的 id、模型消耗式的 `readOutput` 游标、`stopping`、`kill` 与 `reported` 完成通知契约。activity 是瞬态观察面:非消耗式绝对偏移、模型不可见、重启即无。合并服务会迫使一个注册表同时携带消耗式与非消耗式两种游标,并模糊这个刻意不进 session log 的面与"模型可见 ⟺ 已记录"不变量的边界。
- **`dsh-client-ui-activity` 渲染唯一的合并列表;`dsh-client-ui-jobs` 删除**(预发布阶段,不留兼容层),合并入口占用原 job 列表的 slot 顺位。每个 `jobsBySession` 行与携带其 `correlation.jobId` 的 activity 逐行 join:job 提供身份、生命周期(`stopping` 只存在于此)、时长与模型可见 `detail`;activity 提供可展开的输出面板。没有 job 的 activity(workflow 运行)保留自己的行;job 不在投影里的 activity 也不会被丢弃。没有 activity 注册表时 job 行照常渲染——只是没有面板,合并控件退化为与旧 job 列表完全等价。
- **用户可见词汇统一为"任务"(task)**——`N 个任务进行中` / `N tasks running`——因为对用户而言每一行都是在跑的工作,而"后台任务"排除了 workflow 运行、"活动"命名的是内部面而非用户概念。词典命名空间与包名保持 `activity`:内部名遵循命名台账,不跟随展示文案。
- **层次**:进行中的行在前(命令为主行、kind 徽章加状态为副行、时长每秒跳动),已结束的行折为降权单行;每个非空分组带自己的标题(进行中 / 已结束)。没有可观察 activity 的行渲染为无展开交互的静态行。
刻意不合并的:subagent 委托行保持裸 job 行(subagent 面板是它的呈现面),前台命令留在工具卡片里——两者都不接活动生产者。
## 曾考虑的替代方案
- **合并服务层**——因上述游标语义与模型可见性理由否决。
- **保留 job 列表、把输出挂上去**——workflow 运行(无 job 的 activity)无处安放,第二个列表终究得活下来。
## 浏览器验收带来的细化
在真实应用里驱动合并列表得出四个呈现决策,全部限定在展开面板与头部条带:
- **头部条带顺序为 preset → 任务 → subagent 目录。** 根会话的后代数量从面包屑(`header.lineage`)移入操作区(order 30)并去掉 `/` 分隔符——根会话没有可面包屑的层级;子会话保留 `根 / 子` 切换器,那里的 `/` 是真实层级。移位后的席位由 `SubagentCatalogAction` 拥有;谱系 renderer 在根会话上不再渲染任何东西。
- **面板的复制控件复制命令**(`TerminalBlock.copyText`)而非输出:长命令在行内被省略号截断,控件是它唯一的完整来源,而输出本身就是下方可选中的文本。提供 `copyText` 还让控件在任何输出出现之前就保持渲染。
- **面板以滚动取代折叠**:命令与输出行完整换行(`--dsl-terminal-command-whitespace` / `--dsl-terminal-line-whitespace: pre-wrap`,输出区因此永不横向滚动),输出区以固定高度封顶(`--dsl-terminal-output-max-height`),`maxLines: Infinity` 禁用头尾折叠。
- **面板不画运行状态点**(`TerminalBlock.runStateDot: false`)——上方的行已携带同一状态——并通过 `--dsl-terminal-gutter` 收回状态点落区。面板左侧竖线随之移除。
四项全部以 `TerminalBlock` 的可选项落地(prop 或 `--dsl-terminal-*` 变量),工具卡片保持既有的折叠/省略/状态点行为不变。
## 测试
组件套件覆盖 join(job 生命周期优先、activity 提供面板、裸 job 行、独立 activity)、分段、时长与面板选项;`pnpm vitest run packages/client/ui-activity packages/client/ui-primitives packages/client/ui-subagent` 运行它们。无密钥 web e2e 对(`background-job-list`、`live-activity-stream`)按 ARIA golden 端到端回放合并列表;`agent-preset-selection` 钉住头部条带顺序。
## 后果
- 两个 web e2e 场景(`background-job-list`、`live-activity-stream`)现在断言同一个"任务"列表;job 场景的行获得了实时输出面板,这正是 join 端到端生效的证据。
- live 行上的人工 kill 控件仍被 job-display note 记录的 jobs `reported` 契约问题阻塞。
- `job` 词典命名空间随包一起消失;`activity` 拥有合并后的全部文案,包括时长词汇。
+15
View File
@@ -160,6 +160,9 @@
"architecture/2026-08-01-packaged-ripgrep-search.i18n.yaml": "sha256:43c9128079955e974fe1da3ce1a10779b4f5a25ac3629f92e3b1de847ad45050",
"architecture/2026-08-01-packaged-ripgrep-search.md": "sha256:36cf2d6daa654abced690965338aec4628069dc77be3a1da6368a9b097a59ff4",
"architecture/2026-08-01-packaged-ripgrep-search.zh.md": "sha256:45b2fe8e0d492eeff313d2ef3c98fd69761b3257eee3476b8bf53312e8b6095f",
"architecture/2026-08-03-per-session-agent-presets.i18n.yaml": "sha256:3fe057a67e41f9ce4f5cc8b162e264494b39169588fe9c9e48dcf95fd1b208a1",
"architecture/2026-08-03-per-session-agent-presets.md": "sha256:d4c6c6af11f41b57e95ca359e2cbe09780cb0851037998824a5966cab5e12626",
"architecture/2026-08-03-per-session-agent-presets.zh.md": "sha256:c91248b2d9cc5c51c77e9d195aca5d51a06684971b1afafc60b2def72248afca",
"architecture/2026-08-03-pi-ai-declared-provider-catalog.i18n.yaml": "sha256:c1a59c3c04afada633740c808b98e765ccad09220c4c79d8f4207313c9e9aab6",
"architecture/2026-08-03-pi-ai-declared-provider-catalog.md": "sha256:fae0c59ece887f7d9344b6254d20cc671894074d53eefffb85dbf45b9104d2b6",
"architecture/2026-08-03-pi-ai-declared-provider-catalog.zh.md": "sha256:8032cefedd51b292d810051ff32e8825958e6d90b2e523a374294ef2f859ca1e",
@@ -751,6 +754,12 @@
"bug-fix/2026-09-09-content-sized-diagram-previews.i18n.yaml": "sha256:a4404fe8e35ca5b6371f9cdb58fea6724cf3e7737bb2db8a3a321d534454b17c",
"bug-fix/2026-09-09-content-sized-diagram-previews.md": "sha256:5239e98e5c057d6f7e10c3f10a0f67273778d99b0c4f10b31a1921c3c6474b80",
"bug-fix/2026-09-09-content-sized-diagram-previews.zh.md": "sha256:c1ee3790a9d5c3561e1d93d5e649afa904c7d19b9d77248c85ef16daccd953e2",
"bug-fix/2026-09-16-movable-mandatory-update-window.i18n.yaml": "sha256:e526ce14ae9557e24364e3686778b130090445ef2152d976b7cc93e76eac6401",
"bug-fix/2026-09-16-movable-mandatory-update-window.md": "sha256:67546cea0b3de2b3e32957a508dbde097d91dd919fe067c8dbecb2c65b50c366",
"bug-fix/2026-09-16-movable-mandatory-update-window.zh.md": "sha256:20a0aade6ee71c89e84d70e23c89bb51e8a38c674d103dd43022feabb8d27be3",
"bug-fix/2026-09-11-session-controller-fork-turn-cut.i18n.yaml": "sha256:4442529ced1c8e48d30f7b50c65ff03eb92dce9815beb438cedbf29f234bce47",
"bug-fix/2026-09-11-session-controller-fork-turn-cut.md": "sha256:a996f24a896c7fc5d81e015db3eae27d8a46c853287572e815623ce35eae1b12",
"bug-fix/2026-09-11-session-controller-fork-turn-cut.zh.md": "sha256:6486f0eacf5067ef9850dc32f1c27b2109a7d4e9e3c498366f5c38742bb53ac5",
"feature/2026-06-14-acp-agent-client-protocol.i18n.yaml": "sha256:006795baa43ae962a8d125cc0f1e9f134bc2ee9fb758b6e7669e3fa0126e1918",
"feature/2026-06-14-acp-agent-client-protocol.md": "sha256:6828c0af74bb3fb96206ca6b21c0e56a000b50e4744aad4bc2c05092f3a5a31b",
"feature/2026-06-14-acp-agent-client-protocol.zh.md": "sha256:ba104e841a1fb84edbd3b6c8119d50445b7785255a7a8d13bb9ac8a2cb4d2e69",
@@ -1372,6 +1381,9 @@
"feature/2026-08-25-loaded-turn-chat-navigation.i18n.yaml": "sha256:86bd02d56cf95ac465d0f6e326ccf75432ffb9c3db47373ae6a795195cff9ea7",
"feature/2026-08-25-loaded-turn-chat-navigation.md": "sha256:0ee1cb6da76b93e467d747aa8ba07697ba648cb213ba52202c51ee1e85dff1a8",
"feature/2026-08-25-loaded-turn-chat-navigation.zh.md": "sha256:5d3e567027fc65241856bf9f9cf0d73a20d53214c49bfbc884ab2ddd19503918",
"feature/2026-08-25-unified-task-list.i18n.yaml": "sha256:fec0d4a3ecf59aafd440bc801c686b14d63152c937f51bebe0da61ff615f6f33",
"feature/2026-08-25-unified-task-list.md": "sha256:30bd4e338a3de2f59af98d512c7878928c61846e6700d6012fc45e51c0bf94df",
"feature/2026-08-25-unified-task-list.zh.md": "sha256:88fce0a52b5387ea98c1f68742c03b2f475c5465c680befb32c587066e52cfce",
"feature/2026-08-26-web-trigger-menu-presentation-polish.i18n.yaml": "sha256:69179f3056c6d5fc35119e83dd7a5af30213502dac3c1f2d4fd43eae96ee2ecf",
"feature/2026-08-26-web-trigger-menu-presentation-polish.md": "sha256:c54e3bbe528d6be711541c61e777e6e4ed4fbc1374df97d846b015d3693679f4",
"feature/2026-08-26-web-trigger-menu-presentation-polish.zh.md": "sha256:7db49018eebbba539838d734a80fbb330e2b73fea3b343900591a3605c6c03b5",
@@ -1771,6 +1783,9 @@
"simplification/2026-08-06-user-bubbles-drop-the-branch-action.i18n.yaml": "sha256:e000606d801984d4ee473779891ffe8c197f64f54b75efd82fb46dc84217cd59",
"simplification/2026-08-06-user-bubbles-drop-the-branch-action.md": "sha256:b93ce0d79ee72306d569d80fd81308a2b5704d95df8854b7cbd2f3d898b3f776",
"simplification/2026-08-06-user-bubbles-drop-the-branch-action.zh.md": "sha256:999e3e1252f771c420506a80b7c0d512b70da6770e3e62bb92169180b4cee3e8",
"simplification/2026-08-08-copy-only-preset-authoring.i18n.yaml": "sha256:43bba45597b198a94e3cbf8df31794239c521ecf002338636dca2c099acd94e1",
"simplification/2026-08-08-copy-only-preset-authoring.md": "sha256:be77d385a3c4db534fbbd5b4a830fcb7f6bcd8401221617859569e2c4d837069",
"simplification/2026-08-08-copy-only-preset-authoring.zh.md": "sha256:4c544a08e741c4a273a89dc2a3fda44dffb46af15d0feb8f7bf8efa816bfbd13",
"simplification/2026-08-08-remove-cli-demo.i18n.yaml": "sha256:1baa8649e186e159afb03f112059f46f7a35c303a6039374db8bc4ca743d4bdf",
"simplification/2026-08-08-remove-cli-demo.md": "sha256:a34ce79b674c67a6a47d99c1fa7141b2ae58e36b99da5989d323d229d1921baa",
"simplification/2026-08-08-remove-cli-demo.zh.md": "sha256:96a2a7cc34c7b0abf71b77d666a6349d681fd8a0d9a9c81e30d2aa538d62797a",
@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/archived/simplification/2026-08-08-copy-only-preset-authoring.md
2026-08-08-copy-only-preset-authoring.md: 07f8a6553cf9c70d5289a074b6e6d0d677dd4a28
2026-08-08-copy-only-preset-authoring.zh.md: 58757a9774354b116d235f890beb136034f29904
@@ -1,6 +1,7 @@
# Agent Note: Copy-only preset authoring, and the way into a preset's files
Status: implemented
Archived: 2026-09-20
English | [中文](2026-08-08-copy-only-preset-authoring.zh.md)
@@ -1,6 +1,7 @@
# Agent Note: 仅复制的 preset 创作,与通往 preset 文件的入口
Status: implemented
Archived: 2026-09-20
[English](2026-08-08-copy-only-preset-authoring.md) | 中文
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.md
2026-06-11-content-block-vocabulary.md: d7d3f6b43a3f65d1421f026e5b6c2cc1ba1eadd2
2026-06-11-content-block-vocabulary.zh.md: ed4f915dff6dcb6dbc91400f9bfa5384253aea7b
2026-06-11-content-block-vocabulary.md: 917dd5225fdc4ab391e9e4629769abbb68095731
2026-06-11-content-block-vocabulary.zh.md: 9d228eb34dafbdebab44b45dc3b290c9327d3b2b
@@ -10,7 +10,7 @@ The harness needs one internal language for messages that the loop, session log,
## Decision
Own the vocabulary: messages are arrays of typed content blocks (`text`, `reasoning`, `tool-call`, `tool-result`), with the union derived from the merge-extensible `ContentBlockMap` so plugins add block types via declaration merging. The same merge-extensible-map pattern types every "stringly" field (`MessageSource`, `FinishReason`, `TurnTrigger`, `TurnEndReason`). Streaming is a raw chunk protocol; `BlockAssembler` is the single shared assembly implementation. Adapters translate to provider wire formats — mapping cost lives in adapters, where it belongs.
Own the vocabulary: user, assistant, and system messages are arrays of typed content blocks (`text`, `reasoning`, `tool-call`), with the union derived from the merge-extensible `ContentBlockMap` so plugins add block types via declaration merging. Tool results are first-class tool-role messages whose own content holds result blocks; released Session format v3 retains the historical `tool-result` content block inside a user-role wrapper, and the adjacent V3-to-V4 edge lifts that wrapper into the first-class role. The same merge-extensible-map pattern types every "stringly" field (`MessageSource`, `FinishReason`, `TurnTrigger`, `TurnEndReason`). Streaming is a raw chunk protocol; `BlockAssembler` is the single shared assembly implementation. Adapters translate to provider wire formats — mapping cost lives in adapters, where it belongs.
In-session context injection (`context/message`) and mid-turn steering originally rendered as tagged user-role envelopes (the system-reminder pattern) rather than a new role, so adapters carry zero burden. Both now project as plain user content with no wrapper; see [the injected-content-envelope Agent Note](../simplification/2026-07-20-unwrap-injected-content-envelopes.md). Live-adapter validation confirms this rendering for current DeepSeek behavior; a future provider-specific mismatch belongs in that adapter rather than a new canonical role.
@@ -10,7 +10,7 @@ harness 需要一套统一的内部消息语言,供 agent loop(智能体循
## 决策
自主拥有词汇:消息是类型化内容块的数组(`text`、`reasoning`、`tool-call`、`tool-result`),其联合类型派生自可合并扩展的 `ContentBlockMap`,插件通过声明合并添加新的块类型。同一可合并扩展映射模式为所有「字符串化」字段提供类型(`MessageSource`、`FinishReason`、`TurnTrigger`、`TurnEndReason`)。流式输出采用原始分片协议;`BlockAssembler` 是唯一的共享组装实现。适配器负责转换为提供方的协议格式(wire format)——映射成本留在适配器中,正是它该在的地方。
自主拥有词汇:user、assistant 与 system 消息是类型化内容块的数组(`text`、`reasoning`、`tool-call`),其联合类型派生自可合并扩展的 `ContentBlockMap`,插件通过声明合并添加新的块类型。Tool result 是一等 tool-role message,结果块属于该 message 自己的 content;已发布的 Session format v3 仍将历史 `tool-result` content block 保存在 user-role 包装中,邻接的 V3 到 V4 边会将该包装提升为一等角色。同一可合并扩展映射模式为所有「字符串化」字段提供类型(`MessageSource`、`FinishReason`、`TurnTrigger`、`TurnEndReason`)。流式输出采用原始分片协议;`BlockAssembler` 是唯一的共享组装实现。适配器负责转换为提供方的协议格式(wire format)——映射成本留在适配器中,正是它该在的地方。
会话内上下文注入(`context/message`)和轮次中途 steering(中途引导)最初渲染为带标签的 user-role 信封(system-reminder 模式),而非引入新角色,因此适配器无需承担额外负担。如今两者都投影为无包装的普通用户内容;见[注入内容信封 Agent Note](../simplification/2026-07-20-unwrap-injected-content-envelopes.zh.md)。实际适配器验证已确认此渲染方式符合当前 DeepSeek 的行为;如果未来某提供方出现不兼容,应在该适配器内处理,而非引入新的规范角色。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md
2026-06-20-generic-long-running-tool-runtime.md: c386d3b338c94e0e5bbf8e99c2c7a30971ba92b7
2026-06-20-generic-long-running-tool-runtime.zh.md: 002514740a15c5961960d20976fc18f7fd8071ff
2026-06-20-generic-long-running-tool-runtime.md: 5960eb268656d7e316c763631451db1c6d1c6e49
2026-06-20-generic-long-running-tool-runtime.zh.md: ea63ae82962a735b3a96e1803708172a33f83e9d
@@ -27,7 +27,7 @@ The literal types live on the [tasks subsystem page](../../../../docs/subsystems
The process-local provider also owns bounded admission, whose rationale is recorded in the [bounded background job admission decision](../../archived/bug-fix/2026-08-11-bounded-background-job-admission.md). Its positive-safe-integer `maxConcurrentJobsPerOwner` config defaults to `10`; `start()` derives each exact `Agent` object's active count from `running` and `stopping` records, while every unowned task shares one service bucket. Capacity rejection occurs before `run()` and id allocation, and producer `done` settlement is the only event that releases a stopping task's place. The provider does not queue, preempt, or retain a second mutable count.
`outputLimitBytes` is producer-owned presentation policy, not a registry buffer. The registry validates and projects it unchanged into `JobSnapshot`; generic control APIs apply the cap to complete model-facing output after adding their own status or notice metadata. Omitting it preserves the existing controller behavior, so the runtime does not impose a hidden default on unrelated producer families.
`outputLimitBytes` is producer-owned presentation policy, not a registry buffer. The registry validates and projects it unchanged into `JobView`; generic control APIs apply the cap to complete model-facing output after adding their own status or notice metadata. Omitting it preserves the existing controller behavior, so the runtime does not impose a hidden default on unrelated producer families.
A model-facing producer exposes that committed id in its canonical success value, normally `{ kind: 'background', jobId }`; Native rendering may keep human-readable prose. A pre-aborted background call fails rather than returning a no-op because no task exists to satisfy the promised handle. Once registration publishes the id, cancellation belongs to the task's own controller and the job runtime: later cancellation of the producing tool call must not kill the published task. `job_kill`, owner disposal, and service teardown request cancellation; foreground execution remains coupled to the call's `exec.signal`.
@@ -62,7 +62,7 @@ For contract-compliant producers, `AgentHandle.dispose()` resolves only after ow
- `read(id, caller?)` for a consuming stream delta or an idempotent final result.
- `kill(id, caller?, reason?)` for cancellation.
- `wait(id, timeoutMs, caller?, signal?)` for bounded terminal waiting.
- `onJobDone(listener)` for effect-scoped observation with exact-owner delivery and listener containment.
- `events.subscribe(filter, listener)` for effect-scoped observation of registration, progress, output, settlement, and removal, filtered by owner or owner scope, with listener containment.
- `attachController(name)` for the task-controller availability fence.
`wait` returns the terminal snapshot when the task settles or the live snapshot when its timeout expires. Aborting a wait cancels only that wait. If settlement has already assigned terminal delivery to the waiter, the terminal snapshot still wins. Waiters unregister synchronously on abort so a same-tick settlement cannot suppress a completion notice on behalf of a reader that receives nothing.
@@ -81,7 +81,7 @@ Stream reads share one task-scoped consuming cursor because the owning model is
The system prompt tells the model to retain job ids, continue independent work instead of busy-polling or duplicating a running task, collect relevant tasks before its final answer, and kill work that no longer matters. Completion delivers a logged message to the exact owner's session. A busy owner is injected; an idle owner is woken, under the bounded policy the [idle-owner wake decision](../feature/2026-08-11-background-job-completion-wakes-an-idle-owner.md) owns.
The runtime marks a terminal task `reported` when a read or wait delivers it, when a live waiter has claimed delivery at settlement, or when the model explicitly kills it. Reported tasks do not inject redundant completion notices. Listener failures are logged independently, do not stop later listeners, and are not awaited by waiters or teardown. When a snapshot carries `outputLimitBytes`, `dsh-tool-jobs` preserves UTF-8 boundaries and reuses an existing producer truncation marker rather than duplicating it. Reads reserve status suffixes and retain the output tail; completion notices reserve the stable `background job <id>` prefix and `job_output` instruction before truncating variable kind, label, status, detail, or the truncation marker itself, so the minimum PTY cap still identifies the task to collect. The job controller resolves the caller-visible producer cap in a prepended pre-execute listener before policy can deny or short-circuit dispatch, then applies it through the task definitions' last-mile `finalizeContent` callback so normalized tool errors, outer pipeline failures, and single-text policy results cannot escape the bound; deliberately structured multi-block policy results retain policy ownership of their shape and size.
`dsh-tool-jobs` keeps the delivery ledger: a wait claims the task when it starts (and withdraws if the task outlives the wait) and a `job_kill` claims it when accepted, so a settlement the model already received through a tool result injects no redundant completion notice; teardown settlements and unowned tasks are never announced ([seam consolidation](2026-09-03-jobs-seam-consolidation.md)). Listener failures are logged independently, do not stop later listeners, and are not awaited by waiters or teardown. When a view carries `outputLimitBytes`, `dsh-tool-jobs` preserves UTF-8 boundaries and reuses an existing producer truncation marker rather than duplicating it. Reads reserve status suffixes and retain the output tail; completion notices reserve the stable `background job <id>` prefix and `job_output` instruction before truncating variable kind, label, status, detail, or the truncation marker itself, so the minimum PTY cap still identifies the task to collect. The job controller resolves the caller-visible producer cap in a prepended pre-execute listener before policy can deny or short-circuit dispatch, then applies it through the task definitions' last-mile `finalizeContent` callback so normalized tool errors, outer pipeline failures, and single-text policy results cannot escape the bound; deliberately structured multi-block policy results retain policy ownership of their shape and size.
## Producer opt-in
@@ -105,7 +105,7 @@ Separate bash and subagent output/stop tools duplicate ids, isolation, cleanup,
### An immediate abstract task-runtime backend
The current `JobStart.run()` contract passes in-process callbacks and exact `Agent` objects. A durable backend changes identity, restart, ownership, and observation semantics, so at introduction time the registry stayed one concrete service rather than freezing the wrong boundary. The [task-registry contract Agent Note](../../archived/architecture/2026-07-26-job-registry-seam.md) later separated the contract from the process-local implementation without changing these in-process semantics.
The current `JobSpec.run()` contract returns in-process hooks and pushes output through an in-process `JobHandle`, and owners resolve to live `Agent`s in-process. A durable backend changes identity, restart, ownership, and observation semantics, so at introduction time the registry stayed one concrete service rather than freezing the wrong boundary. The [task-registry contract Agent Note](../../archived/architecture/2026-07-26-job-registry-seam.md) later separated the contract from the process-local implementation without changing these in-process semantics.
### Consumer-owned authorization or cleanup events
@@ -27,7 +27,7 @@ Status: implemented
进程内 Service Provider 还拥有有界准入,其理由记录在[有界后台任务准入决策](../../archived/bug-fix/2026-08-11-bounded-background-job-admission.md)中。它的 `maxConcurrentJobsPerOwner` 配置必须是正的安全整数,默认值为 `10`;`start()` 从 `running` 与 `stopping` 记录派生每个确切 `Agent` 对象的活动数量,而全部无 owner 任务共享一个服务级桶。容量拒绝发生在 `run()` 与 id 分配之前,处于 stopping 的任务只有在生产方 `done` 结算时才释放名额。Service Provider 不排队或抢占任务,也不保留第二份可变计数。
`outputLimitBytes` 是生产方拥有的呈现策略,而非注册表缓冲区。注册表校验该值,并将其原样投影到 `JobSnapshot`;通用任务控制器添加自身的状态或通知元数据后,再将该上限应用于完整的面向模型输出。省略该值时保持现有控制器行为,因此运行时不会向无关的生产方类别施加隐式默认值。
`outputLimitBytes` 是生产方拥有的呈现策略,而非注册表缓冲区。注册表校验该值,并将其原样投影到 `JobView`;通用任务控制器添加自身的状态或通知元数据后,再将该上限应用于完整的面向模型输出。省略该值时保持现有控制器行为,因此运行时不会向无关的生产方类别施加隐式默认值。
面向模型的生产方会在规范成功值中暴露已提交的 id,通常为 `{ kind: 'background', jobId }`;Native 渲染仍可保留便于人类阅读的行文。预先被中止的后台调用会失败,而不是返回空操作,因为不存在可履行所承诺句柄的任务。一旦注册过程发布 id,取消就归任务自身的控制器与任务运行时所有:随后取消生产工具调用不得终止已发布的任务。`job_kill`、所有者资源释放和服务拆除会请求取消;前台执行仍与调用的 `exec.signal` 耦合。
@@ -62,7 +62,7 @@ job id 在运行时全局可见且可预测,因此注册表会授权每次访
- `read(id, caller?)`:消费式流增量或幂等的最终结果。
- `kill(id, caller?, reason?)`:取消。
- `wait(id, timeoutMs, caller?, signal?)`:有界的终止等待。
- `onJobDone(listener)`:effect 作用域内的观察,具有精确所有者投递和监听器隔离。
- `events.subscribe(filter, listener)`:effect 作用域内对注册、进度、输出、结算与移除的观察,按所有者或所有者 scope 过滤,具有监听器隔离。
- `attachController(name)`:任务控制器可用性防线。
`wait` 在任务完成时返回终止快照,在等待超时时返回当前快照。中止一次等待只取消该次等待。如果结算已经将终止投递分配给该等待方,终止快照仍然优先。等待方在中止时同步注销,因此同一 tick 内发生结算时,不会代表一个实际未收到任何内容的读取方压制完成通知。
@@ -81,7 +81,7 @@ job id 在运行时全局可见且可预测,因此注册表会授权每次访
系统提示词要求模型保留 job id、在后台工作运行时继续处理独立工作而非忙轮询或重复启动同一任务、在给出最终答案前收集相关任务,并终止不再重要的工作。完成时,系统会向确切所有者的会话交付一条已记录的消息:繁忙的所有者走注入,空闲的所有者会被唤醒,其有界策略由[空闲所有者唤醒决策](../feature/2026-08-11-background-job-completion-wakes-an-idle-owner.zh.md)负责。
当读取或等待交付终止任务、尚在等待的等待方在结算时认领了投递,或模型显式终止任务时,运行时将终止任务标为 `reported`。已报告的任务不会注入冗余的完成通知。监听器失败会独立记录,不会阻止后续监听器,也不会被等待方或资源销毁过程等待。当快照携带 `outputLimitBytes` 时,`dsh-tool-jobs` 会保持 UTF-8 边界,并复用生产方已有的截断标记,而不会重复添加。读取会为状态后缀预留空间并保留输出尾部;完成通知会先为稳定的 `background job <id>` 前缀与 `job_output` 指令预留空间,再截断可变的 kind、label、status、detail,乃至截断标记本身,因此 PTY 的最小上限仍能标识需要收集的任务。任务控制器在策略有机会拒绝或短路分发之前,于最先执行的 pre-execute 监听器中解析调用方可见的生产方上限;随后通过任务定义最后一道的 `finalizeContent` 回调应用该上限,使规范化的工具错误、外层流水线失败与单文本策略结果都无法绕过该边界;经特意结构化的多块策略结果仍由策略拥有其形状与大小。
`dsh-tool-jobs` 持有投递台账:等待在开始时认领任务(任务比等待活得更久则撤回),`job_kill` 被接受时也认领,因此模型已经通过工具结果收到的结算不会再注入冗余的完成通知;teardown 结算与无主任务从不播报([seam 收敛](2026-09-03-jobs-seam-consolidation.zh.md))。监听器失败会独立记录,不会阻止后续监听器,也不会被等待方或资源销毁过程等待。当视图携带 `outputLimitBytes` 时,`dsh-tool-jobs` 会保持 UTF-8 边界,并复用生产方已有的截断标记,而不会重复添加。读取会为状态后缀预留空间并保留输出尾部;完成通知会先为稳定的 `background job <id>` 前缀与 `job_output` 指令预留空间,再截断可变的 kind、label、status、detail,乃至截断标记本身,因此 PTY 的最小上限仍能标识需要收集的任务。任务控制器在策略有机会拒绝或短路分发之前,于最先执行的 pre-execute 监听器中解析调用方可见的生产方上限;随后通过任务定义最后一道的 `finalizeContent` 回调应用该上限,使规范化的工具错误、外层流水线失败与单文本策略结果都无法绕过该边界;经特意结构化的多块策略结果仍由策略拥有其形状与大小。
## 生产方显式启用
@@ -105,7 +105,7 @@ bash seam 暴露 `resolve`、`run` 和 `start`。`start(spec)` 返回一个 `She
### 立即抽象任务运行时后端
当前 `JobStart.run()` 约定传入进程内回调与确切的 `Agent` 对象。持久化后端会改变身份、重启、所有权与观察语义,因此在引入之时注册表保持为单一具体服务,而非固化错误的边界。[任务注册表约定 Agent Note](../../archived/architecture/2026-07-26-job-registry-seam.md)后来在不改变这些进程内语义的前提下,将约定与进程内实现分离。
当前 `JobSpec.run()` 约定返回进程内钩子并经进程内的 `JobHandle` 推送输出,所有者也在进程内解析为存活的 `Agent`。持久化后端会改变身份、重启、所有权与观察语义,因此在引入之时注册表保持为单一具体服务,而非固化错误的边界。[任务注册表约定 Agent Note](../../archived/architecture/2026-07-26-job-registry-seam.md)后来在不改变这些进程内语义的前提下,将约定与进程内实现分离。
### 由消费方负责授权或清理事件
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md
2026-07-08-tool-output-spill-files.md: fa6697aad63c97b0ef580fe1ca46e50541e3d682
2026-07-08-tool-output-spill-files.zh.md: a995eafc7878b342a2164c5116da1f43ada791a5
2026-07-08-tool-output-spill-files.md: e32aec72f423dff33a29d9012c0f71c9cfbd24fb
2026-07-08-tool-output-spill-files.zh.md: 2941fc516723e72b5d5670e674ce612f3cf581cc
@@ -66,36 +66,7 @@ interface SpillRef {
### Spill policy
`dsh-spill-policy` is a `tools/post-execute` result transformer with one configuration knob:
```ts ignore-check
interface Config {
/** Omitted means no automatic spill policy. Present means apply to oversized plain text tool results. */
maxInlineBytes?: number
}
```
When `maxInlineBytes` is omitted the plugin registers nothing (a true no-op). When set, it applies a default policy to final plain-text tool results:
1. Let the tool run normally, delegating via `next()` so a downstream listener settles the result first.
2. Flatten the accepted final `ContentBlock[]` only when it is entirely plain text; a result with any non-text block is left untouched.
3. If its UTF-8 byte size is at or below `maxInlineBytes`, leave it unchanged.
4. If it is larger, call `ctx.spillStore.saveText()` with the full final text.
5. Replace the model-facing result with a retained head/tail preview plus the spill reference.
The preview is an implementation default owned by the policy: a head/tail split of `maxInlineBytes` via the retention library's `TextRetainer`. Future config can expose preview sizing only after a second deployment needs it.
The replacement text is intentionally generic because the policy only knows the final formatted tool result, not the tool's internal resource:
```text
<retained preview>
(Omitted N bytes. Full formatted result stored at: /.../session-.../....txt. Use read with offset/limit, or grep this path to search within it.)
```
If `ctx.spillStore.saveText()` fails (permissions, ENOSPC, backend unavailable), or the call has no session owner, or no backend is loaded, the plugin logs the reason and returns the original result unchanged. Spill failure never turns a successful tool call into an `isError` result or hides the inline result.
The policy skips `read` to avoid a circular `read -> spill file -> read again` loop. Additional opt-out configuration is deferred until a real second tool needs it.
`dsh-spill-policy` retains ordered text/image ends under `maxInlineTokens` after post-execute policy accepts the result. `saveText()` stores the complete result with readable image paths; image bytes remain in attachment storage. The [multimodal retention decision](../../implemented/bug-fix/2026-09-21-multimodal-tool-result-retention.md) owns image projection order, atomic image omission, and route pricing. An omitted budget installs no listeners, recovery failures keep the original result, and model-facing `read` results skip retention to avoid a read/spill loop.
## Showcase: web_fetch
@@ -129,7 +100,7 @@ With `dsh-spill-policy` configured, a large formatted fetch result is automatica
- id: spill-policy
name: '@deepseek-ai/dsh-spill-policy'
config:
maxInlineBytes: 50000
maxInlineTokens: 12500
```
This separation is important. `web-fetch-http` still owns resource caps (`maxResponseBytes`, `maxBodyChars`) to protect network, memory, and decoding work. `spill-policy` owns only the model-facing context cap after the result already exists. If the provider already returned `truncated: true`, the spill file contains the full formatted result the tool returned, not the full original webpage; the policy does not claim otherwise.
@@ -186,11 +157,11 @@ The local-backend value proposition depends on the existing `read`/`grep` tools
**Snapshot gap.** No ACP snapshot scenario covers the transcript-visible `web_fetch` spill notice yet. The ACP snapshot harness replays keyless and cannot hit the live web, and a `web_fetch` spill requires a real over-cap HTTP body; a deterministic scenario would need a seeded loopback fetch target the replay tree does not currently wire (the examples do not load `tool-web` at all). The behavior is covered instead by the `dsh-tool-web` integration test against a loopback server. Closing the gap is follow-up work: wire `tool-web` + a seeded fetch target into the ACP example, then record a `web-fetch-spill` scenario.
The policy can become too large if it starts owning tool-specific semantics. It stays narrow: plain-text final results only. Tool-owned early spill remains future work.
The policy can become too large if it starts owning tool-specific semantics. It handles accepted text/image sequences without interpreting tool-specific text. Tool-owned early spill remains future work.
## Alternatives considered
**Require each tool to opt in with a retention declaration.** Rejected: the goal is a default behavior similar to Claude Code's generic tool-result persistence. A single `maxInlineBytes` deployment knob is enough to prove the shape.
**Require each tool to opt in with a retention declaration.** Rejected: the goal is a default behavior similar to Claude Code's generic tool-result persistence. A single `maxInlineTokens` deployment knob is enough to prove the shape.
**Make `tool-results` a broad tool-result platform.** Rejected: a broad package name invites retention policy, result replacement, preview wording, search, and early spill into one seam. The shared storage part is smaller: save text and return a locator plus retrieval hint.
@@ -66,36 +66,7 @@ interface SpillRef {
### spill 策略
`dsh-spill-policy` 是一个 `tools/post-execute` 结果转换器,只提供一个配置项:
```ts ignore-check
interface Config {
/** Omitted means no automatic spill policy. Present means apply to oversized plain text tool results. */
maxInlineBytes?: number
}
```
省略 `maxInlineBytes` 时,插件不会注册任何内容,是真正的无操作。设置该值后,它会对最终的纯文本工具结果应用默认策略:
1. 让工具正常运行,通过 `next()` 委托,使下游监听器先结算结果。
2. 仅当已接受的最终 `ContentBlock[]` 全部是纯文本时,才将其展平;含任何非文本块的结果保持不变。
3. 如果 UTF-8 字节大小不超过 `maxInlineBytes`,保持不变。
4. 如果超出上限,使用完整的最终文本调用 `ctx.spillStore.saveText()`。
5. 把模型可见结果替换为保留的首尾预览和 spill 引用。
预览属于策略所有的实现默认值:以 `maxInlineBytes` 为上限,使用保留库的 `TextRetainer` 进行首尾分割。只有第二个部署证明有此需求后,未来配置才会公开预览大小。
替换文本刻意保持通用,因为策略只知道最终格式化的工具结果,不了解工具的内部资源:
```text
<retained preview>
(Omitted N bytes. Full formatted result stored at: /.../session-.../....txt. Use read with offset/limit, or grep this path to search within it.)
```
如果 `ctx.spillStore.saveText()` 失败(权限、ENOSPC、后端不可用),或调用没有会话所有者,或未加载后端,插件会记录原因并原样返回结果。spill 失败绝不会把成功的工具调用变为 `isError` 结果,也不会隐藏内联结果。
策略跳过 `read`,以避免形成 `read -> spill file -> read again` 循环。额外的选择退出配置要等确实出现第二个有此需求的工具后再引入。
`dsh-spill-policy` 在执行后策略接受结果之后,按 `maxInlineTokens` 对文字和图片共同计量并保留首尾。完整结果仍通过 `saveText()` 保存,图片本体留在附件存储,结果文件记录可读取路径。图片投影顺序、整图省略和模型计量由[图文结果保留决策](../../implemented/bug-fix/2026-09-21-multimodal-tool-result-retention.zh.md)负责。省略配置时不安装监听器,存储失败时保留原结果;`read` 的模型可见结果跳过自动省略,避免重复读取产生循环。
## 示例:web_fetch
@@ -129,7 +100,7 @@ ctx.tools.register(defineTool({
- id: spill-policy
name: '@deepseek-ai/dsh-spill-policy'
config:
maxInlineBytes: 50000
maxInlineTokens: 12500
```
这项分离很重要。`web-fetch-http` 仍负责资源上限(`maxResponseBytes`、`maxBodyChars`),用来保护网络、内存和解码工作。`spill-policy` 只负责结果已经存在后针对模型上下文的上限。如果提供方已经返回 `truncated: true`,spill 文件包含的是工具返回的完整格式化结果,而不是原始网页全文;策略不会做出其他承诺。
@@ -186,11 +157,11 @@ ctx.tools.register(defineTool({
**快照缺口。** 目前没有 ACP 快照场景覆盖 transcript(文本记录)可见的 `web_fetch` spill 提示。ACP 快照 harness 在无密钥环境中回放,无法访问实时 web,而 `web_fetch` spill 需要一个真实的超上限 HTTP 正文;确定性场景需要一个预置的 loopback fetch 目标,但当前回放树尚未接线(示例根本没有加载 `tool-web`)。该行为改由 `dsh-tool-web` 针对 loopback server 的集成测试覆盖。弥补该缺口属于后续工作:把 `tool-web` 和预置 fetch 目标接入 ACP 示例,然后录制 `web-fetch-spill` 场景。
如果策略开始负责工具专用语义,就会膨胀得过大。它的范围保持狭窄:只处理纯文本最终结果。由工具负责的提前 spill 仍留作未来工作。
如果策略开始负责工具专用语义,就会膨胀得过大。它处理已接受的图文序列,不解释工具专用的文字语义。由工具负责的提前 spill 仍留作未来工作。
## 考虑过的替代方案
**要求每个工具通过保留声明选择加入。**不予采纳,因为目标是实现类似 Claude Code 通用工具结果持久化的默认行为。只需一个 `maxInlineBytes` 部署配置项即可验证该形态。
**要求每个工具通过保留声明选择加入。**不予采纳,因为目标是实现类似 Claude Code 通用工具结果持久化的默认行为。只需一个 `maxInlineTokens` 部署配置项即可验证该形态。
**把 `tool-results` 建成宽泛的工具结果平台。** 不予采纳:宽泛的包名会诱使系统把保留策略、结果替换、预览措辞、搜索和提前 spill 合并进一个 seam。可共享的存储部分更小:保存文本,并返回定位符与检索提示。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md
2026-07-10-single-file-executable-sdk-runtime-distribution.md: 56c9770966647e97763e0eaf339e4589fba673fb
2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: 04351105a56304020f5696cf1ce82d7a83ed2ced
2026-07-10-single-file-executable-sdk-runtime-distribution.md: 7260d11c84f8fb07cf6c4691357f92e0d00fd8f3
2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: 653fb3d9b782d9010f693e4af8ad94475abb9d94
@@ -23,6 +23,8 @@ The exe is packaged with the **`--sea` (enhanced SEA) mode** of [@yao-pkg/pkg](h
`@yao-pkg/pkg` is an exact-pinned root `devDependency` invoked as `pnpm exec pkg`, with [`patches/@[email protected]`](../../../../patches/@[email protected]) removing the SEA bootstrap's `patchChildProcess` call. Unpatched, pkg rewrites spawned commands named `node` — including the string after a `-c`/`/c` flag, exactly the Bash tool's `bash -c` form — to the executable itself and stamps `PKG_EXECPATH` into every child environment, so a model-issued `node --version` silently boots the dsh CLI; Node's own SEA layer performs no such rewrite, and a SEA binary cannot impersonate plain Node because it always boots its embedded app. With the call removed, children resolve `node` through PATH like any other process (a machine without Node reports command-not-found honestly), no `PKG_EXECPATH` reaches children, absolute `process.execPath` spawns still re-enter the app, worker threads never applied the hook, and `process.pkg` sidecar selection is untouched.
The same patch makes the SEA bootstrap's native-addon extraction atomic. On the first `dlopen` of an addon inside the VFS, pkg's `patchDlopen` copies the addon's package tree to `$PKG_NATIVE_CACHE_PATH/pkg/<sha256 of the .node file>/` (default `~/.cache/pkg/...`), a per-user directory that every concurrent process of the same executable shares; upstream writes each file straight to its final path with `writeFileSync`, which truncates a file another process may already have mapped and lets that process's `existsSync`/hash check read a half-written file. Concurrent first starts therefore die with SIGBUS on Linux or fail with `ERR_DLOPEN_FAILED: Library not loaded` on sharp's libvips on macOS (#4664). The patch routes the three write sites through a same-directory temporary file and `renameSync`, so a cache path is either absent or complete, every process finishes its own copy pass before it loads, and concurrent renames of identical content need no lock. The cache location, hash keying, and per-file comparison are unchanged, so a consumer that isolates the cache per instance through `PKG_NATIVE_CACHE_PATH` keeps working; worker threads only define the hook and never install it. Measured (linux-x64, node24 target, pkg 6.21.0, a probe that loads sharp with its 18 MB libvips library, 16 concurrent first starts against one empty cache, five rounds): 39 of 80 processes died with SIGBUS before the change and 0 of 80 after it, with no temporary files left behind.
Terminology reminder: pkg's `/snapshot` VFS has nothing to do with this repo's testing-system "snapshot" (ACP replay expected outputs, `$DSH_SNAPSHOT`); this document says "VFS" for the former.
### The serving interface is a plugin inside the dsh application
@@ -38,7 +40,7 @@ The Python client supplies an explicit Harness home and selects the `sdk` profil
Inside the exe's VFS sits a **real package tree in build-artifact form** (each package's `lib/` plus a real `node_modules`). The packaged JSON-RPC entry supplies its installed harness base to app-boot's root Include: relative plugin specifiers resolve from the external configuration directory, while bare package names resolve from the VFS, so a configuration inside another Node project cannot shadow the packaged plugin set. The ordinary development bin leaves bare packages configuration-owned. Bare specifiers in the packaged entry resolve upward along `node_modules` from the entry's position inside the VFS and land inside the VFS naturally. The closed set needs no allowlist code — the set is whatever the VFS has installed, and importing a name outside the set fails.
The deploy root is [`python/sdk-runtime/package.json`](../../../../python/sdk-runtime/package.json) (`dsh-python-runtime-closure`, a pnpm workspace member and a zero-code pure dependency manifest) — the unified source of truth for "which plugins the exe ships" and "what the Python runtime distributes". Adding a plugin to the exe = adding one dependency line to the manifest and repackaging. [`scripts/verify-runtime-closure.ts`](../../../../scripts/verify-runtime-closure.ts) reads every shipped `packages/preset/agent-presets/presets/*/agent.cordis.yml`, evaluates `disabled` conditions that compare `process.platform` for every target in `python/sdk-runtime/platforms.json`, and requires each active workspace plugin at the runtime root through an explicit `workspace:` dependency. It also traverses every workspace package covered by that manifest and requires every non-optional workspace peer, reporting the complete preset or referencing-package → missing-dependency chain; unknown platform conditions remain active so a plugin cannot be omitted by an unsupported expression. `pnpm run hygiene`, CI static, and the single-exe build run it before packaging. Deploy also packs by each package's `files`, so the shared chunks tsdown splits out must be covered by `files`.
The deploy root is [`python/sdk-runtime/package.json`](../../../../python/sdk-runtime/package.json) (`dsh-python-runtime-closure`, a pnpm workspace member and a zero-code pure dependency manifest) — the unified source of truth for "which plugins the exe ships" and "what the Python runtime distributes". Adding a plugin to the exe = adding one dependency line to the manifest and repackaging. [`scripts/verify-runtime-closure.ts`](../../../../scripts/verify-runtime-closure.ts) reads every shipped `packages/bundle/web-app/cordis.patch.yml*/agent.cordis.yml`, evaluates `disabled` conditions that compare `process.platform` for every target in `python/sdk-runtime/platforms.json`, and requires each active workspace plugin at the runtime root through an explicit `workspace:` dependency. It also traverses every workspace package covered by that manifest and requires every non-optional workspace peer, reporting the complete preset or referencing-package → missing-dependency chain; unknown platform conditions remain active so a plugin cannot be omitted by an unsupported expression. `pnpm run hygiene`, CI static, and the single-exe build run it before packaging. Deploy also packs by each package's `files`, so the shared chunks tsdown splits out must be covered by `files`.
The deploy root includes `@deepseek-ai/dsh-mcp-client` as an explicitly supported custom-configuration plugin even though no shipped preset mounts it. An external config can therefore connect to user-supplied stdio and Streamable HTTP MCP servers and register their tools; the distribution does not carry those servers. The optional resource service provides MCP Resources; MCP Prompts remain unsupported. The executable and installed-wheel smokes start a temporary stdio server, discover its tool, and complete one model-requested call.
@@ -23,6 +23,8 @@ exe 使用 [@yao-pkg/pkg](https://github.com/yao-pkg/pkg)(vercel/pkg 归档后
`@yao-pkg/pkg` 是精确钉版的根 `devDependency`,经 `pnpm exec pkg` 调用,并以 [`patches/@[email protected]`](../../../../patches/@[email protected]) 移除 SEA bootstrap 中的 `patchChildProcess` 调用。未打补丁时,pkg 会把 spawn 的 `node` 命令——包括 `-c`/`/c` 标志后的命令串,恰是 Bash 工具的 `bash -c` 形态——改写为 exe 自身,并向每个子进程环境注入 `PKG_EXECPATH`,模型下发的 `node --version` 会静默启动 dsh CLI;Node 自身的 SEA 层没有这种改写,且 SEA 二进制永远启动内嵌应用、无法充当纯 Node。移除该调用后,子进程像普通进程一样经 PATH 解析 `node`(无 Node 的机器如实报 command not found),子进程环境不再出现 `PKG_EXECPATH`,以 `process.execPath` 绝对路径 spawn 的重入不受影响,worker 线程本来就未应用该钩子,`process.pkg` 侧车选择也不受影响。
同一补丁还把 SEA bootstrap 的原生插件解包改成原子写入。VFS 内的插件首次 `dlopen` 时,pkg 的 `patchDlopen` 会把该插件所在的包目录整棵拷到 `$PKG_NATIVE_CACHE_PATH/pkg/<.node 文件的 sha256>/`(默认 `~/.cache/pkg/...`),这是同一个 exe 的所有并发进程共用的按用户目录;上游用 `writeFileSync` 直接写目标路径,会把另一个进程可能已经映射的文件截断,并让该进程的 `existsSync`/哈希比对读到写了一半的文件。因此并发首次启动在 Linux 上以 SIGBUS 崩溃,在 macOS 上对 sharp 的 libvips 报 `ERR_DLOPEN_FAILED: Library not loaded`(#4664)。补丁让三处写入都经过同目录临时文件加 `renameSync`,于是缓存路径要么不存在、要么完整,每个进程都在加载前走完自己的拷贝,相同内容的并发 rename 不需要锁。缓存位置、哈希键与逐文件比对均未改变,通过 `PKG_NATIVE_CACHE_PATH` 按实例隔离缓存的消费方照常工作;worker 线程只定义该钩子而从不安装。 实测(linux-x64、node24 构建目标、pkg 6.21.0,一个加载 sharp 及其 18 MB libvips 库的探针,16 个进程对同一个空缓存并发首次启动,跑五轮):改动前 80 个进程中 39 个以 SIGBUS 崩溃,改动后 80 个全部正常,且无残留临时文件。
术语提醒:pkg 的 `/snapshot` VFS 与本仓库测试体系的「快照」(ACP(Agent Client Protocol)回放预期输出、`$DSH_SNAPSHOT`)无关,本文用「VFS」指前者。
### 对外服务接口是 dsh 应用中的插件
@@ -38,7 +40,7 @@ Python 客户端提供显式 Harness home,并选择 `sdk` profile 与有序 pa
exe 的 VFS 内是**构建产物形态的真实包树**(各包的 `lib/` + 真实 `node_modules`)。打包专用 JSON-RPC 入口会向 app-boot 的根 Include 提供自身已安装 harness 的基准位置:相对插件说明符从外部配置目录解析,裸包名则从 VFS 解析,因此位于另一个 Node 项目内的配置无法遮蔽已打包的插件集合。普通开发 bin 仍由配置项目提供裸包。打包入口中的裸包名从该入口在 VFS 内的位置沿 `node_modules` 向上解析,自然落在 VFS 内。封闭集不需要白名单代码——VFS 中安装了什么,集合中就有什么;`import()` 集合外的名称会失败。
部署根目录是 [`python/sdk-runtime/package.json`](../../../../python/sdk-runtime/package.json)(`dsh-python-runtime-closure`,pnpm 工作区成员、零代码纯依赖 manifest),也是「exe 安装哪些插件」与「Python 运行时分发什么」的统一真源。向 exe 添加插件,就是在 manifest 中增加一行依赖后重新打包。[`scripts/verify-runtime-closure.ts`](../../../../scripts/verify-runtime-closure.ts) 读取每个已发布的 `packages/preset/agent-presets/presets/*/agent.cordis.yml`,针对 `python/sdk-runtime/platforms.json` 中的每个目标解析比较 `process.platform` 的 `disabled` 条件,并要求该目标启用的每个工作区插件都通过显式的 `workspace:` 依赖列在运行时根目录。它还遍历该 manifest 覆盖的全部工作区包,要求每个非可选的工作区对等依赖(peer dependency)都显式列出,并报告“preset 或引用包 → 缺失依赖”的完整链路;无法识别的平台条件会保持启用,避免因不支持的表达式遗漏插件。`pnpm run hygiene`、CI 静态检查与 single-exe 构建都会在打包前运行该门禁。部署还会依据各包的 `files` 字段打包,因此 tsdown 拆出的共享分片必须被 `files` 覆盖。
部署根目录是 [`python/sdk-runtime/package.json`](../../../../python/sdk-runtime/package.json)(`dsh-python-runtime-closure`,pnpm 工作区成员、零代码纯依赖 manifest),也是「exe 安装哪些插件」与「Python 运行时分发什么」的统一真源。向 exe 添加插件,就是在 manifest 中增加一行依赖后重新打包。[`scripts/verify-runtime-closure.ts`](../../../../scripts/verify-runtime-closure.ts) 读取每个已发布的 `packages/bundle/web-app/cordis.patch.yml*/agent.cordis.yml`,针对 `python/sdk-runtime/platforms.json` 中的每个目标解析比较 `process.platform` 的 `disabled` 条件,并要求该目标启用的每个工作区插件都通过显式的 `workspace:` 依赖列在运行时根目录。它还遍历该 manifest 覆盖的全部工作区包,要求每个非可选的工作区对等依赖(peer dependency)都显式列出,并报告“preset 或引用包 → 缺失依赖”的完整链路;无法识别的平台条件会保持启用,避免因不支持的表达式遗漏插件。`pnpm run hygiene`、CI 静态检查与 single-exe 构建都会在打包前运行该门禁。部署还会依据各包的 `files` 字段打包,因此 tsdown 拆出的共享分片必须被 `files` 覆盖。
部署根目录显式包含 `@deepseek-ai/dsh-mcp-client`,将其作为自定义配置可用的插件,即使随附 preset 均未挂载该插件。外部配置因此可以连接由用户提供的 stdio 与 Streamable HTTP MCP server 并注册其工具;分发物不包含这些 server。可选资源服务提供 MCP Resources;MCP Prompts 仍不受支持。可执行程序与已安装 wheel 包的冒烟测试会启动临时 stdio server,发现其工具,并完成一次由模型请求的调用。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.md
2026-07-16-explicit-turn-cancellation.md: 09631349226837d7f93112fe5e9c517f2fee099c
2026-07-16-explicit-turn-cancellation.zh.md: baf039d49578895d53ffae4c6b88d29b3d1563b6
2026-07-16-explicit-turn-cancellation.md: d59accf2dc3f894ffab226e5ddc9d2e746f4d93e
2026-07-16-explicit-turn-cancellation.zh.md: 8fb0728eb2bbe8fa01dbefebe7e02aa8c836617a
@@ -12,7 +12,7 @@ The [initiating Agent scope decision](2026-07-15-agent-initiator-scope.md) inten
## Decision
Agent owns the runtime-only `AgentCancelCause` union `{ kind: 'user' } | { kind: 'parent' }`; `agent.cancel()` defaults to `user`. TypeScript enforces that vocabulary at this typed same-process boundary, with no runtime validator, fallback, or special compatibility contract for untyped callers. An active `TurnCancellation` copies the typed discriminant into a fresh frozen signal reason; idle cancellation has no holder to mutate and does not arm later work.
Agent owns the runtime-only `AgentCancelCause` union `{ kind: 'user' } | { kind: 'parent' } | { kind: 'hook'; reason: string } | { kind: 'disposed' }`, and `agent.cancel()` requires one. TypeScript enforces that vocabulary at this typed same-process boundary, with no runtime validator, fallback, or special compatibility contract for untyped callers. The holder aborts with the caller's own object rather than a detached copy, so a transport reaching that signal can extend it: Node's fetch assigns a `stack` onto the reason it receives. The loop therefore copies the declared fields where the cause becomes durable data. Freezing the cause at `cancel()` is not an alternative — undici performs that assignment from strict-mode code, so a frozen reason makes `fetch` reject with `TypeError: Cannot add property stack` in place of the abort reason. Idle cancellation has no holder to mutate and does not arm later work.
An interrupted live turn ends with the coarse durable `{ kind: 'aborted' }` outcome. The terminal event records what happened to the turn, while the runtime signal identifies who requested cancellation; it does not duplicate `user` or `parent` into replay. Session seed/load rejects legacy aborted records with a reason or any other extra field, so replay cannot reintroduce caller-owned cancellation detail. The process-local `agent/cancel-requested` notification is not durable; a future audit requirement uses a separate durable control-request event so a request and its eventual outcome remain distinct. Durable events contain no stack, signal, error object, free-form cancellation text, or backend-private detail.
@@ -22,7 +22,7 @@ The driver keeps only a cause-less pre-run marker for queued work cancelled befo
The explicit event signatures pass a single payload object: agent-scoped events carry `agent` and `signal` in the payload with `next` last, and the remaining APIs keep `signal` immediately before a waterfall's final `next`. `PreStepContext` and `RequestFailureContext` are retired, with their fields folded into the `agent/pre-step` and `agent/request-error` payloads ([payload-object events](../../archived/architecture/2026-08-06-agent-event-payload-objects.md)). Pre-step entry, request configuration, request-error recovery, model generation, tool execution, approval, turn stopping, and subagent or workflow requests all receive the current signal. Hook bridges must also supply `RunHookOptions.signal`, so a turn cancellation reaches the bash executor's provider-managed termination and join boundary; the [native-containment decision](2026-08-28-subprocess-native-containment.md) owns the supported local scope, Job, and fallback mechanics. `SystemPrompt.assemble()` carries `signal?: AbortSignal` in `AssembleContext` because that object is an explicit request value that can also represent signal-less assembly outside a turn. Listeners may cooperate with the signal but must not retain it to control another turn.
`ctx.agents` continues to carry only the initiating Agent. Ambient Agent presence does not imply liveness, a current turn, or cancellation authority. The cause reader is private to the loop and states the machine-private slot invariant (only `cancel()` aborts a turn controller, always with a canonical frozen cause) instead of re-validating the reason structurally; no public helper reads a cause off an arbitrary signal. Concurrent Agents isolate both their initiator identities and their turn signals; a child driver shadows the parent initiator while its parent request signal still travels through the subagent seam.
`ctx.agents` continues to carry only the initiating Agent. Ambient Agent presence does not imply liveness, a current turn, or cancellation authority. The cause reader is private to the loop and states the machine-private slot invariant (only `cancel()` aborts a turn controller, always with a typed cause) instead of re-validating the reason structurally; no public helper reads a cause off an arbitrary signal. Concurrent Agents isolate both their initiator identities and their turn signals; a child driver shadows the parent initiator while its parent request signal still travels through the subagent seam.
Agent disposal requests the runtime-only `{ kind: 'disposed' }` interruption on the active holder. If cancellation already won the controller reason, the reason cannot be rewritten, so terminal classification first checks lifecycle state: disposed wins, then a supported `user` or `parent` cause becomes the coarse aborted outcome, and unrelated exceptions retain the existing error path. ACP cancellation maps to `user`; in-process spawn and fork propagation map to `parent`. Remote ACP subagents retain their existing wire protocol.
@@ -30,7 +30,7 @@ Cancellation remains cooperative. The loop checks interruption before and after
## Verification
Contract tests verify the typed caller union, frozen detachment, default and first-wins behavior, the coarse Session JSON round trip and legacy-record rejection, ACP `user`, in-process subagent `parent`, and disposal precedence. Loop tests make cooperative listeners wait on the signal at pre-step, system-prompt assembly, request, model stream, request-error recovery, tool execution, and turn stopping; they assert one signal within a turn, a fresh signal across turns, and no cancellation authority during terminal publication or a blocked durability flush. A real hook bridge test cancels and reaps a blocked prompt hook before idle.
Contract tests verify the typed caller union, field copying where the cause becomes durable data, default and first-wins behavior, the coarse Session JSON round trip and legacy-record rejection, ACP `user`, in-process subagent `parent`, and disposal precedence. Loop tests make cooperative listeners wait on the signal at pre-step, system-prompt assembly, request, model stream, request-error recovery, tool execution, and turn stopping; they assert one signal within a turn, a fresh signal across turns, and no cancellation authority during terminal publication or a blocked durability flush. A real hook bridge test cancels and reaps a blocked prompt hook before idle.
Initiator-scope tests assert that every hook still observes the exact Agent and no ambient turn signal, concurrent Agents retain independent identities and signals, and a nested child driver shadows only identity. Race tests cover idle cancellation, pre-run cancellation, replacement submission from a `running` listener, repeated cancellation, and cancel-versus-dispose quiescence.
@@ -12,7 +12,7 @@ Status: implemented
## 决策
Agent 拥有仅用于运行时的 `AgentCancelCause` 联合类型 `{ kind: 'user' } | { kind: 'parent' }`;`agent.cancel()` 默认使用 `user`。TypeScript 在这个类型化的同进程边界中强制执行该词汇,不提供运行时校验器、后备行为,也不为无类型调用方提供特殊兼容性约定。活跃的 `TurnCancellation` 会把类型化判别字段复制为一个全新且已冻结的 signal 原因;空闲状态下没有可修改的持有者,也不会让后续工作预先进入取消状态。
Agent 拥有仅用于运行时的 `AgentCancelCause` 联合类型 `{ kind: 'user' } | { kind: 'parent' } | { kind: 'hook'; reason: string } | { kind: 'disposed' }`,`agent.cancel()` 必须显式传入其一。TypeScript 在这个类型化的同进程边界中强制执行该词汇,不提供运行时校验器、后备行为,也不为无类型调用方提供特殊兼容性约定。持有者中止时携带的是调用方自己的那个对象,而非一份分离的拷贝,因此能接触到该 signal 的传输层可以向其添加属性:Node 的 fetch 会给它收到的 reason 赋一个 `stack`。所以循环在 cause 成为持久数据的那一步拷贝已声明字段。在 `cancel()` 处冻结 cause 并非可选方案——undici 是在严格模式代码中执行该赋值的,被冻结的 reason 会让 `fetch` 以 `TypeError: Cannot add property stack` 取代中止原因而拒绝。空闲状态下没有可修改的持有者,也不会让后续工作预先进入取消状态。
正在运行的轮次被中断后,以粗粒度的持久化结果 `{ kind: 'aborted' }` 结束。终态事件记录轮次发生了什么,运行时 signal 标识谁请求了取消;回放不会重复保存 `user` 或 `parent`。会话 seed/load 会拒绝携带取消原因或任何其他额外字段的旧式中止记录,因此回放无法重新引入由调用方持有的取消细节。仅限进程内的 `agent/cancel-requested` 通知不会持久化;未来若有审计需求,应使用独立的持久化控制请求事件,让请求与最终结果保持为两项事实。持久化事件不包含调用栈、signal、错误对象、自由文本取消原因或后端私有细节。
@@ -22,7 +22,7 @@ AgentLoop 为每个待启动轮次私有地持有一个 `TurnCancellation`。它
显式事件签名传递单个 payload 对象:agent 作用域事件在 payload 中携带 `agent` 和 `signal`,`next` 位于最后;其余 API 保持 `signal` 紧邻 waterfall(瀑布式事件)的最终 `next` 之前。`PreStepContext` 与 `RequestFailureContext` 已退役,其字段并入 `agent/pre-step` 与 `agent/request-error` 的 payload([payload-object 事件](../../archived/architecture/2026-08-06-agent-event-payload-objects.md))。进入 pre-step 时、请求配置、请求错误恢复、模型生成、工具执行、审批、轮次停止以及 subagent 或工作流请求都会收到当前 signal。钩子桥接器也必须提供 `RunHookOptions.signal`,使轮次取消能够到达 Bash 执行器由提供方管理的终止与等待边界;[原生 containment 决策](2026-08-28-subprocess-native-containment.zh.md)负责受支持本地路径上的 scope、Job 与 fallback 机制。`SystemPrompt.assemble()` 在 `AssembleContext` 中携带 `signal?: AbortSignal`,因为该对象是显式请求值,也可表示轮次之外不携带 signal 的组装。监听器可以配合该 signal 取消,但不得保留它来控制其他轮次。
`ctx.agents` 仍只携带发起 Agent。环境中的 Agent 并不代表存活、当前轮次或取消权限。cause 读取器是 loop 私有的,它直接陈述机器私有的 slot 不变量(只有 `cancel()` 会中止轮次控制器,且总是携带规范的冻结 cause),而不是对 reason 做结构化再校验;不存在从任意 signal 读取 cause 的公开辅助函数。并发 Agent 会同时隔离各自的发起方身份和轮次 signal;子驱动会遮蔽父发起方,而父请求 signal 仍通过 subagent seam 传递。
`ctx.agents` 仍只携带发起 Agent。环境中的 Agent 并不代表存活、当前轮次或取消权限。cause 读取器是 loop 私有的,它直接陈述机器私有的 slot 不变量(只有 `cancel()` 会中止轮次控制器,且总是携带类型化的 cause),而不是对 reason 做结构化再校验;不存在从任意 signal 读取 cause 的公开辅助函数。并发 Agent 会同时隔离各自的发起方身份和轮次 signal;子驱动会遮蔽父发起方,而父请求 signal 仍通过 subagent seam 传递。
Agent dispose(资源释放)会在活跃持有者上请求仅用于运行时的 `{ kind: 'disposed' }` 中断。若取消已经先占用控制器的中断原因,该原因便无法改写,因此终态分类会先检查生命周期状态:资源释放结果优先,之后受支持的 `user` 或 `parent` 取消原因形成粗粒度的中止结果,其他异常保留现有错误路径。ACP(Agent Client Protocol)取消映射为 `user`;进程内 spawn 和 fork 的传播映射为 `parent`。远程 ACP subagent 保持现有协议。
@@ -30,7 +30,7 @@ Agent dispose(资源释放)会在活跃持有者上请求仅用于运行时
## 验证
约定测试验证类型化调用方联合类型、冻结且与调用方分离、默认行为与首次请求优先行为、粗粒度的会话 JSON 往返与旧式记录拒绝、ACP `user`、进程内 subagent `parent` 以及 dispose 优先级。AgentLoop 测试让协作式监听器在 pre-step、系统提示词组装、请求、模型流、请求错误恢复、工具执行和轮次停止处等待 signal;并断言同一轮次使用一个 signal,不同轮次使用全新的 signal,终态发布期间和持久化刷新受阻期间不存在取消权限。真实钩子桥接器测试会在报告空闲状态前取消并回收受阻的提示词钩子。
约定测试验证类型化调用方联合类型、在 cause 成为持久数据处拷贝字段、默认行为与首次请求优先行为、粗粒度的会话 JSON 往返与旧式记录拒绝、ACP `user`、进程内 subagent `parent` 以及 dispose 优先级。AgentLoop 测试让协作式监听器在 pre-step、系统提示词组装、请求、模型流、请求错误恢复、工具执行和轮次停止处等待 signal;并断言同一轮次使用一个 signal,不同轮次使用全新的 signal,终态发布期间和持久化刷新受阻期间不存在取消权限。真实钩子桥接器测试会在报告空闲状态前取消并回收受阻的提示词钩子。
发起方作用域测试断言所有钩子仍观察到同一个 Agent 且没有环境中的轮次 signal,并发 Agent 保持独立的身份与 signal,嵌套子驱动只遮蔽身份。竞态测试覆盖空闲状态取消、运行前取消、从 `running` 监听器提交替代提示词、重复取消以及取消与 dispose 竞争下的完全停稳。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-20-routed-model-context-and-compaction-policy.md
2026-07-20-routed-model-context-and-compaction-policy.md: 26d8a4a85c284e50b9635017f0dd0bf905a4427f
2026-07-20-routed-model-context-and-compaction-policy.zh.md: 8656200a4b25c2ca70160e8f5949a7518e390168
2026-07-20-routed-model-context-and-compaction-policy.md: d276354cb21f45eaec1c4e3202c7be1770745efb
2026-07-20-routed-model-context-and-compaction-policy.zh.md: b6e2a0b914f78e19675d80b86d8d962e8ba8c80f
@@ -16,7 +16,7 @@ Neither obvious configuration owner is sufficient. Compact-basic is optional and
`LlmAdapter.resolveModel(provider, model, signal?)` returns aggregate metadata for one exact route, with optional `LlmModelContext` under its `context` field. `LlmRuntime.resolveModelInfo()` selects the registered route owner, validates a positive integer `contextWindow`, and returns detached metadata. The query is independent of `listModels()`: an unlisted dynamic model may have capacity metadata, and an absent `context` means only that the adapter cannot describe capacity.
The hand-rolled DeepSeek adapter accepts optional `contextWindow` on each configured model plus an adapter-wide `defaultContextWindow`. Exact model capacity wins; an entry without capacity and an unlisted pass-through id inherit the adapter default, or omit `context` when it is absent. The two built-in model entries each publish an exact 256,000-token capacity. The pi-ai adapter resolves capacity from the same catalog descriptor that authoritatively resolves the request model.
The hand-rolled DeepSeek adapter accepts optional `contextWindow` on each configured model plus an adapter-wide `defaultContextWindow`. Exact model capacity wins; an entry without capacity and an unlisted pass-through id inherit the adapter default, or omit `context` when it is absent. The built-in model entries each declare a 1,000,000-token context capacity. The pi-ai adapter resolves capacity from the same catalog descriptor that authoritatively resolves the request model.
### Token measurement remains model-agnostic
@@ -26,17 +26,17 @@ The hand-rolled DeepSeek adapter accepts optional `contextWindow` on each config
Compact-basic owns consumer policy. Top-level fields define defaults; `modelPolicies` contains partial overrides keyed by the exact `{ provider, model }` pair. Duplicate targets and unknown or invalid fields fail plugin load. `thresholdRatio` defaults to `0.8`, and retention defaults to `retainRatio: 0.16`; callers may use an absolute `retainTokens` instead, but the two retention forms are mutually exclusive. After inheritance, a ratio retention that is not below its threshold ratio also fails plugin load because no model capacity can make that policy valid.
For proactive pressure, compaction-basic reads the latest durable request route, resolves its adapter capacity and exact-target policy, and scales ratios into a `ResolvedCompactSpec`. It performs this resolution on every check, so a provider or model switch in one session changes capacity and policy immediately. An absolute retained budget that is not below the scaled threshold fails when the target capacity first makes that comparison possible.
For proactive pressure, compaction-basic reads the latest durable route and resolves its adapter capacity `W`, request output reservation `O`, and exact-target policy. The trigger is `floor(min(W × thresholdRatio, W − O − headroomTokens))`, with default headroom of 65,536 tokens. The window fraction limits history growth while the second cap leaves extra room after a full output for compaction input and generation; it does not bound arbitrary tool-result growth. Retention scales `W − O` independently. `O` is the effective envelope’s `maxTokens`, else the adapter’s per-request default, else zero. Resolution runs on every check, so route or output-cap changes immediately affect policy. A nonpositive pressure budget or retention at least as large as the trigger fails when target capacity makes that comparison possible.
The same exact-target override can select summarization provider/model, summarization output cap, convergence retries, and overflow retry cap. These are compaction concerns and never enter an LLM provider.
### Target-specific pressure failures preserve optional composition
An adapter that lacks capacity metadata remains a valid LLM route. Manual proactive pressure fails with a target-specific configuration error; the automatic listener warns once per exact route and continues with full history. The same per-route suppression applies when resolved capacity exposes an invalid absolute retention budget, while unrelated operational failures remain independently visible. Canonical provider-confirmed overflow does not need capacity metadata: it bypasses the proactive threshold and normal retention budget, attempts one maximal balanced reduction, and preserves the original provider error unless replacement proves progress.
An adapter that lacks capacity metadata remains a valid LLM route. Manual proactive pressure fails with a target-specific configuration error; the automatic listener warns once per exact route and continues with full history. The same suppression applies when output plus headroom exhausts capacity or retention reaches the trigger, while unrelated operational failures remain independently visible. Canonical provider-confirmed overflow does not need capacity metadata: it bypasses the proactive threshold and normal retention budget, attempts one maximal balanced reduction, and preserves the original provider error unless replacement proves progress.
## Testing
Service tests cover detached context metadata, invalid adapter output, catalog independence, and default absence. Adapter tests cover DeepSeek exact/default/unlisted resolution, invalid capacities, and pi-ai exact descriptor resolution. Compact tests cover ratio scaling, exact provider/model overrides, load-time rejection of invalid merged ratios, runtime absolute-budget validation, same-model-id provider switches, target-specific warning suppression, and capacity-independent overflow recovery. Loader fixtures reject the removed token-meter capacity setting, and examples configure capacity on adapters.
Service tests cover detached context metadata, invalid adapter output, catalog independence, and default absence. Adapter tests cover DeepSeek exact/default/unlisted resolution, invalid capacities, and pi-ai exact descriptor resolution. Compact tests cover both trigger caps, default and overridden headroom, envelope output caps and adapter fallback, independent retention scaling, invalid budgets, exact-target overrides, provider switches, warning suppression, and capacity-independent overflow recovery. The keyless `compaction-output-reserve` headless scenario records a nonzero request cap, proactive compaction before the next step, and replacement history. Loader fixtures reject the removed token-meter capacity setting, and examples configure capacity on adapters.
## Alternatives considered
@@ -52,6 +52,6 @@ Service tests cover detached context metadata, invalid adapter output, catalog i
- The same compaction-basic instance safely handles different windows, provider switches, and identical model ids under different providers without consulting discovery metadata.
- LLM-only and meter-only compositions remain valid; loading compaction-basic adds no reverse dependency from adapters.
- DeepSeek deployments may set exact per-model capacities, or use `defaultContextWindow` for entries without capacity and unlisted pass-through ids.
- Ratio defaults scale naturally across models, while exact-target absolute retention remains available for deployment-specific behavior.
- Ratio defaults scale naturally across models and exclude the output tokens each request reserves, while exact-target absolute retention remains available for deployment-specific behavior.
This note supersedes the global-capacity and no-model-policy parts of the [replay token meter service Agent Note](../../archived/architecture/2026-07-15-replay-token-meter-service.md). Its single-fold measurement decision remains unchanged.
@@ -16,7 +16,7 @@ Status: implemented
`LlmAdapter.resolveModel(provider, model, signal?)` 返回一条精确路由的聚合元数据,其中可选的 `LlmModelContext` 位于 `context` 字段下。`LlmRuntime.resolveModelInfo()` 选择已注册的路由所属方,验证 `contextWindow` 为正整数,并返回与适配器内部状态分离的元数据。该查询独立于 `listModels()`:不在目录中的动态模型也可以拥有容量元数据,而缺少 `context` 只表示适配器无法描述容量。
手写 DeepSeek 适配器允许每个已配置模型提供可选 `contextWindow`,并支持适配器级 `defaultContextWindow`。精确模型容量优先;未提供容量的模型项与未列出的透传 id 会继承适配器默认值,若默认值也不存在则省略 `context`。两个内置模型项都公开精确的 256,000 token 容量。pi-ai 适配器从同一个目录描述符解析容量,该描述符也用于权威解析请求模型。
手写 DeepSeek 适配器允许每个已配置模型提供可选 `contextWindow`,并支持适配器级 `defaultContextWindow`。精确模型容量优先;未提供容量的模型项与未列出的透传 id 会继承适配器默认值,若默认值也不存在则省略 `context`。内置模型项各自声明 1,000,000 token 的上下文容量。pi-ai 适配器从同一个目录描述符解析容量,该描述符也用于权威解析请求模型。
### Token 计量保持模型无关
@@ -26,17 +26,17 @@ Status: implemented
Compact-basic 拥有消费方策略。顶层字段定义默认值;`modelPolicies` 包含以精确 `{ provider, model }` 组合为键的部分覆盖。重复目标、未知字段或无效字段都会让插件加载失败。`thresholdRatio` 默认为 `0.8`,保留策略默认为 `retainRatio: 0.16`;调用方也可以改用绝对 `retainTokens`,但两种保留形式互斥。完成继承后,如果保留比例不小于阈值比例,插件也会加载失败,因为任何模型容量都无法让该策略有效。
对于主动压力检查,compaction-basic 读取最新持久请求路由,解析其适配器容量与精确目标策略,再把比例缩放为 `ResolvedCompactSpec`。每次检查都会重新解析,因此同一会话切换提供方或模型后,容量与策略会立即变化。若绝对保留预算不小于缩放后的阈值,系统会在目标容量首次允许比较两者时失败。
对于主动压力检查,compaction-basic 读取最新持久路由,解析适配器容量 `W`、请求输出预留 `O` 和精确目标策略。触发阈值为 `floor(min(W × thresholdRatio, W − O − headroomTokens))`,默认余量为 65,536 tokens。窗口比例限制历史增长,第二项则在一次完整输出后为压缩输入与生成留出额外空间;它无法限制任意工具结果的增长。保留预算独立按 `W − O` 缩放。`O` 取自生效信封的 `maxTokens`,否则取适配器按请求的默认值,再回退到零。每次检查都会重新解析,因此路由或输出上限变化会立即影响策略。若压力预算非正,或保留预算不低于触发阈值,会在目标容量允许比较时失败。
同一精确目标覆盖还可以选择摘要提供方/模型、摘要输出上限、收敛重试次数与溢出重试上限。这些都属于压缩问题,不会进入任何 LLM 提供方。
### 目标专用压力错误仍保留可选组合
缺少容量元数据的适配器仍是有效 LLM 路由。手动主动压力检查会返回目标专用配置错误;自动监听器按精确路由只警告一次,并继续保留完整历史。当已解析容量暴露出无效的绝对保留预算时,系统也按路由抑制重复警告;其他运行故障仍会各自对外可见。提供方已经确认的规范化溢出不需要容量元数据:它绕过主动阈值与普通保留预算,尝试一次最大且平衡的缩减,并在替换无法证明进展时保留原始提供方错误。
缺少容量元数据的适配器仍是有效 LLM 路由。手动主动压力检查会返回目标专用配置错误;自动监听器按精确路由只警告一次,并继续保留完整历史。当输出预留与余量耗尽容量,或保留预算达到触发阈值时,也会抑制重复警告;其他运行故障仍会各自对外可见。提供方已经确认的规范化溢出不需要容量元数据:它绕过主动阈值与普通保留预算,尝试一次最大且平衡的缩减,并在替换无法证明进展时保留原始提供方错误。
## 测试
服务测试覆盖与适配器内部状态分离的上下文元数据、无效适配器输出、目录独立性与默认缺失行为。适配器测试覆盖 DeepSeek 的精确容量、默认容量、未列出模型解析及无效容量,以及 pi-ai 的精确描述符解析。压缩测试覆盖比例缩放、精确提供方/模型覆盖、加载期拒绝无效合并比例、运行时校验绝对预算、相同模型 id 的提供方切换、目标专用警告抑制与不依赖容量的溢出恢复。Loader fixture(测试前置数据)会拒绝已经移除的 token-meter 容量设置,示例则在适配器上配置容量。
服务测试覆盖与适配器内部状态分离的上下文元数据、无效适配器输出、目录独立性与默认缺失行为。适配器测试覆盖 DeepSeek 的精确容量、默认容量、未列出模型解析及无效容量,以及 pi-ai 的精确描述符解析。压缩测试覆盖两项阈值上限、默认和覆盖余量、信封输出上限与适配器回退、独立保留预算缩放、无效预算、精确目标覆盖、提供方切换、警告抑制和不依赖容量的溢出恢复。无需密钥的 `compaction-output-reserve` headless 场景记录非零请求上限、下一步之前的主动压缩和替换后的历史。Loader fixture(测试前置数据)会拒绝已经移除的 token-meter 容量设置,示例则在适配器上配置容量。
## 考虑过的替代方案
@@ -52,6 +52,6 @@ Compact-basic 拥有消费方策略。顶层字段定义默认值;`modelPolici
- 同一个 compaction-basic 实例无需查询发现元数据,就能安全处理不同窗口、提供方切换,以及不同提供方下的相同模型 id。
- 仅 LLM 与仅 meter 的组合仍然有效;加载 compaction-basic 不会让适配器产生反向依赖。
- DeepSeek 部署可以设置精确的逐模型容量,也可以让未提供容量的模型项与未列出的透传 id 使用 `defaultContextWindow`。
- 比例默认值会随模型自然缩放,同时仍可按精确目标使用绝对保留值,以满足部署专用行为。
- 比例默认值会随模型自然缩放,并扣除每次请求预留的输出 token;同时仍可按精确目标使用绝对保留值,以满足部署专用行为。
本记录取代[回放式 token 计量服务 Agent Note](../../archived/architecture/2026-07-15-replay-token-meter-service.md) 中的全局容量与无模型策略部分,单折叠计量决策保持不变。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-23-client-plugin-loading-model.md
2026-07-23-client-plugin-loading-model.md: a8d016a70792ad4ac6a71083677ce5d82a30af0a
2026-07-23-client-plugin-loading-model.zh.md: 1b94909c9611f01e5a6d7fcb50ec3f91ee88fab1
2026-07-23-client-plugin-loading-model.md: b8c23108a7a1cf3a04ded373d8705bae626c70c2
2026-07-23-client-plugin-loading-model.zh.md: efaea843120e5b023bbc8f59663ea47468944c71
@@ -46,7 +46,7 @@ A `require.async("./client.<name>.js")` call requests the exact revisioned `/plu
The shared tsdown preset emits a map for every plugin entry and chunk and rewrites first-party source paths into the browser-resolvable repository form `/packages/<group>/<package>/src/...`. The production Client pass consumes `lib/types`; the preset supplies each tsc map to Rolldown and fills `sourcesContent` from the original files, so the final map reaches TypeScript/TSX instead of stopping at emitted JavaScript. Other workspace sources inlined into a bundle likewise resolve to their `packages/` owner, while dependency paths remain unchanged. Combo generation strips each local debug directive, records its generated-line offset, resolves every authored source against the original per-plugin map URL, and emits an Indexed Source Map v3. An authored map supplies its section; otherwise an identity section embeds the generated bundle and uses the packer's `sourceURL` as its source name when present. The absolute combo map URL mirrors the script resource list by changing every `client.js` suffix to `client.js.map`, so `/plugins/??<package-a>/client.js,<package-b>/client.js&rev=<rev>` points to `/plugins/??<package-a>/client.js.map,<package-b>/client.js.map&rev=<rev>`. A chunk script likewise points to its own map URL. Source-map files are read and combined only when those map URLs receive `GET`; `HEAD` materializes neither script nor map bodies. The Vite shell also emits source maps, letting shell code and externally loaded plugins map stacks and performance profiles back to TypeScript/TSX.
The graph retains each row's revisioned one-resource combo URL for HMR and adds a revisioned descriptor for every startup combo request; several descriptors may carry the same scheduling phase. Initial row revisions are opaque process nonces rather than content hashes; they keep the snapshotted one-resource response immutable without hashing every plugin at startup. The shared preset stamps the entry after every package output is written. After the watcher observes that completed-build stamp, `rebuilt(id)` hashes the entry bytes together with its timestamp and publishes the resulting revision; a chunk-only build therefore advances the owner revision without a Host-side sibling scan. Startup combo revisions derive from the ordered row revisions. Script bodies are assembled on their first `GET`; source-map files are read and combined separately on their first map `GET`. `HEAD` materializes neither body. Versioned scripts and maps use immutable caching, and unrelated graph recomposition retains already-materialized chunk responses at the same revision. The Host serves only exact generated URLs; stale revisions and unadvertised resource lists return 404 instead of aliasing different bytes. An external script's `error` event exposes neither response status nor body, so failure diagnostics name only the URL; the same-origin Host and build-stamped registration id form the identity boundary, while the post-`load` factory-presence check rejects an artifact that did not register the expected id.
The graph retains each row's revisioned one-resource combo URL for HMR and adds a revisioned descriptor for every startup combo request; several descriptors may carry the same scheduling phase. Initial publication and `rebuilt(id)` use the same hash of entry mtime, ctime, and size; artifact contents never enter the hash. Ctime distinguishes rewrites that preserve mtime and size. A Host restart over unchanged artifacts therefore retains every row revision, so the reconnecting SSE snapshot does not trigger browser plugin replacement. The shared preset stamps the entry after every package output is written; a chunk-only build therefore advances the owner revision without a Host-side sibling scan. Startup combo revisions derive from the ordered row revisions. Script bodies are assembled on their first `GET`; source-map files are read and combined separately on their first map `GET`. `HEAD` materializes neither body. Versioned scripts and maps use immutable caching, and unrelated graph recomposition retains already-materialized chunk responses at the same revision. The Host serves only exact generated URLs; stale revisions and unadvertised resource lists return 404 instead of aliasing different bytes. An external script's `error` event exposes neither response status nor body, so failure diagnostics name only the URL; the same-origin Host and build-stamped registration id form the identity boundary, while the post-`load` factory-presence check rejects an artifact that did not register the expected id.
### The loading flow, end to end
@@ -56,7 +56,7 @@ What happens between `dsh web` starting and the UI appearing? Three stages: the
1. The composing app (`apps/cli`) ships the roster as ordinary rows in its `cordis.yml` config tree — client plugin packages are entry rows like every host plugin, including the always-mounted `client-hmr` row. `auditStartupEntries` reports failed imports, activation errors with their original stacks, and pending dependencies; optional entries warn and required entries reject startup ([startup policy](2026-09-09-consumer-owned-startup-strictness.md)).
2. The `dsh-client-modules` node half (the package is dual-face: its browser half is the module table) resolves each live Loader entry through the same `name` and owning-tree `baseUrl` inputs that imported its Host face, then reads the nearest owning package.json `dsh.client` declaration and composes `window.__DSH_BOOT__`: `{ rev, entries: [{ id, url, rev, inject?, immediately?, external? }], batches: [{ phase, url, rev, entries }] }`. The manifest package name is the browser module identity even when an overlay names a relative source or built entry file. Distinct active Loader sources resolving to one package name fail composition; after one source unloads, the surviving source supplies the row without a fiber restart. The row's three optional fields come from manifests, never hand-copied. Composition orders requested dynamic rows before their consumers, rejects synchronous request cycles, and assigns every row to exactly one initial batch. It refuses declared plugins without built `./client` bundles and groups their package/path rows under one required source-build instruction; malformed declaration fields also fail activation, and the Host audit reports either error from the FAILED fiber.
3. Scanning is incremental per package — there is no full-rescan code path. Each cordis `internal/plugin` emission marks the fiber's entry name dirty (entry-less fibers drop O(1)); a microtask flush reconciles each dirty name against live loader entries, with package metadata (including the negative "not a client package" verdict) cached per entry name and owning-tree base URL for the process lifetime and bundle re-hashing reachable only through `rebuilt(id)`. The activation pass seeds the same dirty set from current entries and flushes synchronously, so first scan and steady state share one implementation. Initial rows receive an opaque process nonce plus sequence without hashing their artifacts; startup combo revisions hash the combined script inputs plus indexed map, and the rows plus batch descriptors hash into `graph.rev`. The graph types are single-sourced in the modules package's `./client` export — the webserver knows nothing about the graph, while modules registers the combo route and contributes structured index-injection rows.
3. Scanning is incremental per package — there is no full-rescan code path. Each cordis `internal/plugin` emission marks the fiber's entry name dirty (entry-less fibers drop O(1)); a microtask flush reconciles each dirty name against live loader entries, with package metadata (including the negative "not a client package" verdict) cached per entry name and owning-tree base URL for the process lifetime and changed bundle publication reachable only through `rebuilt(id)`. The activation pass seeds the same dirty set from current entries and flushes synchronously, so first scan and steady state share one implementation. Initial rows hash their pre-read filesystem metadata; startup combo revisions hash the ordered row identities and revisions, and the rows plus batch descriptors hash into `graph.rev`. The graph types are single-sourced in the modules package's `./client` export — the webserver knows nothing about the graph, while modules registers the combo route and contributes structured index-injection rows.
Why is the roster yml rows and not a scan? Because which plugins compose into a deployment is a composition decision, not a package property — a package declaring `dsh.client` in the repo does not mean this deployment mounts it, so discovery-by-scan cannot make that call; the node half scans only what the tree actually mounted.
@@ -80,7 +80,7 @@ The Host SSE adapter forwards existing graph-change notifications and sends the
Hot reload is a composition decision: the web bundle mounts the `client-hmr` row (a normal plugin package) unconditionally; its node half brings the bundle watch and the SSE channel, and the chain stays idle until a rebuild watcher rewrites client bundles. A composition that must not expose it disables the row.
How does a rebuilt bundle become a reload signal? The hmr node half observes it itself — no builder tells it. Before reading each startup snapshot, the module host captures the entry stat baseline and exposes it through `ctx.clientModules.artifactBaseline(id)`. One HMR-owned interval compares every current graph row with that baseline. An unchanged row starts watching without a content read or hash; a write after baseline capture is already a stat delta and only that row enters `rebuilt(id)`. This avoids both an initial all-row re-hash and `fs.watchFile`, whose asynchronous first baseline can silently absorb a construction-time rebuild. Watch membership follows `onGraphChanged`; vanished rows drop out, and an entry missing at poll time keeps its row dirty so reappearance forces a new revision even with identical metadata. The shared tsdown preset stamps `client.js` after every sibling output has been written; on an entry mtime or size delta, or a dirty row, `rebuilt(id)` derives the revision from the entry bytes and completed-build timestamp. A chunk-only edit therefore changes the revision, and a partial write followed by the completion stamp supplies a later stat change that self-heals. When the `rev` changes, the node half broadcasts a `rebuilt` frame on `GET /plugins/events` — a system SSE channel that sends the full graph on connect and `rebuilt` frames on change, presentation-only wire that never enters the session log. Polling is deliberate because inotify does not fire on the weka network mount, the same reason the build-side watcher needs `--poll`; one entry stat per row and interval is sufficient, the interval is a validated config field (default 500ms), and disposal clears the one timer. Rebuilding artifacts is any process using the shared Client tsdown preset — `scripts/dev-web.ts` remains the watch-build entry point, discovering its package list through `dsh.client` while scanning `packages/*/*/package.json` at startup — and builder and host share no notification protocol.
How does a rebuilt bundle become a reload signal? The hmr node half observes it itself — no builder tells it. Before reading each startup snapshot, the module host captures the entry stat baseline and exposes it through `ctx.clientModules.artifactBaseline(id)`. One HMR-owned interval compares every current graph row with that baseline. An unchanged row starts watching without a content read or hash; a write after baseline capture is already a stat delta and only that row enters `rebuilt(id)`. This avoids redundant reads when installing the watcher and avoids `fs.watchFile`, whose asynchronous first baseline can silently absorb a construction-time rebuild. Watch membership follows `onGraphChanged`; vanished rows drop out, and an entry missing at poll time keeps its row dirty so reappearance retries publication even when mtime and size match the baseline. The shared tsdown preset stamps `client.js` after every sibling output has been written; on an entry mtime, ctime, or size delta, or a dirty row, `rebuilt(id)` derives the revision from those three metadata values. It reads the new bundle snapshot only if the revision changes. A chunk-only edit therefore changes the revision, and a partial write followed by the completion stamp supplies a later stat change that self-heals. When the `rev` changes, the node half broadcasts a `rebuilt` frame on `GET /plugins/events` — a system SSE channel that sends the full graph on connect and `rebuilt` frames on change, presentation-only wire that never enters the session log. Polling is deliberate because inotify does not fire on the weka network mount, the same reason the build-side watcher needs `--poll`; one entry stat per row and interval is sufficient, the interval is a validated config field (default 500ms), and disposal clears the one timer. Rebuilding artifacts is any process using the shared Client tsdown preset — `scripts/dev-web.ts` remains the watch-build entry point, discovering its package list through `dsh.client` while scanning `packages/*/*/package.json` at startup — and builder and host share no notification protocol.
On the browser side, the transport delegates code replacement to the same modules controller that owns graph reconciliation:
@@ -120,4 +120,4 @@ Roster: it lives in the web bundle's config tree (`packages/bundle/web-app/cordi
| Full ctx-ification now (React and libraries via services, no module table) | Static UI libraries still expose synchronous values, so removing the table would leave those imports without a shared identity |
| Eager instantiation with a frozen table | Runs bundle side effects at script arrival; lazy registration keeps execution at Cordis import while recursive `require` materializes registered requests |
| Fetch response text, then inject an inline `<script>` | Makes the module system buffer the complete source and maintain separate fetch/execute paths; dynamic source execution also breaks the browser-native association among the network resource, source map, and profile |
| Builder-push rebuild channel (`POST /plugins/rebuilt` from the orchestrator's `onSuccess`) | Couples reload to one blessed builder process and a second wire protocol; the webserver already holds every bundle path, and stat polling covers the torn-write race (re-hash on every stat change) that once justified pushing |
| Builder-push rebuild channel (`POST /plugins/rebuilt` from the orchestrator's `onSuccess`) | Couples reload to one blessed builder process and a second wire protocol; the webserver already holds every bundle path, and stat polling covers the torn-write race (publication on every stat change) that once justified pushing |
@@ -46,7 +46,7 @@ Host 会快照每个已构建插件入口 bundle,并把每个调度阶段的
共享 tsdown 预设为每个插件入口与 chunk 产出 map,并把第一方源码路径重写成浏览器可识别的仓库形式 `/packages/<group>/<package>/src/...`。生产 Client 构建会消费 `lib/types`;预设把每份 tsc map 交给 Rolldown,并从原文件补齐 `sourcesContent`,使最终 map 回到 TypeScript/TSX,而不是停在编译后的 JavaScript。内联进 bundle 的其他 workspace 源码同样回到其 `packages/` 归属,依赖包路径保持原样。Combo 生成会移除每个局部调试指令、记录其生成行偏移、以原插件 map URL 解析每个自带 source,再产出 Indexed Source Map v3。插件有自带 map 时直接用于对应 section;没有时则生成 identity section,内嵌构建后 bundle,并在存在时把 packer 写入的 `sourceURL` 用作 source 名。绝对 combo map URL 会平行改写脚本资源列表中的每个 `client.js` 后缀,因此 `/plugins/??<package-a>/client.js,<package-b>/client.js&rev=<rev>` 指向 `/plugins/??<package-a>/client.js.map,<package-b>/client.js.map&rev=<rev>`。chunk 脚本同样指向自己的 map URL。只有在这些 map URL 收到 `GET` 后,source map 文件才会被读取和组合;`HEAD` 不会物化脚本或 map body。Vite 壳同样产出 sourcemap,使壳代码与经外部加载的插件都能从 stack 和性能 profile 回到 TypeScript/TSX。
图为 HMR 保留每个 row 带 revision 的单资源 combo URL,并为每个启动 combo 请求增加带 revision 的描述;多条描述可以使用同一调度阶段。初始 row revision 是进程级不透明 nonce,而不是内容哈希;它无需在启动时哈希每个插件,也能保证已快照的单资源响应不可变。共享预设会在每个包输出写完后标记入口。watcher 观察到该构建完成标记后,`rebuilt(id)` 会把入口字节与其时间戳一起哈希并发布所得 revision;因此只重建 chunk 也会推进 owner revision,无需 Host 扫描 sibling。启动 combo revision 从有序 row revision 派生。脚本 body 在首次 `GET` 时组合;source map 文件在首次 map `GET` 时单独读取并组合。`HEAD` 不会物化任一 body。版本化脚本与 map 使用 immutable 缓存,无关图重组会保留相同 revision 下已经物化的 chunk 响应。Host 只提供精确生成的 URL;陈旧 revision 与未发布资源列表返回 404,不会别名到其他字节。外部脚本的 `error` 事件不给响应状态与正文,因此失败诊断只报告 URL;同源 Host 与构建期写入的 registration id 是身份边界,`load` 后的 factory 存在性检查负责拒绝未登记预期 id 的产物。
图为 HMR 保留每个 row 带 revision 的单资源 combo URL,并为每个启动 combo 请求增加带 revision 的描述;多条描述可以使用同一调度阶段。首次发布与 `rebuilt(id)` 使用相同的入口 mtime、ctime 和大小哈希;产物内容不参与求哈希。ctime 用于区分保留 mtime 和大小的重写。Host 重启时若产物未变,每个 row 的 revision 都保持不变,因此重连后的 SSE 快照不会触发浏览器插件替换。共享预设会在每个包输出写完后标记入口;因此只重建 chunk 也会推进 owner revision,无需 Host 扫描 sibling。启动 combo revision 从有序 row revision 派生。脚本 body 在首次 `GET` 时组合;source map 文件在首次 map `GET` 时单独读取并组合。`HEAD` 不会物化任一 body。版本化脚本与 map 使用 immutable 缓存,无关图重组会保留相同 revision 下已经物化的 chunk 响应。Host 只提供精确生成的 URL;陈旧 revision 与未发布资源列表返回 404,不会别名到其他字节。外部脚本的 `error` 事件不给响应状态与正文,因此失败诊断只报告 URL;同源 Host 与构建期写入的 registration id 是身份边界,`load` 后的 factory 存在性检查负责拒绝未登记预期 id 的产物。
### 装载流程,端到端
@@ -56,7 +56,7 @@ Host 会快照每个已构建插件入口 bundle,并把每个调度阶段的
1. 负责组合的 app(`apps/cli`)把名册作为普通行放进它的 `cordis.yml` 配置树——client 插件包与每个 host 插件一样是 entry 行,包括无条件挂载的 `client-hmr` 行。`auditStartupEntries` 报告 import 失败、带原始 stack 的激活错误,以及待满足的依赖;optional entry 输出 warning,required entry 则使启动失败([启动策略](2026-09-09-consumer-owned-startup-strictness.zh.md))。
2. `dsh-client-modules` 的 node 半(该包是双面的:浏览器半就是模块表)使用 Host face import 时相同的 `name` 与所属 tree `baseUrl` 解析每个 live Loader entry,再读取最近归属 package.json 的 `dsh.client` 声明并组合出 `window.__DSH_BOOT__`:`{ rev, entries: [{ id, url, rev, inject?, immediately?, external? }], batches: [{ phase, url, rev, entries }] }`。即使 overlay 指向相对的 source 或 built entry 文件,manifest 包名仍是浏览器模块身份。若不同的 active Loader source 解析到同一包名,组合会失败;一个来源卸载后,仍存活的来源无需重启 fiber 即可提供该 row。Row 的三个可选字段都来自 manifest,永不人肉抄写。组合会把被请求的动态图 row 排到消费者之前、拒绝同步请求环,并把每个 row 恰好分配给一个初始批次。它会拒绝没有已构建 `./client` bundle 的已声明插件,并把它们的 package/path 行归到一条源码构建要求下;畸形声明字段同样会让激活失败,Host 检查会从 FAILED fiber 报告这两类错误。
3. 扫描是单包增量——不存在全量重扫代码路径。每次 cordis `internal/plugin` 发射把该 fiber 的 entry 名标脏(无 entry 的 fiber O(1) 丢弃);微任务 flush 把每个脏名对账 live loader entries,包元数据(含「非 client 包」的否定结论)按 entry 名与所属 tree base URL 缓存至进程结束,bundle 重哈希只经 `rebuilt(id)` 可达。激活趟从当前 entries 灌同一脏集合并同步 flush,初扫与稳态共享一条实现。初始 row 使用不透明的进程 nonce 加序号,不对其产物求哈希;启动 combo revision 对合并脚本输入及 indexed map 求哈希,row 与批次描述再共同哈希进 `graph.rev`。图类型单源在 modules 包的 `./client` 出口——webserver 对图一无所知;modules 会注册 combo 路由并贡献结构化 index 注入行。
3. 扫描是单包增量——不存在全量重扫代码路径。每次 cordis `internal/plugin` 发射把该 fiber 的 entry 名标脏(无 entry 的 fiber O(1) 丢弃);微任务 flush 把每个脏名对账 live loader entries,包元数据(含「非 client 包」的否定结论)按 entry 名与所属 tree base URL 缓存至进程结束,变化后 bundle 的发布只经 `rebuilt(id)` 可达。激活趟从当前 entries 灌同一脏集合并同步 flush,初扫与稳态共享一条实现。初始 row 对读取前的文件系统元数据求哈希;启动 combo revision 对有序 row 的身份与 revision 求哈希,row 与批次描述再共同哈希进 `graph.rev`。图类型单源在 modules 包的 `./client` 出口——webserver 对图一无所知;modules 会注册 combo 路由并贡献结构化 index 注入行。
为什么名册是 yml 行而不是扫描?因为哪些插件组合进一次部署是组合决策,不是包属性——一个在仓库中声明了 dsh.client 的包,不代表这次部署要挂载它,扫描发现无从替人做这个决定;node 半只扫描配置树实际挂载了的东西。
@@ -80,7 +80,7 @@ Host SSE 适配器转发现有图变化通知,并在连接时发送当前完
热重载是一项组合决策:web 组合包无条件挂载 `client-hmr` 行(一个常规的插件包),其 node 半带来 bundle 监视与 SSE(Server-Sent Events)通道;没有重建 watcher 改写客户端 bundle 时链路保持空闲。不应暴露它的组合可以禁用该行。
重建好的 bundle 怎么变成重载信号?hmr 的 node 半自己观察——没有构建器来通知它。模块 host 在读取每份启动快照前捕获入口 stat 基线,并通过 `ctx.clientModules.artifactBaseline(id)` 暴露它。HMR 自持的单个定时器把当前图的每个 row 与这份基线比较:未变化的 row 直接开始监视,不读取内容也不求哈希;基线捕获后的写入已经形成 stat 差异,只有该 row 会进入 `rebuilt(id)`。这同时消除了启动期的全量重哈希,并避开 `fs.watchFile` 以异步首次 stat 建立基线、可能静默吸收构造期重建的问题。监视集合的成员随 `onGraphChanged` 更新;消失的 row 撤下监视,轮询时缺失的入口则让对应 row 保持标脏状态,文件重现时即使元数据相同也强制生成新 revision。共享 tsdown 预设会在每个 sibling 输出写完后标记 `client.js`;入口 mtime 或 size 变化、或 row 处于标脏状态时,`rebuilt(id)` 从入口字节与构建完成时间戳派生 revision。只修改 chunk 因此也会更换 revision;部分写入之后的完成标记还会提供一次更晚的 stat 变化以完成自愈。`rev` 变化时,node 半在 `GET /plugins/events` 上广播 `rebuilt` 帧——这是一条系统级 SSE 通道,连接即发全量图,变更时发 `rebuilt` 帧,仅供呈现的 wire,永不进会话日志。轮询是刻意选择:inotify 在 weka 网络挂载上不触发,构建侧监视器需要 `--poll` 也是同一原因;每个 row 每个间隔只需一次入口 stat,轮询间隔是一个经校验的配置字段(默认 500ms),dispose(资源释放)会清掉那一个定时器。重建产物的进程必须使用共享 Client tsdown 预设;`scripts/dev-web.ts` 仍作为 watch 构建入口保留,其包清单在启动时扫描 `packages/*/*/package.json` 按 dsh.client 发现,构建器与 host 之间没有通知协议。
重建好的 bundle 怎么变成重载信号?hmr 的 node 半自己观察——没有构建器来通知它。模块 host 在读取每份启动快照前捕获入口 stat 基线,并通过 `ctx.clientModules.artifactBaseline(id)` 暴露它。HMR 自持的单个定时器把当前图的每个 row 与这份基线比较:未变化的 row 直接开始监视,不读取内容也不求哈希;基线捕获后的写入已经形成 stat 差异,只有该 row 会进入 `rebuilt(id)`。这既避免安装 watcher 时重复读取,也避开 `fs.watchFile` 以异步首次 stat 建立基线、可能静默吸收构造期重建的问题。监视集合的成员随 `onGraphChanged` 更新;消失的 row 撤下监视,轮询时缺失的入口则让对应 row 保持标脏状态,文件重现时即使 mtime 和大小与基线相同也重试发布。共享 tsdown 预设会在每个 sibling 输出写完后标记 `client.js`;入口 mtime、ctime 或大小变化、或 row 处于标脏状态时,`rebuilt(id)` 从这三个元数据值派生 revision。只有 revision 变化时,它才读取新的 bundle 快照。只修改 chunk 因此也会更换 revision;部分写入之后的完成标记还会提供一次更晚的 stat 变化以完成自愈。`rev` 变化时,node 半在 `GET /plugins/events` 上广播 `rebuilt` 帧——这是一条系统级 SSE 通道,连接即发全量图,变更时发 `rebuilt` 帧,仅供呈现的 wire,永不进会话日志。轮询是刻意选择:inotify 在 weka 网络挂载上不触发,构建侧监视器需要 `--poll` 也是同一原因;每个 row 每个间隔只需一次入口 stat,轮询间隔是一个经校验的配置字段(默认 500ms),dispose(资源释放)会清掉那一个定时器。重建产物的进程必须使用共享 Client tsdown 预设;`scripts/dev-web.ts` 仍作为 watch 构建入口保留,其包清单在启动时扫描 `packages/*/*/package.json` 按 dsh.client 发现,构建器与 host 之间没有通知协议。
浏览器侧的传输把代码替换交给负责图对账的同一个 modules 控制器:
@@ -120,4 +120,4 @@ Wire 两侧运行同一份治理实现;浏览器特有层只包含一套模块
| 现在就彻底 ctx 化(React 与库全走服务,不设模块表) | 静态 UI 库仍暴露同步实体,因此删除模块表会让这些 import 失去共享身份 |
| 冻结表 + 到达即实例化 | 会在 script 到达时执行 bundle 副作用;惰性登记把执行推迟到 Cordis import,并由递归 `require` 物化已登记请求 |
| fetch 响应文本后注入内联 `<script>` | 模块系统必须缓冲整份源码并维护 fetch/execute 两条路径;动态源码执行也切断浏览器网络资源、sourcemap 与 profile 的原生关联 |
| 构建器推送重建通道(编排器在 `onSuccess` 里 POST `/plugins/rebuilt`) | 把重载耦合到一个钦定的构建器进程和第二套 wire 协议;webserver 本就握有每个 bundle 路径,stat 轮询(每次 stat 变化即重哈希)已兜住当年为推送辩护的撕裂写竞态 |
| 构建器推送重建通道(编排器在 `onSuccess` 里 POST `/plugins/rebuilt`) | 把重载耦合到一个钦定的构建器进程和第二套 wire 协议;webserver 本就握有每个 bundle 路径,stat 轮询(每次 stat 变化即发布)已兜住当年为推送辩护的撕裂写竞态 |
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.md
2026-07-25-web-client-session-scope-and-provide-channel.md: 6126d06e48111a551a0dc57247659a3086986c66
2026-07-25-web-client-session-scope-and-provide-channel.zh.md: e7bf4aa6952b4fd7b187c91e2ac5d64aac32cabb
2026-07-25-web-client-session-scope-and-provide-channel.md: 76561bc0e330b3e3a26607569ae327217d0a0b67
2026-07-25-web-client-session-scope-and-provide-channel.zh.md: d5d92b2ca53ba78929dd8591a6ded7028e4fed24
@@ -26,9 +26,9 @@ The reference-owned lifetime and Provider targeting now follow [Client Session r
Host-side `session.create(workspaceId)` produces Session + Agent + cwd in one piece (an atomic bundle, never split); the client side is the mirror of that birth — the instant a session row enters the list mirror, the client mints its Agent scope (actx + provide + the full input surface mounted):
- Session identity is the host's true form from birth: the sessionId arrives via the `session.create` response / the `host/session-added` frame, and every client-side address (the scope tag, slot store keys, RPC addressing) uses that same id.
- The materialization moment = the instant the user picks a Workspace (cwd settled): the client calls `session.create({workspaceId})` on the spot and receives the complete entity.
- A manual Workspace pick or successful [first-use startup initialization](../feature/2026-09-20-default-workspace.md) establishes cwd before the client calls `session.create({workspaceId})` and receives the complete entity.
- "New Session with no workspace picked" is a **pure view state** (a navigation position) corresponding to no session/scope entity; until the pick, the composer is locked whole (no slash, no plain text).
- A "blank session" is just an ordinary materialized session whose log is still empty; to every Agent-scope plugin on the host (goal/plan/skill/…) it is indistinguishable from any session, so slash/plan are all naturally live.
- A "blank session" is an ordinary materialized session with no turn yet; to Agent-scope plugins on the host (goal/plan/skill/…) it remains an ordinary Session, so slash/plan are naturally live.
### Agent scope: the actx is the sole session carrier in the client-side cordis world
@@ -63,21 +63,21 @@ Session instances share the scope's lifecycle, while the catalog reports discove
A session "materialized but with no first prompt" is governed by the summary-derived bit `blank` (a derived column, not a header field; SessionHeader stays immutable):
- The host criterion: `session.seq === 0` (zero log events = no user message yet). A live session reads `summarize()` straight from memory; a cold session is always `false` — the JSONL provider's lazy-create contract guarantees a never-appended session never enters `persistence.list()`, so blank never touches disk.
- The Host criterion is absence of `turn/start`: the `sessionListMetadata.blank` projection starts true and only that event clears it, so slash-command, configuration, and creation-checkpoint events leave it true even after they reach disk — blank does not mean unpersisted. Missing metadata falls back to `session.seq === 0` for live Sessions and false for cold ones; cold summaries otherwise read cached metadata, leaving cache misses visible as unknown. Reuse acquires the writer under [blank Session acquisition](2026-09-17-process-local-blank-sessions.md).
- The wire carries it in two places: the required `SessionSummary.blank` column, and the required `blank` field on the `host/session-added` frame (always true at creation, letting other tabs enter the same blank-session state into their mirrors).
- The client mirror only lowers, never raises (monotonic), flipped from three sources, all reusing existing wire signals:
- The sender's own tab: the **successful response** to the first `prompt()` flips false (acceptance proves the user/message is already in the host log — this flip is confirmation, not optimism; `onEngaged` synchronously updates the list mirror, converting the current `New Session` row in place to an ordinary title, adding no list row). A rejected first prompt keeps the session blank: aligned with host authority, still shown as `New Session`, keeping its connectWorkspace reuse eligibility while it remains a Workspace member.
- Other tabs: the `host/session-status (running:true)` frame flips it — a blank session never runs, so the first running necessarily means no longer blank;
- Reconnect alignment: `session.list`'s summary.blank is authoritative, so a tab that missed frames aligns naturally on its next pull; a stale blank:true can never mark a converted session back to blank.
- Client display also reflects accepted/running observations, whose retention the [blank rollback decision](../bug-fix/2026-09-15-client-session-blank-reconciliation.md) owns:
- The sender's own tab: the **successful response** to the first `prompt()` converts the current `New Session` row in place, adding no list row — acceptance is display memory, not proof that a turn or user message reached durable history. A rejected first prompt keeps the session blank: aligned with host authority, still shown as `New Session`, keeping its connectWorkspace reuse eligibility while it remains a Workspace member.
- Other tabs: the `host/session-status (running:true)` frame converts it — a blank session never runs, so the first running necessarily means no longer blank;
- Reconnect alignment: a list pull updates Host summaries while the Manager keeps those observations, and a `sessionListMetadata` hint of existing history prevents re-blanking on top of that; an empty-history response cannot mark a converted session back to blank.
- List discipline: the store retains every row; the Workspace browser's grouping, flat view, search, and counts share one visible projection — every non-blank session shows, while blank sessions show only the row retained by the `mainView` source, with its title forced to `New Session`. After a Workspace switch, the old blank entity stays in the mirror but is hidden from the list while the target Workspace's main blank shows; the user-visible surface therefore holds at most one blank row globally.
- The residue ledger takes zero GC: after a refresh, blank sessions come back with the bit intact and are reused on the next same-workspace connect while they remain members, so the ordinary single-tab path keeps at most one per workspace; after a host restart, blanks leave no disk trace and simply evaporate; the extra empty shells from multi-tab races only become non-current hidden rows, digested by later reuse, with no coordination.
- Blank Sessions can persist through creation checkpoints and command events, and have no automatic garbage collection: browser reloads, Host restarts, and Workspace connections reuse eligible blanks after acquiring their writer and skip occupied candidates, while list-loading and multi-tab races can still create additional blank Sessions, whose old logs remain on disk.
### connectWorkspace: the sole entry point of New Session
`workspaces.connectWorkspace(workspaceId): Promise<SessionId>` (owned by WorkspaceRuntime — it holds both the workspace canonical path and the sessions reference):
- The reuse arm: the list mirror is searched for `blank && cwd == workspace.path && sessionIds.includes(id)` — the host's own membership rule, never cwd alone. A cwd match without the account slot (a CLI/TUI session birthed at the host cwd, or a deleted/recreated registration) would open a session no grouping surface can show under this Workspace, so it falls through to the create arm instead (see the [membership reuse fix](../../archived/bug-fix/2026-08-05-workspace-blank-session-reuse-membership.md)); a hit returns that id directly, creating nothing.
- The create arm: on a miss, `session.create({workspaceId})` returns the new id.
- The reuse arm: the list mirror is searched for `blank && cwd == workspace.path && sessionIds.includes(id)` — the host's own membership rule, never cwd alone. A cwd match without the account slot (a CLI/TUI session birthed at the host cwd, or a deleted/recreated registration) would open a session no grouping surface can show under this Workspace, so it falls through to the create arm instead (see the [membership reuse fix](../../archived/bug-fix/2026-08-05-workspace-blank-session-reuse-membership.md)); a hit is adopted with explicit-id `session.create`, which acquires and retains the writer before returning. Only writer contention skips a candidate.
- The create arm: when no candidate can be acquired, `session.create({workspaceId})` returns the new id.
- An unknown workspaceId fails loud (never silently creating somewhere else).
- The resolution guarantee (one contract for both arms): when the promise resolves, the returned id is already in the list store. The view owner then retains it synchronously, so a draft mover can write text through that binding before history readiness without waiting for a notifier flush.
- The caller takes the id and installs a `mainView` reference; sending the first prompt is an ordinary `session.prompt` — the Session already exists, a failure is an ordinary prompt failure, the draft text is still in the machine, and a retry is simply sending again.
@@ -129,12 +129,13 @@ Blank Sessions retain the header's leading and corner slots so navigation contro
| Components receiving wiring-callback bundles (two-layer inject→props pass-down) | The standard-kit channel lets components fetch their own; the public API converges to hooks + stable props |
| Swapping the no-session Hero view for the entire session Conversation | Even with the outer layout unchanged, the Hero, picker, and composer subtrees would remount together, making the whole UI region jump |
| Making InputBar itself `session-maybe` | The input state machine, keyboard command surface, and actions would all have to accept absent values; replacing only the disabled input body keeps optionality at the shell boundary |
| A dedicated conversion frame | `session-status(running:true)` semantically implies conversion (a blank session never runs); adding a frame buys zero information for one more wire type |
| A dedicated conversion frame | Existing acceptance and running signals provide the observations used by the display rule; a new frame is unnecessary for that rule |
## Consequences
- Plugins gain session context isomorphic to the host's: per-session state hangs on the actx and mounts/tears down in one piece with the scope fiber, making leaks structurally impossible; two-session isolation is structurally guaranteed by the scope filter.
- The client object layer converges to a wire mirror: session identity, lifecycle, and capability adjudication all defer to the host entity — the input system (the next layer) always faces a session with a real Agent, and providers like slash/skill uniformly address by sessionId directly.
- Blank-session governance takes zero dedicated mechanisms: state rides one derived bit, visibility rides the unified list projection (only the current blank shows, as `New Session`), reclamation rides lazy persistence's existing contract (evaporation on restart), and the ordinary ceiling rides same-Workspace reuse.
- Blank-session governance takes zero dedicated mechanisms: state rides one derived bit, visibility rides the unified list projection (only the current blank shows, as `New Session`), reuse acquires the persisted writer, and the ordinary ceiling rides same-Workspace reuse. The accepted/running display memory that guards the conversion is Client-local.
- The cost: the id→ctx handoff discipline and provide's Concurrent discipline are conventions rather than type-enforced, pinned by review and tests. The single state axis still withholds machine faces until a Session exists; the [resident conversation shell](../../../../packages/client/ui-conversation/README.md) routes activation to the Workspace picker during that interval.
- Known gaps: approval/question recovery across prune (TODO); model selection returns in live-mutation shape (the host `selectModel` trio is ready-made, its client consumer not yet built).
@@ -26,9 +26,9 @@ web client 只有一张全局会话面:slot 全部从根上下文渲染,插
host 侧 `session.create(workspaceId)` 一体产出 Session + Agent + cwd(作为不可拆分的原子整体);client 侧就是这次出生的镜像——会话行进入 list mirror 的瞬间,client 为它铸 Agent scope(actx + provide + 输入面全套挂上):
- 会话身份自出生即为 host 真身:sessionId 由 `session.create` 响应 / `host/session-added` 帧带来,client 侧一切寻址(scope tag、slot store 键、RPC 地址)用的都是同一个 id。
- 实体化时点 = 用户选定 Workspace(cwd 确定)的瞬间:client 当场调 `session.create({workspaceId})`,拿到完整实体。
- 手动选定 Workspace 或完成[首次使用的启动初始化](../feature/2026-09-20-default-workspace.zh.md) 后,cwd 已确定,client 再调 `session.create({workspaceId})`,拿到完整实体。
- 「New Session 且未选 workspace」是**纯视图态**(一个导航位置),不对应任何会话/scope 实体;选定之前 composer 整体锁死(无 slash、无纯文本)。
- 「空会话」就是一个日志还空着的普通实体化会话;对 host 上所有 Agent-scope 插件(goal/plan/skill(技能)/…)它与任何会话无异,slash/plan 天然全活。
- 「空会话」是尚无轮次的普通实体化会话;对 host 上的 Agent-scope 插件(goal/plan/skill(技能)/…)而言,它仍是普通 Session,因此 slash/plan 自然可用。
### Agent scope:actx 是 client 侧 cordis 世界的唯一会话载体
@@ -63,21 +63,21 @@ Session 实例与 scope 同生命周期;catalog 只报告可发现性,不持
「实体化但无首条提示词」的会话经 summary 派生位 `blank` 治理(派生列而非 header 字段,SessionHeader 保持不可变):
- host 判据:`session.seq === 0`(零日志事件 = 尚无用户消息)。live 会话 `summarize()` 内存直读;cold 会话恒 `false`——JSONL provider 的 lazy-create 约定保证 never-appended 会话不进入 `persistence.list()`,所以 blank 从不落盘。
- Host 判据是没有 `turn/start`:`sessionListMetadata.blank` 投影初始为 true,且只有该事件会清除它,因此 slash 命令、配置事件与创建检查点即使已落盘也不会清除——blank 不代表未持久化。metadata 缺失时,live Session 回退到 `session.seq === 0`,cold Session 回退到 false;cold summary 其余情况读缓存元数据,缓存缺失时以未知状态保留在列表中。复用按[空白 Session 获取](2026-09-17-process-local-blank-sessions.zh.md)规则取得写锁。
- wire 承载两处:`SessionSummary.blank` 必填列;`host/session-added` 帧必填 `blank` 字段(创建时恒 true,供别的 tab 按同一空会话状态入镜像)。
- client 镜像只降不升(单调),三来源翻转,全部复用既有 wire 信号:
- 发送方本地:首次 `prompt()` 的**成功响应**翻 false(受理即证明用户消息已入 host 日志——此点翻转是确证而非乐观;`onEngaged` 同步更新列表镜像,当前 `New Session` 行原地转为普通标题,不新增列表行)。首条提示词被拒则会话保持 blank:与 host 权威对齐、继续显示为 `New Session`、在仍为该工作区成员时保持 connectWorkspace 复用资格。
- 其他端:`host/session-status (running:true)` 帧翻转——blank 会话从不 running,首次 running 必然已非 blank;
- 重连对齐:`session.list` 的 summary.blank 是权威,错过帧的端下次拉取自然对齐;陈旧的 blank:true 不能把已转正的会话重新标回 blank。
- 客户端展示还受已观察到的受理/运行影响,其保留期限由 [blank 回退修复决策](../bug-fix/2026-09-15-client-session-blank-reconciliation.zh.md) 拥有:
- 发送方本地:首次 `prompt()` 的**成功响应**把当前 `New Session` 行原地转正,不新增列表行——受理是展示记忆,不证明轮次或用户消息已写入持久历史。首条提示词被拒则会话保持 blank:与 host 权威对齐、继续显示为 `New Session`、在仍为该工作区成员时保持 connectWorkspace 复用资格。
- 其他端:`host/session-status (running:true)` 帧转正——blank 会话从不 running,首次 running 必然已非 blank;
- 重连对齐:列表拉取更新 Host 摘要,Manager 则保留这些观察;在此基础上,`sessionListMetadata` 提示已有历史也会阻止回退 blank,因此空历史响应不能把已转正的会话标回 blank。
- 列表纪律:store 保留全部行;Workspace browser 的分组、平铺、搜索和计数共用同一可见投影——所有非 blank 会话都显示,blank 会话只显示由 `mainView` 来源持有的一行,并强制标题为 `New Session`。切换 Workspace 后,旧 blank 实体仍在镜像中但从列表隐藏,目标 Workspace 的主 blank 显示;因此用户可见面全局至多一条 blank 行。
- 残留账零 GC:刷新后 blank 会话带位回来,下次同 workspace 且仍为成员时复用,普通单端路径使每个 workspace 至多保留一个;host 重启后 blank 无盘痕自然蒸发;多 tab 竞态多出的空壳只会成为非 current 隐藏行,后续复用消化,不做协调。
- blank Session 可以通过创建检查点与命令事件持久化,且没有自动垃圾回收:浏览器刷新、Host 重启与工作区连接会在取得写锁后复用符合条件的空白会话并跳过被占用的候选;但列表加载与多 tab 竞态仍可能创建额外的 blank Session,其旧日志保留在磁盘上。
### connectWorkspace:New Session 的唯一入口
`workspaces.connectWorkspace(workspaceId): Promise<SessionId>`(归属 WorkspaceRuntime——它同时持有 workspace 规范 path 与 sessions 引用):
- 复用臂:list mirror 中找 `blank && cwd == workspace.path && sessionIds.includes(id)`——host 自己的成员规则,绝不只按 cwd。没有账户槽位的 cwd 匹配(CLI(命令行界面)/TUI 在 host cwd 创建的会话,或已删除/重建的注册)会打开一个任何分组表面都无法显示在该工作区下的会话,因此落到新建臂(见[成员复用修复](../../archived/bug-fix/2026-08-05-workspace-blank-session-reuse-membership.md));命中直接返回该 id,不新建。
- 新建臂:未命中则 `session.create({workspaceId})`,返回新 id。
- 复用臂:list mirror 中找 `blank && cwd == workspace.path && sessionIds.includes(id)`——host 自己的成员规则,绝不只按 cwd。没有账户槽位的 cwd 匹配(CLI(命令行界面)/TUI 在 host cwd 创建的会话,或已删除/重建的注册)会打开一个任何分组表面都无法显示在该工作区下的会话,因此落到新建臂(见[成员复用修复](../../archived/bug-fix/2026-08-05-workspace-blank-session-reuse-membership.md));命中后通过显式 ID 的 `session.create` 收养,在返回前取得并持续持有写锁。仅写锁争用会跳过候选。
- 新建臂:没有可获取的候选时,`session.create({workspaceId})`,返回新 id。
- 未知 workspaceId fail loud(不静默创建到别处)。
- 解析保证(两臂同约定):promise resolve 时返回的 id 已在 list store。视图 owner 随后同步 retain,因此 draft 搬运方可以在历史就绪前通过该 binding 写入文本,无需等待 notifier flush。
- 调用方拿 id 安装一份 `mainView` reference;首条提示词发送就是普通 `session.prompt`——Session 本来就在,失败即普通提示词失败,draft 文本还在 machine 里,重试即再次发送。
@@ -129,12 +129,13 @@ blank Session 保留 header 的 leading 与 corner slot,让右侧栏展开入
| 组件收 wiring 回调包(inject→props 两层下传) | 标准件通道让组件自取;公共 API 收敛为 hooks + 稳定 props |
| Hero 无会话视图与会话 Conversation 整支互换 | 即使外层 layout 不变,Hero、picker 与 composer 子树仍会一起重建,界面产生整块抖动 |
| 让 InputBar 自身变成 `session-maybe` | 输入状态机、键盘命令面与动作都被迫接受缺省值;只替换 disabled 输入体能把可选性留在外壳边界 |
| 专用「转正」帧 | `session-status(running:true)` 语义蕴含转正(blank 会话从不 running),加帧是 wire 多一型换零信息 |
| 专用「转正」帧 | 既有受理和运行信号已经提供展示规则所需的观察;该规则不需要新增帧 |
## 后果
- 插件获得与 host 同构的会话上下文:逐会话状态挂 actx、随 scope fiber 一次拆装,泄漏结构性不可能;双会话隔离由 scope filter 结构性保证。
- client 对象层收敛为 wire 镜像:会话身份、生命周期、能力判别全部以 host 实体为准——输入体系(下一层)面对的永远是「有真 Agent 的会话」,slash/skill 等提供方一律以 sessionId 直接寻址。
- 空会话治理零专用机制:状态靠一个派生位,可见性靠统一列表投影(仅 current blank 以 `New Session` 展示),回收靠 lazy persistence 的既有约定(重启蒸发),常规上限靠同 Workspace 复用。
- 空会话治理零专用机制:状态靠一个派生位,可见性靠统一列表投影(仅 current blank 以 `New Session` 展示),复用时获取持久化写锁,常规上限靠同 Workspace 复用。转正所依赖的受理/运行展示记忆仅保留在客户端。
- 代价:id→ctx 换乘纪律、provide 的 Concurrent 纪律都是约定而非类型强制,靠 review 与测试钉住。单一状态轴仍会在 Session 存在前隐藏 machine face;这段时间内,[常驻会话壳](../../../../packages/client/ui-conversation/README.zh.md)会把激活操作转到 Workspace picker。
- 已知欠账:approval/question 跨 prune 恢复(TODO);模型选择以 live-mutation 形状回归(host `selectModel` 三件套现成,其 client 消费方尚未构建)。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-28-identified-immutable-message-values.md
2026-07-28-identified-immutable-message-values.md: 9d2b22a5a1fc87ea7b0ff8eb24b2e8c4b86543e1
2026-07-28-identified-immutable-message-values.zh.md: 6b36cfec0a24196ca526b0ffb21180967aa640eb
2026-07-28-identified-immutable-message-values.md: 73c9c14479117cf654d10b3da23d155cca77054d
2026-07-28-identified-immutable-message-values.zh.md: 7a5b9d6524beff01ac8a9e2020f75093271b7ab3
@@ -16,7 +16,7 @@ This made identity a routing side effect rather than a message invariant. Produc
`createMessage(input)` is the canonical role-generic creation boundary. It mints a `MessageId`, detaches the supplied role, content, and source, and deep-freezes the complete value before returning it. `createUserMessage({ content, source })` fixes the user role for prompt and context producers. `createAssistantMessage({ content, source })` fixes both the assistant role and the model source kind, so model-output producers supply only content plus provider, model, and optional replay state. All creation helpers exclude an input id so callers cannot accidentally present creation as import. `freezeMessage(message)` is the separate import or transformation boundary: it detaches and deep-freezes a message whose identity already exists, without minting a replacement.
The message helpers live in `dsh-llm` beside the base message vocabulary because their complete contracts depend only on that vocabulary. They use `dsh-brand`'s stateless `brandString()` constructor for `MessageId` and `dsh-util-values`'s shared `deepFreeze()` implementation after detaching input with `structuredClone()`. `createToolResultMessage()` belongs with the other creation helpers: it couples a tool call id to the exact user-role tool-result block and source without depending on session state or events. `dsh-session` consumes complete messages rather than owning their construction.
The message helpers live in `dsh-llm` beside the base message vocabulary because their complete contracts depend only on that vocabulary. They use `dsh-brand`'s stateless `brandString()` constructor for `MessageId` and `dsh-util-values`'s shared `deepFreeze()` implementation after detaching input with `structuredClone()`. `createToolResultMessage()` belongs with the other creation helpers: it couples a tool call id to a tool-role message carrying the result content, error state, and source without depending on session state or events. `dsh-session` consumes complete messages rather than owning their construction.
The `Agent` interface accepts a complete `UserMessage` through `followup`, `steer`, and `inject`. These operations never allocate or return identity; they freeze an imported value whose id the caller already holds. Inbox claims and `agent/pre-step` receive that message directly. A content rewrite creates a frozen replacement with the same id, while an additional context is a separately created `UserMessage` with its own id.
@@ -16,7 +16,7 @@ harness 曾存在多种形似消息的表示,各自采用不同的标识规则
`createMessage(input)` 是角色通用的规范创建边界。它会生成 `MessageId`,将传入的角色、内容和来源与调用方对象解除引用关系,并在返回完整值前将其深度冻结。`createUserMessage({ content, source })` 为提示词和上下文生产方固定 user 角色。`createAssistantMessage({ content, source })` 同时固定 assistant 角色与模型来源类别,因此模型输出生产方只需提供内容,以及提供方、模型和可选的回放状态。所有创建辅助函数的输入都不包含 id,因此调用方不会意外地把新消息的创建伪装成已有消息的导入。`freezeMessage(message)` 是独立的导入或转换边界:它会将已有标识的消息与调用方对象解除引用关系并深度冻结,不会生成替代标识。
消息辅助函数位于基础消息词汇旁的 `dsh-llm` 中,因为它们的完整约定只依赖该词汇。它们使用 `dsh-brand` 的无状态 `brandString()` 构造函数生成 `MessageId`,并在通过 `structuredClone()` 分离输入后使用 `dsh-util-values` 的共享 `deepFreeze()` 实现。`createToolResultMessage()` 与其他创建辅助函数同属此处:它将工具调用 id 与确切的 user-role 工具结果块及来源耦合起来,不依赖会话状态或事件。`dsh-session` 只消费完整消息,不负责构造它们。
消息辅助函数位于基础消息词汇旁的 `dsh-llm` 中,因为它们的完整约定只依赖该词汇。它们使用 `dsh-brand` 的无状态 `brandString()` 构造函数生成 `MessageId`,并在通过 `structuredClone()` 分离输入后使用 `dsh-util-values` 的共享 `deepFreeze()` 实现。`createToolResultMessage()` 与其他创建辅助函数同属此处:它将工具调用 id 与携带结果内容、错误状态及来源的 tool-role 消息耦合起来,不依赖会话状态或事件。`dsh-session` 只消费完整消息,不负责构造它们。
`Agent` 接口通过 `followup`、`steer` 和 `inject` 接收完整的 `UserMessage`。这些操作绝不会分配或返回标识;它们会冻结导入的值,而调用方已经持有该值的 id。inbox 领取和 `agent/pre-step` 会直接接收该消息。改写内容时会创建具有相同 id 的冻结替代值,而每个附加上下文都是单独创建的 `UserMessage`,拥有自己的 id。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.md
2026-07-29-dsh-source-launch-tsx-esm.md: 5401b0257faa69eaf76b11e7b51d06afd7ec2b8c
2026-07-29-dsh-source-launch-tsx-esm.zh.md: f32fa3ece91bf7b9829d7012ceb090cabaa833a9
2026-07-29-dsh-source-launch-tsx-esm.md: 700577cb9b137d67f20baeb639f99f4c0877011d
2026-07-29-dsh-source-launch-tsx-esm.zh.md: 4d322d3dcf49442ee62f82203f17bd2ab16467fe
@@ -14,11 +14,13 @@ Startup latency also mattered: the off-thread `module.register()` hooks worker s
## Decision
The `dsh` TUI, Web, and headless source launches run `node --import tsx/esm`: tsx's ESM-only hook owns both TypeScript transformation and tsconfig `paths` projection. The root `dsh` script uses that vector directly from the repository root; artifact generation is a separate operation under the [source-launch/build separation decision](../../archived/simplification/2026-08-12-separate-source-launch-from-build.md). The CJS hook stays off because the CLI source graph is ESM-only; measured runtime launch to the TUI banner is ~0.7s versus ~1.1s under the full tsx default and ~0.75s under the removed native chain.
The `dsh` CLI source launches run `node --import tsx/esm`: tsx's ESM-only hook owns both TypeScript transformation and tsconfig `paths` projection. The root `dsh` script uses that vector directly from the repository root; artifact generation is a separate operation under the [source-launch/build separation decision](../../archived/simplification/2026-08-12-separate-source-launch-from-build.md). The CJS hook stays off because the CLI source graph is ESM-only; the implementation-time measurement to the TUI banner was ~0.7s versus ~1.1s under the full tsx default and ~0.75s under the removed native chain.
`scripts/tspath-loader.ts` and `apps/cli/src/tsconfig-paths-loader.ts` are deleted. With them went the loader's runtime rule of mapping a workspace import only for declared runtime dependencies — tsx applies the `paths` map unconditionally. Declaration completeness now rests on the static gates alone: `verify-cordis-config` for configured bare plugins, and workspace constraints for manifests. (That runtime rule found real bugs: `dsh-plan-mode` and `dsh-tool-jobs` imported `@deepseek-ai/dsh-llm` while declaring it only in devDependencies; since fixed.)
tsx owns workspace `paths` mapping without checking whether the importer declares each package as a runtime dependency. Declaration completeness rests on the static gates: `verify-cordis-config` for configured bare plugins, and workspace constraints for manifests. The removed repo-owned paths loaders enforced those declarations at runtime and caught imports of `@deepseek-ai/dsh-llm` declared only in devDependencies; tsx does not provide that check.
The node-compat CI matrix (Node 22.19 and 26) gains `dsh-source-launch-smoke` (`apps/cli/tests/source-launch.compat.spec.ts`): a keyless piped-stdio launch of the exact production runtime vector asserting the non-zero-exit TTY refusal. Any future Node change to module hooks or TypeScript handling turns this gate red instead of breaking developers' `pnpm dsh`.
tsx skips the paths map when the importer URL contains `/node_modules/`. The [runtime resolution](2026-09-09-profile-resolution-generations.md) therefore records real declaring-package anchors, including recursive dependencies. This lets workspace fallback imports resolve to `src/` consistently, rather than mixing built `lib/` providers with source callers whose module-local Symbols differ. Packages without a workspace mapping retain ordinary export resolution.
The node-compat CI matrix runs `dsh-source-launch-smoke` (`apps/cli/tests/source-launch.compat.spec.ts`) without a build: keyless launches assert the required-profile diagnostic and profile dependency resolution without mixed Tools/AgentLoop `src/` and `lib/` instances. The source and built-bin suites share ordinary-directory and npm-link profile cases; the latter launches plain Node against built exports. The required build-backed smoke also runs `apps/cli/tests/profiles/headless/tests/source-tool.built.e2e.ts`: it checks real headless tool dispatch through tsx after the native addon and generated typert contributors are available, while still requiring core workspace modules to load from source. Future changes to module hooks or TypeScript handling are checked through these real entry paths.
## Alternatives considered
@@ -35,4 +37,4 @@ The node-compat CI matrix (Node 22.19 and 26) gains `dsh-source-launch-smoke` (`
- One launch vector across the whole engines range, including future Node lines that change native TypeScript support; the smoke gate enforces it per matrix line.
- TypeScript transformation is delegated to tsx/esbuild again, reversing the prior note's goal of proving Node-native transformation; that goal is unreachable while vendored sources use non-erasable syntax and Node ships no transform mode.
- The runtime declared-dependency enforcement in source launches is gone; undeclared workspace imports now surface only through static gates or built-mode resolution failures.
- Runtime launch improves ~0.4s over the full tsx default; ACP keeps `--import tsx` because its graph was not audited for CJS-hook dependence and its launch latency is not on the interactive path.
- Every CLI source profile uses the same ESM-only hook, including ACP. Workspace runtime entries reached by this launch must expose ESM exports; the launcher does not install a CJS TypeScript transform hook.
@@ -14,11 +14,13 @@ Status: implemented
## 决策
`dsh` 的 TUI、Web 与无头源码启动运行 `node --import tsx/esm`:由 tsx 的 ESM-only 钩子同时负责 TypeScript 转换与 tsconfig `paths` 投影。根目录的 `dsh` 脚本直接从仓库根目录使用同一启动方式;产物生成是独立操作,由[源码启动与构建分离决策](../../archived/simplification/2026-08-12-separate-source-launch-from-build.md)规定。CJS 钩子保持关闭,因为 CLI(命令行界面)源码图是纯 ESM;实测运行时启动至 TUI banner 耗时约 0.7s,对比完整 tsx 默认形态约 1.1s、已移除的原生链约 0.75s。
`dsh` CLI(命令行界面)源码启动运行 `node --import tsx/esm`:由 tsx 的 ESM-only 钩子同时负责 TypeScript 转换与 tsconfig `paths` 投影。根目录的 `dsh` 脚本直接从仓库根目录使用同一启动方式;产物生成是独立操作,由[源码启动与构建分离决策](../../archived/simplification/2026-08-12-separate-source-launch-from-build.md)规定。CJS 钩子保持关闭,因为 CLI 源码图是纯 ESM;实现时测得启动至 TUI banner 耗时约 0.7s,对比完整 tsx 默认形态约 1.1s、已移除的原生链约 0.75s。
`scripts/tspath-loader.ts` 与 `apps/cli/src/tsconfig-paths-loader.ts` 已删除。随之消失的还有该 loader「仅为已声明运行时依赖映射 workspace import」的运行时规则——tsx 无条件应用 `paths` 映射。声明完整性现在仅由静态门禁保障:配置的裸插件走 `verify-cordis-config`,manifest(元数据清单)走 workspace constraints。(该运行时规则确实发现过真实缺陷:`dsh-plan-mode` 与 `dsh-tool-jobs` 导入 `@deepseek-ai/dsh-llm` 却只声明在 devDependencies;后已修复。)
tsx 负责 workspace `paths` 映射,不检查导入方是否把每个包声明为运行时依赖。声明完整性由静态门禁保障:配置的裸插件走 `verify-cordis-config`,manifest(元数据清单)走 workspace constraints。已移除的仓库自有 paths loader 曾在运行时强制这些声明,并发现过将 `@deepseek-ai/dsh-llm` 仅声明在 devDependencies 中的 import;tsx 不提供这项检查。
node-compat CI 矩阵(Node 22.19 与 26)新增 `dsh-source-launch-smoke`(`apps/cli/tests/source-launch.compat.spec.ts`):以精确的生产运行时启动向量做 keyless 管道 stdio 启动,断言进程会因 TTY 拒绝而以非零状态退出。未来 Node 对模块钩子或 TypeScript 处理的任何改动都会让该门禁变红,而不是破坏开发者的 `pnpm dsh`。
导入方 URL 含有 `/node_modules/` 时,tsx 会跳过 paths 映射。因此,[runtime resolution](2026-09-09-profile-resolution-generations.zh.md)记录声明包的真实锚点,也覆盖递归依赖。这让 workspace fallback import 一致解析到 `src/`,避免混用构建后的 `lib/` 提供方与模块本地 Symbol 不同的源码调用方。没有 workspace 映射的包仍使用普通导出解析。
node-compat CI 矩阵在不执行 build 的情况下运行 `dsh-source-launch-smoke`(`apps/cli/tests/source-launch.compat.spec.ts`):无密钥启动断言必需 profile 的诊断,以及不混用 Tools/AgentLoop `src/` 与 `lib/` 实例的 profile 依赖解析。源码与构建入口测试共享普通目录和 npm-link profile 用例;后者通过普通 Node 加载构建后的导出。必跑的 build-backed smoke 还运行 `apps/cli/tests/profiles/headless/tests/source-tool.built.e2e.ts`:在 native addon 和生成的 typert 贡献文件就绪后,通过 tsx 检查真实 headless 工具派发,同时仍要求核心 workspace 模块从源码加载。模块钩子或 TypeScript 处理的后续变化由这些真实入口检查。
## 备选方案
@@ -35,4 +37,4 @@ node-compat CI 矩阵(Node 22.19 与 26)新增 `dsh-source-launch-smoke`(`
- 整个 engines 范围(包括未来改变原生 TypeScript 支持的 Node 版本线)只有一个启动向量;冒烟门禁按矩阵行强制执行。
- TypeScript 转换重新委托给 tsx/esbuild,逆转了前一篇 Agent Note「证明 Node 原生转换可用」的目标;在 vendor 源码使用不可擦除语法且 Node 不再提供 transform 模式的情况下,该目标不可达。
- 源码启动中的运行时依赖声明强制不复存在;未声明的 workspace import 现在只能通过静态门禁或构建模式的解析失败暴露。
- 运行时启动相比完整 tsx 默认形态快约 0.4s;ACP(Agent Client Protocol)保留 `--import tsx`,因为它的依赖图尚未就 CJS 钩子依赖性做审计,且其启动延迟不在交互路径上。
- 每个 CLI 源码 profile 都使用相同的 ESM-only 钩子,包括 ACP(Agent Client Protocol)。该启动方式访问的 workspace 运行时入口必须提供 ESM 导出;启动器不安装 CJS TypeScript 转换钩子。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md
2026-08-02-typert-remote-method-calls.md: b599b3f9a353738bbb44a4da9b5a72be8309e2fb
2026-08-02-typert-remote-method-calls.zh.md: 64dc84f0ae07b10d50452ef7e36c30db102953cf
2026-08-02-typert-remote-method-calls.md: 2a12aaf78f638d8af786d2ca817a3fee07a2c614
2026-08-02-typert-remote-method-calls.zh.md: bf4a7b0179b4dc878a0a53c7076c02e6f1bf0273
@@ -31,8 +31,8 @@ The Remote consumer projection contains `.d.ts`, `.d.ts.map`, and `.js` files. T
| `@deepseek-ai/dsh-typert-protocol` | Declares only the minimal `ctx.typert` protocol | `TypertRemoteService`, decorators, binding fallback, descriptors, lookup/Context, and the Remote map; no dependency on the compiler, Zod, Connection, or Browser |
| Typert registry | `ctx.typert` | Separately stores reflection for the current environment, imported Remote contributions, lookup providers, and Context providers |
| Typert generator/loader | No new business service | Generates three kinds of `lib` artifacts from the Host/Client Programs and registers the current environment's artifacts with `ctx.typert` |
| API Gateway's Host face | `ctx.typertGateway` | Associates Host definitions with live Services, decodes parameters, resolves receivers, and invokes methods |
| Connection | `ctx.connection` | Exclusively owns the HTTP Server/future WebSocket, the shared `/api` route, RPC envelope, rpcId, serialization, trust, error transport, Typert interception, and owner-registered exact Fetch routes on the same channel |
| API Gateway's Host face | `ctx.typertGateway` | Associates Host definitions with live Services, decodes parameters, resolves receivers, invokes methods, and projects unary results into carrier-neutral fields and attachments |
| Connection | `ctx.connection` | Exclusively owns the HTTP Server/future WebSocket, the shared `/api` route, RPC envelope, rpcId, physical serialization, trust, error transport, Typert interception, and owner-registered exact Fetch routes on the same channel |
| API Gateway's Client face | `ctx.remote`, `ctx.remote.<namespace>` | Mounts Remote contributions, materializes each namespace as a traced `remote.<namespace>` child Service, and delegates canonical calls to `ctx.connection.rpc` |
| API Remotes | No new service | Owns Host Agent/Session lookup policy and serves as the only Client business facade, selecting and mounting `/remote` contributions while exposing the selected API declarations |
| Agent/Session owning packages | Existing domain services | Provide both static interface merges and runtime lookup/Context providers |
@@ -181,7 +181,7 @@ Consequently, `SessionId`, the Agent wire ID, the request, and the result all re
Remote methods themselves use declaration-map navigation. Typert anchors `InvocationModel.location` to the decorated Host method-name token and emits a source-map segment on the corresponding property of the namespace interface. For an adapter-backed endpoint, after the TypeScript editor resolves `ctx.remote.models.list` to its generated declaration, `typert.remote-client.d.ts.map` takes it to the Host Service's `remoteExportList` entry point. That entry point explicitly calls the existing, unrenamed `list()` method; the map does not misidentify the decorator, class, or full signature as the method definition.
Typert generates a wire Zod codec for the same symbol key. The Host Gateway uses parameter and identity codecs to validate input. Client Remote trusts its generated TypeScript arguments and successful Host results instead of executing invocation codecs. If a complex type cannot produce a strict codec, the LIB build fails instead of degrading to `unknown` or unchecked JSON.
Typert generates a wire Zod codec for the same symbol key. The Host Gateway uses parameter and identity codecs to validate input. Client Remote trusts its generated TypeScript arguments and JSON success results. Binary success results use their generated codec after multipart decoding, as specified by [binary Remote transfer](2026-09-17-workspace-file-binary-transfer.md). If a complex type cannot produce a strict codec, the LIB build fails instead of degrading to `unknown` or unchecked JSON.
Named business types referenced by Remote methods must be exported from public, type-only subpaths. If the only reachable entry also imports Host Services, Cordis `Context` merges, or Host-only implementations, the build fails and requires the business package to provide a safe type entry. Primitives, literals, and simple compositions explicitly supported by Typert need no additional names.
@@ -514,7 +514,7 @@ Canonical public types require business DTOs to have type-only entries, which ma
Type imports and runtime contributions have different effects. `import type {}` extends only the static Remote surface. If a real calling environment omits the value contribution, the Client Remote Service must fail with an explicit "Remote not mounted" error.
Generated Host and Client artifacts carry matching Zod factories, but Client Remote does not materialize invocation schemas. Canonical symbol keys, the same generated model, and Host wire validation keep the two sides aligned without comparing schema object identities across realms.
Generated Host and Client artifacts carry matching Zod factories; Client Remote materializes only binary result schemas. Canonical symbol keys, the same generated model, and Host wire validation keep the two sides aligned without comparing schema object identities across realms.
A consumer may import a Remote contract that is not currently mounted on the Host. The types mean "this protocol capability was selected by the consumer," not that a corresponding Service currently exists in the target process; an unavailable endpoint must fail explicitly at runtime.
@@ -31,8 +31,8 @@ Remote 消费端投影同时包含 `.d.ts`、`.d.ts.map` 和 `.js`。`.d.ts` 只
| `@deepseek-ai/dsh-typert-protocol` | 只声明 `ctx.typert` 的最小协议 | `TypertRemoteService`、decorator、binding 回退、descriptor、lookup/Context 和 Remote map;不依赖 compiler、Zod、Connection 或 Browser |
| Typert registry | `ctx.typert` | 分开保存当前环境 reflection、导入的 Remote contribution、lookup provider 和 Context provider |
| Typert generator/loader | 无新增业务服务 | 从 Host/Client Program 生成三类 `lib` 产物,并把当前环境产物注册到 `ctx.typert` |
| API Gateway 的 Host face | `ctx.typertGateway` | 关联 Host definition 与活 Service,解码参数、解析 receiver 并调用方法 |
| Connection | `ctx.connection` | 独占 HTTP Server/未来 WebSocket、共享 `/api` route、RPC envelope、rpcId、序列化、trust、错误传输、Typert 拦截,以及各 owner 在同一 channel 上注册的精确 Fetch route |
| API Gateway 的 Host face | `ctx.typertGateway` | 关联 Host definition 与活 Service,解码参数、解析 receiver、调用方法,并将一元结果投影为载体无关的字段与附件 |
| Connection | `ctx.connection` | 独占 HTTP Server/未来 WebSocket、共享 `/api` route、RPC envelope、rpcId、物理序列化、trust、错误传输、Typert 拦截,以及各 owner 在同一 channel 上注册的精确 Fetch route |
| API Gateway 的 Client face | `ctx.remote`、`ctx.remote.<namespace>` | mount Remote contribution,把每个 namespace 实体化为可追踪的 `remote.<namespace>` 子 Service,并把规范调用交给 `ctx.connection.rpc` |
| API Remotes | 无新增服务 | 负责 Host Agent/Session lookup 策略,并作为 Client 业务的唯一 facade,选择并挂载 `/remote` contribution,同时暴露所选 API 声明 |
| Agent/Session owning 包 | 既有领域服务 | 同时提供静态 interface merge 与运行时 lookup/Context provider |
@@ -181,7 +181,7 @@ import type { CreateGoalRequest, CreateGoalResult } from '@deepseek-ai/dsh-goal/
Remote 方法本身使用 declaration map 导航。Typert 把 `InvocationModel.location` 固定在 Host 被装饰方法的方法名 token,并在 namespace interface 的对应属性上写入 source-map segment。对于由适配器支撑的 endpoint,TypeScript editor 从 `ctx.remote.models.list` 取得生成 declaration 后,再沿 `typert.remote-client.d.ts.map` 跳到 Host Service 的 `remoteExportList` 远程出口。该出口继续显式调用不改名的存量 `list()`,map 不把 decorator、class 或整个签名误当成方法定义位置。
Typert 为同一 symbol key 生成 wire Zod codec。Host Gateway 用参数与 identity codec 校验输入;Client Remote 信任生成的 TypeScript 参数与成功的 Host 结果,不执行调用 codec。复杂类型无法生成严格 codec 时,LIB 构建失败,不降级为 `unknown` 或无校验 JSON。
Typert 为同一 symbol key 生成 wire Zod codec。Host Gateway 用参数与身份 codec 校验输入。Client Remote 信任生成的 TypeScript 参数与 JSON 成功结果。二进制成功结果在 multipart 解码后执行生成的结果 codec,具体由[二进制 Remote 传输](2026-09-17-workspace-file-binary-transfer.zh.md)规定。复杂类型若无法生成严格 codec,LIB 构建失败,不降级为 `unknown` 或未校验 JSON。
Remote 方法引用的命名业务类型必须从纯类型公共 subpath 导出。如果唯一可达入口会带入 Host Service、Cordis `Context` merge 或 Host-only 实现,构建失败并要求业务包提供安全的类型出口。原始值、字面量和 Typert 明确支持的简单组合不需要额外命名。
@@ -514,7 +514,7 @@ SRC 弱 descriptor 不验证普通 JSON 内部结构。Host Remote 签名变化
类型 import 与运行时 contribution 是两种不同效果。`import type {}` 只扩展静态 Remote surface;真实调用环境遗漏 value contribution 时,Client Remote Service 必须以明确的「Remote 未挂载」错误失败。
生成的 Host 与 Client 产物携带匹配的 Zod factory,但 Client Remote 不实例化调用 schema。规范 symbol key、同一生成模型和 Host wire 校验让两侧保持一致,而无需跨 realm 比较 schema 对象 identity。
生成的 Host 与 Client 产物携带匹配的 Zod factory;Client Remote 仅实例化二进制结果 schema。规范 symbol key、同一生成模型和 Host wire 校验让两侧保持一致,而无需跨 realm 比较 schema 对象 identity。
消费端可以导入 Host 当前未挂载的 Remote contract。类型表示「该协议能力已被消费端选择」,不保证目标进程当前存在对应 Service;运行时 endpoint 不可用必须明确失败。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-04-configuration-source-ownership.md
2026-08-04-configuration-source-ownership.md: 6dea1f08a9f2a0319f894a504082ff70bbe1118f
2026-08-04-configuration-source-ownership.zh.md: 1ccf2c266c3e52f2f10d3bceb3b1505ae6266b15
2026-08-04-configuration-source-ownership.md: b8c146ed76e500fcde3affd92bc5188b83083223
2026-08-04-configuration-source-ownership.zh.md: 22afc15aec97a40829655fcb8179ad90a369bf0c
@@ -4,6 +4,8 @@ Status: implemented
English | [中文](2026-08-04-configuration-source-ownership.zh.md)
The non-secret settings tier below is superseded by [profile-owned live configuration](2026-09-19-profile-owned-live-configuration.md). Credential ordering and environment restrictions remain current.
## Problem
`$DSH_HOME/.env` had just [become an ordinary environment layer](../../archived/architecture/2026-08-04-credentials-yaml-and-user-environment-layer.md), which left the harness resolving user-facing values from a flattened `process.env` that could no longer say where a value came from. Three consequences followed.
@@ -4,6 +4,8 @@ Status: implemented
[English](2026-08-04-configuration-source-ownership.md) | 中文
下文的非秘密 settings 层已由 [profile 所属实时配置](2026-09-19-profile-owned-live-configuration.zh.md) 替代。凭据优先级与环境限制仍然适用。
## Problem
`$DSH_HOME/.env` 刚刚[变成普通环境层](../../archived/architecture/2026-08-04-credentials-yaml-and-user-environment-layer.md),这使得 harness 解析面向用户的值时面对的是一个压平的 `process.env`,再也说不清某个值来自哪里。由此产生三个后果。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-05-profile-plugin-bundles.md
2026-08-05-profile-plugin-bundles.md: 48786a9c1ccaceb5f16c9eefed01e556cf759e6d
2026-08-05-profile-plugin-bundles.zh.md: c88cbf98e4276549a6fae6a5b1253d3d838a64ca
2026-08-05-profile-plugin-bundles.md: 0607f1c9a2414cc16515967b4520da8a325853fd
2026-08-05-profile-plugin-bundles.zh.md: 9d25d3bea6337c472d161c721e44a66dca8470c9
@@ -14,14 +14,14 @@ Everything becomes a **profile**: a directory `$DSH_HOME/profiles/<name>` with a
The default Profile templates use `@deepseek-ai/dsh-base` as the shared core for `web`, `headless`, `sdk`, and `acp`, with one mode bundle above it. The [standalone `sdk-minimal` profile](../../../../packages/bundle/sdk-minimal/README.md) instead lists one bundle that owns its complete explicit tree. Generic `dsh --profile <name>` hands its remaining arguments to that profile's command-line startup row: Web owns its flag family, headless owns its task positional, and the protocol profiles accept no app options. Patch overlays use launcher-owned `--patch`. A new, non-shipped target can use `--from-default-profile <template>` to copy one default template's bundle list and patch-reload policy before boot or config dump. This creates an independent profile with empty dependencies and an empty user patch: it neither reads a local profile named by the template nor records an inheritance relationship. The launcher claims the complete target directory exclusively, so existing state and concurrent creators fail without modification. `dsh plugin --profile <name> <args...>` is a thin pnpm forwarder that initializes a base-backed profile and reconciles `dsh.profile.bundles` with installed bundle declarations; a package without a bundle declaration remains a plain dependency. [Headless as a direct core entry point](../../archived/architecture/2026-08-09-headless-direct-core-entry-point.md) owns the headless composition contract.
Resolution is two-anchored by construction: `dsh.profile.bundles` names resolve from the dsh installation first, then the profile directory, so in-box bundles always come from the same installation as the running `dsh` and pnpm never manages them. Bare plugin names in patch rows use the [immutable profile resolution generation](2026-09-09-profile-resolution-generations.md), which applies the same installation-first and ordered-bundle rules in memory; retained link and dual modes can materialize the same result.
Resolution is two-anchored by construction: `dsh.profile.bundles` names resolve from the dsh installation first, then the profile directory, so in-box bundles always come from the same installation as the running `dsh` and pnpm never manages them. Bare plugin names in patch rows use the [immutable runtime resolution](2026-09-09-profile-resolution-generations.md), which applies installation-first and ordered-bundle rules in memory and expands dependencies from real package directories.
Two supporting refactors: the webserver's built-in static dist serving became the single-owner **fallback seat** (`registerFallback`/`applyIndexTaps`), with the SPA server extracted to `@deepseek-ai/dsh-host-frontend-static` so the web bundle owns its dist as composition, not launcher code; and the personal-overlay machinery of the [dsh CLI personal-config decision](../../archived/feature/2026-07-20-dsh-cli-personal-config.md) (`loadPersonalPatches`, `$DSH_HOME/config.yaml`) was retargeted to the per-profile and home-level `cordis.patch.yml` layers (`loadOptionalPatches`, `watchUserPatches` taking a filename), superseding that note's entry modes and file location while keeping its Harness-home root, patch semantics, and fail-loud parsing.
## Alternatives considered
- **Dependency-scan plus partial `patchOrder`** (the original sketch): scanning `dependencies` for bundles and ordering unlisted ones alphabetically has two sources of truth and an implicit tie-break; one explicit ordered `dsh.profile.bundles` list is smaller and fully deterministic. A raw `pnpm add` inside the profile installs a library without activating any patch — explicit, no spooky scan.
- **`link:` entries for in-box bundles**: pnpm cannot version, install, or update a `link:` into the installation, it embeds a machine path in a user file, and it breaks when the installation moves. The two-anchor resolution plus healed symlink fallback gives the same guarantee ("bundles come from the installation") without ceremony.
- **`link:` entries for in-box bundles**: pnpm cannot version, install, or update a `link:` into the installation, it embeds a machine path in a user file, and it breaks when the installation moves. Two-anchor resolution plus runtime fallback keeps bundles installation-owned without persisting those links.
- **A pre-boot `context` module in the bundle manifest** for boot-time values (dist path, flag facts): rejected in favor of pure plugins — the glue is ordinary rows and app-owned startup services, so the composition stays fully dumpable and the manifest stays data-only. The launcher-provided host slots (`ctx.cmdlineArgs`, `ctx.appExit`, and the environment snapshot) are provided in `boot()`'s `prepare` hook, before any config-tree entry mounts.
- **Transitive bundle auto-application**: only direct `dsh.profile.bundles` entries contribute layers; a meta-bundle wanting to re-export another bundle's patch must do so explicitly in its own patch file.
- **Dynamic template inheritance or cloning a local profile**: recording a parent would require merge and upgrade rules for bundle membership, dependencies, and user patches, while copying local state would duplicate machine-specific choices. Template-based creation copies only installation-owned defaults once.
@@ -14,14 +14,14 @@ Status: implemented
默认 Profile 模板为 `web`、`headless`、`sdk` 与 `acp` 使用 `@deepseek-ai/dsh-base` 作为共享核心,并在其上叠加一个模式组合包。[独立 `sdk-minimal` profile](../../../../packages/bundle/sdk-minimal/README.zh.md)则只列出一个拥有完整显式配置树的组合包。通用的 `dsh --profile <name>` 把剩余参数交给该 profile 的命令行启动行:Web 持有自己的 flag 家族,headless 持有任务位置参数,协议 profile 不接受应用选项。patch overlay 使用启动器持有的 `--patch`。新的非内置目标可以使用 `--from-default-profile <template>`,在启动或配置 dump 之前复制一个默认模板的 bundle 列表与 patch 重载策略。这会创建依赖为空、用户 patch 为空的独立 profile:它既不读取与模板同名的本地 profile,也不记录继承关系。launcher 会以独占方式领取完整的目标目录,因此既有状态和并发创建者都会在不作修改的情况下失败。`dsh plugin --profile <name> <args...>` 是一层薄薄的 pnpm 转发器,负责初始化一个以 base 为基础的 profile,并依据已安装包的组合包声明调和 `dsh.profile.bundles`;没有组合包声明的包保持为普通依赖。[Headless 作为直接 core 入口](../../archived/architecture/2026-08-09-headless-direct-core-entry-point.md)负责 headless 组合约定。
解析在构造上就是双锚点的:`dsh.profile.bundles` 中的名称先从 dsh 安装目录解析,再从 profile 目录解析,因此内置组合包始终来自与运行中 `dsh` 相同的安装,pnpm 从不管理它们。patch 行中的裸插件名称使用[不可变 profile resolution generation](2026-09-09-profile-resolution-generations.zh.md),在内存中应用相同的安装优先与有序 bundle 规则;保留的 link 与 dual 模式可以物化同一结果。
解析在构造上就是双锚点的:`dsh.profile.bundles` 中的名称先从 dsh 安装目录解析,再从 profile 目录解析,因此内置组合包始终来自与运行中 `dsh` 相同的安装,pnpm 从不管理它们。patch 行中的裸插件名称使用[不可变 runtime resolution](2026-09-09-profile-resolution-generations.zh.md),在内存中应用安装优先与有序 bundle 规则,并从包的真实目录展开依赖。
两项配套重构:webserver 内置的静态 dist 服务改为单一所有者的**回退席位**(`registerFallback`/`applyIndexTaps`),SPA 服务器提取到 `@deepseek-ai/dsh-host-frontend-static`,使 web 组合包以组合的方式持有自己的 dist,而不是靠启动器代码;[dsh CLI 个人配置决策](../../archived/feature/2026-07-20-dsh-cli-personal-config.md)的个人 overlay 机制(`loadPersonalPatches`、`$DSH_HOME/config.yaml`)改为面向逐 profile 与 home 级的 `cordis.patch.yml` 层(`loadOptionalPatches`、接受文件名的 `watchUserPatches`),取代该笔记的各入口模式与文件位置,同时保留其 Harness home 根目录、patch 语义与响亮失败的解析。
## Alternatives considered
- **依赖扫描加部分 `patchOrder`**(最初的草案):扫描 `dependencies` 找出组合包、未列出者按字母序排列,会产生两个真源和一条隐式决胜规则;一份显式有序的 `dsh.profile.bundles` 列表更小、完全确定。在 profile 内直接 `pnpm add` 只会安装一个库,不激活任何 patch——行为显式,没有暗中扫描。
- **内置组合包使用 `link:` 条目**:pnpm 无法对指向安装目录的 `link:` 做版本管理、安装或更新,它会把机器路径嵌进用户文件,并且在安装目录移动后失效。双锚点解析加上每次启动修复的符号链接回退提供了同样的保证(「组合包来自安装目录」),且没有这些繁文缛节。
- **内置组合包使用 `link:` 条目**:pnpm 无法对指向安装目录的 `link:` 做版本管理、安装或更新,它会把机器路径嵌进用户文件,并且在安装目录移动后失效。双锚点解析加运行时 fallback 可以让组合包保持由安装持有,无需持久化这些链接。
- **在组合包 manifest 中放一个启动前 `context` 模块**承载启动期取值(dist 路径、flag 事实):否决,改用纯插件——粘合逻辑就是普通配置行和由应用持有的启动服务,因此组合始终可完整 dump,manifest 保持纯数据。启动器提供的宿主 slot(`ctx.cmdlineArgs`、`ctx.appExit` 与环境快照)在任何配置树条目挂载之前,于 `boot()` 的 `prepare` 钩子中提供。
- **组合包的传递式自动应用**:只有直接列在 `dsh.profile.bundles` 中的条目才贡献层;想重新导出另一个组合包 patch 的元组合包,必须在自己的 patch 文件中显式完成。
- **动态模板继承或克隆本地 profile**:记录父级会要求为 bundle 成员关系、依赖和用户 patch 制定合并与升级规则,而复制本地状态会重复机器特定选择。基于模板的创建只会一次性复制安装自有的默认值。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.md
2026-08-09-client-conversation-node-assembly.md: 43f9f0359822f7d7c5b52c09646fd3ffbf80f783
2026-08-09-client-conversation-node-assembly.zh.md: d7475f5ff8218419743448d455d5d5ffe3cc4091
2026-08-09-client-conversation-node-assembly.md: c8773b77fe761c3a566e02f0bd6aeb18f26e0a89
2026-08-09-client-conversation-node-assembly.zh.md: 076ad35178996e01eae26b3909feae8d967414c1
@@ -14,6 +14,8 @@ Business events also use different correlation models. Tool has call IDs, Assist
## Decision
The [three-stage Tool lifecycle](2026-09-22-tool-call-three-phases.md) owns transient initialization, repeated start matches for one ID, and tool preparation; this record's other node-assembly responsibilities remain applicable.
Client Runtime provides a target-neutral Conversation Node assembly engine. Business plugins register Event Definitions, and view plugins register per-Session View Builders. `ui-conversation` registers the first built-in Definitions and the `chat` builder; Session only submits the current contiguous `SessionEventLikeEntry` window to the engine and publishes its snapshot instead of interpreting individual conversation businesses. The entry's outer discriminator distinguishes standard and packed records, while both carry an aligned inner `SessionEventLike` for Definition dispatch.
This Note retains the derivation, business-by-business validation, responsibilities, algorithms, and trade-offs that remain relevant after implementation.
@@ -291,7 +293,7 @@ Page size, record packing, the number of history loads, and RAF coalescing affec
Inbox demonstrates that every Event can be a start-only instantaneous-state Context; not every business requires a start/update pair. Reader links each next-step state to the prior same-kind Context instead of inventing a lifecycle ID for the entire Inbox. The state itself shares immutable pending splice nodes and one current claimed-batch Set, while unconsumed next-turn input remains outside Conversation because no Chat or Trajectory classification reads it.
Request Prompt demonstrates shared pure interpretation without shared target State: Chat and Trajectory call `inspectRequestPrompt()` from their own Definitions. The function canonicalizes the full header and classifies model-visible system/tool differences; each target then chooses its own output. Chat materializes a non-empty initial system field, a real system change, and each `series` snapshot that explicitly begins a message series or follows a surface replacement. An unchanged `resume` remains in Trajectory and reconstruction state but does not repeat the visible Chat row once its predecessor is loaded. Ordinary append-only later Turns do not write another unchanged header. The first header in a Step follows the provider envelope rather than the header Event position: step one uses the owning Turn start and later steps use their Step start, placing the system field before the request's user-role messages; a later header in the same Step stays at its own Event after the surface rewrite that began the new series. When the preceding header is outside a partial window, a non-`initial` header stays at its own Event and renders conservatively. Prepending an identical predecessor hides an unchanged resume without withdrawing its stable Node key; a real change remains visible. Every header is a full snapshot, so a first loaded `resume`, `change`, or `series` header can render its system field without fabricating a comparison to unloaded history ([resume presentation decision](../bug-fix/2026-09-03-resume-headers-do-not-repeat-system-prompts.md)).
Request Prompt demonstrates shared pure interpretation without shared target State: Chat and Trajectory call `inspectRequestPrompt()` from their own Definitions. The function canonicalizes the full header and classifies model-visible system/tool differences; each target then chooses its own output. Chat materializes a non-empty initial system field, a real system change, and each `series` snapshot that explicitly begins a message series or follows a surface replacement. Resume headers retain a Chat prompt card even when the system text is unchanged. Ordinary append-only later Turns do not write another unchanged header. The first header in a Step follows the provider envelope rather than the header Event position: step one uses the owning Turn start and later steps use their Step start, placing the system field before the request's user-role messages; a later header in the same Step stays at its own Event after the surface rewrite that began the new series. When the preceding header is outside a partial window, a non-`initial` header stays at its own Event and renders conservatively. Prepending an identical predecessor can hide an unchanged `change` header without withdrawing its stable Node key; resume and explicit series-start cards remain visible. Every header is a full snapshot, so a first loaded `resume`, `change`, or `series` header can render its system field without fabricating a comparison to unloaded history ([request-prompt Definition](../../../../packages/client/ui-chat/src/client/conversation-nodes/request-prompt.ts)).
Retry, Assistant, and Turn Tail demonstrate independent claims on one Event. Each Definition updates only its own State and produces its own atomic Chat Node.
@@ -14,6 +14,8 @@ Client Session 既维护传输窗口、连接状态和待处理交互,也在
## 决策
start 的瞬态创建入口、同 ID 多次 start 及工具准备阶段以[工具调用三阶段](2026-09-22-tool-call-three-phases.zh.md)为准;本记录的其他节点组装职责继续适用。
Client Runtime 提供 target-neutral 的 Conversation Node 组装引擎,业务插件注册 Event Definition,视图插件注册 per-Session View Builder。`ui-conversation` 注册第一批内建 Definition 和 `chat` builder;Session 只负责把当前连续 `SessionEventLikeEntry` window 送入引擎并发布它的 snapshot,且不解释具体 conversation 业务。entry 的外层 discriminator 区分标准与 packed record,两者都携带字段对齐的内部 `SessionEventLike`,供 Definition dispatch。
本 Note 保留实现后仍有价值的方案推导、逐业务适配、职责、算法和取舍。
@@ -291,7 +293,7 @@ Chat `order` 的结构性变化仍可能重排当前可见 key;纯 data 更新
Inbox 展示了“每条 Event 都是一个 start-only 瞬间态 Context”,不是所有业务都需要 start/update 配对。每个 next-step state 通过 Reader 与前一个同 kind Context 形成连续 fold,而非给整个 Inbox 人工制造生命周期 ID。state 自身共享不可变 pending splice 节点和一个当前 claimed-batch Set;未消费的 next-turn input 不进入 Conversation,因为 Chat 与 Trajectory 都不读取它来分类。
Request Prompt 展示了如何在不共享 target State 的前提下共用纯解释逻辑:Chat 与 Trajectory 各自在自己的 Definition 中调用 `inspectRequestPrompt()`。该函数规范化完整 header,并判定面向模型的 system/tool 差异;随后每个 target 自行选择产物。Chat 会物化非空的初始 system 字段、真实 system 变化,以及每个显式开启消息序列或紧随表层替换的 `series` 快照。未变化的 `resume` 会留在 Trajectory 和重建状态中,但前序 header 已加载时不会重复可见的 Chat 行。普通的仅追加后续 Turn 不会再次写入未变 header。一个 Step 中的首条 header 遵循提供方信封,而不是 header Event 位置:step one 使用所属 Turn start,后续 step 使用各自的 Step start,把 system 字段放到该请求的 user-role 消息之前;同一 Step 的后续 header 保留在开启新序列的表层改写之后。部分窗口未包含前序 header 时,非 `initial` header 会保留在自身 Event 并保守渲染。prepend 补入相同的前序 header 后,内容未变的 resume 会隐藏但不撤回其稳定 Node key;真实变化仍然可见。每条 header 都是完整快照,因此已加载窗口中的首条 `resume`、`change` 或 `series` header 无需凭空构造与未加载历史的比较,也能渲染其 system 字段([resume 展示决策](../bug-fix/2026-09-03-resume-headers-do-not-repeat-system-prompts.zh.md))。
Request Prompt 展示了如何在不共享 target State 的前提下共用纯解释逻辑:Chat 与 Trajectory 各自在自己的 Definition 中调用 `inspectRequestPrompt()`。该函数规范化完整 header,并判定面向模型的 system/tool 差异;随后每个 target 自行选择产物。Chat 会物化非空的初始 system 字段、真实 system 变化,以及每个显式开启消息序列或紧随表层替换的 `series` 快照。即使 system 文本未变化,resume header 仍保留 Chat prompt 卡片。普通的仅追加后续 Turn 不会再次写入未变 header。一个 Step 中的首条 header 遵循提供方信封,而不是 header Event 位置:step one 使用所属 Turn start,后续 step 使用各自的 Step start,把 system 字段放到该请求的 user-role 消息之前;同一 Step 的后续 header 保留在开启新序列的表层改写之后。部分窗口未包含前序 header 时,非 `initial` header 会保留在自身 Event 并保守渲染。prepend 补入相同的前序 header 后,内容未变的 `change` header 可以隐藏,但不撤回其稳定 Node key;resume 和显式序列起点的卡片仍然可见。每条 header 都是完整快照,因此已加载窗口中的首条 `resume`、`change` 或 `series` header 无需凭空构造与未加载历史的比较,也能渲染其 system 字段([request-prompt Definition](../../../../packages/client/ui-chat/src/client/conversation-nodes/request-prompt.ts))。
Retry、Assistant 和 Turn Tail 展示了同一 Event 被多个 Definition 独立认领。每个 Definition 只更新自己的 State,最终分别生成原子 Chat Node。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.md
2026-08-10-host-plane-ownership-after-presets.md: 78410368cdc66b14fe478a4de9f0ae7a1cbefac5
2026-08-10-host-plane-ownership-after-presets.zh.md: 862b793fab23b209f6f43b2e8045ad7330c30ba0
2026-08-10-host-plane-ownership-after-presets.md: daf48f27c2228b71319d6f5410e6cabe3bc02b39
2026-08-10-host-plane-ownership-after-presets.zh.md: 56babf5e715584b4ee061a334158d46e59b0d350
@@ -6,7 +6,7 @@ English | [中文](2026-08-10-host-plane-ownership-after-presets.zh.md)
## Problem
[Per-session agent presets](2026-08-03-per-session-agent-presets.md) moved every model-facing row onto the agent plane, and each later fix has been one reader that assumed the world before the move. `tasks` came back to the host because a preset row outside its realm resolved it; `goals` never left for the same reason; a child agent's `toolFilter` was repaired once every model-facing tool became an ancestor contribution rather than a global one ([child agents join their parent's preset](../bug-fix/2026-08-10-child-agents-join-their-parent-preset.md)).
[Per-session agent presets](2026-09-18-declarative-agent-presets.md) moved every model-facing row onto the agent plane, and each later fix has been one reader that assumed the world before the move. `tasks` came back to the host because a preset row outside its realm resolved it; `goals` never left for the same reason; a child agent's `toolFilter` was repaired once every model-facing tool became an ancestor contribution rather than a global one ([child agents join their parent's preset](../bug-fix/2026-08-10-child-agents-join-their-parent-preset.md)).
Two more readers were still on the wrong side of that line.
@@ -18,15 +18,15 @@ Nothing named an agent that joined no preset. The join is a scope-parent link; w
**The meter is host-plane.** `dsh-token-meter` returns to the host composition and leaves the presets' `isolate` map, so `compaction-basic` and `tool-result-pruner` resolve the one host instance from inside their realm. The presets keep the realm and the backend — what a preset chooses is whether its agent compacts, not whether its tokens are counted. This is the criterion `tasks` and `goals` are already read by, applied to a Service whose *projection* reach is what made preset ownership wrong: a unit whose empty value is indistinguishable from a real one cannot be per-composition while the table it registers into is per-process.
**An unjoined agent is named twice, at two different points.** `AgentPresets` logs one warning per agent published with a scope chain of length one while a roster is configured. The invariant companion fails instead — and at `system-prompt/assemble`, not at publication, because an unjoined agent is legal until it addresses a model: `recompose` binds exactly such an agent as its first link, and prompt assembly is the only caller that supplies an agent scope, so a host assembly and a standing mount are both correctly out of range.
**An unjoined agent fails at model use.** The invariant companion checks `system-prompt/assemble`, because a bare Agent can legally be bound later through `recompose`. Host-only and cold-scope prompt reads have no Agent and remain outside this check.
Three limits stay open and are recorded where they bite rather than fixed here: projection key presence is not a per-session capability signal ([`dsh-session-projection`](../../../../packages/session/session-projection/README.md)); a superseded standing generation is never reclaimed, which the settings-page authoring flow turns into a per-save cost ([`dsh-agent-presets`](../../../../packages/preset/agent-presets/README.md)); and a temporary plugin mounted through `cordis_mount` belongs to the composition rather than the session that mounted it ([`dsh-tool-cordis`](../../../../packages/extensions/tool-cordis/README.md)).
Projection key presence is not a per-session capability signal ([session projection](../../../../packages/session/session-projection/README.md)). Temporary plugins mounted through `cordis_mount` belong to the composition rather than the invoking session ([tool Cordis](../../../../packages/extensions/tool-cordis/README.md)). [Declarative presets](2026-09-18-declarative-agent-presets.md) own revision retention and reclaim retired trees after their final reference releases.
## Testing
`apps/cli/tests/web-agent-presets.e2e.ts` reads `ctx.get('tokenMeter')` on the booted Web composition before any preset in the file mounts — a preset-side meter sits behind an `isolate` realm and is invisible to `ctx.get`, so the read is an ownership assertion rather than a mount-order coincidence — then asserts a `minimal` session's snapshot carries all three units.
`apps/cli/tests/web-agent-presets.e2e.ts` reads `ctx.get('tokenMeter')` on the booted Web composition before any Agent is created — a preset-side meter sits behind an `isolate` realm and is invisible to `ctx.get`, so the read is an ownership assertion rather than a mount-order coincidence — then asserts a `minimal` session's snapshot carries all three units.
`packages/preset/agent-presets/tests/mount.spec.ts` asserts the warning fires exactly once for a bare agent and not at all for a joined one. `tests/invariant.spec.ts` carries the negative control: an unjoined agent's assembly rejects, while a joined agent's assembly and a scopeless host assembly both pass.
`packages/preset/agent-preset-registry/tests/invariant.spec.ts` rejects an unjoined Agent's assembly and accepts a joined Agent, a Host read and a cold-scope read.
## Alternatives considered
@@ -42,4 +42,4 @@ Three limits stay open and are recorded where they bite rather than fixed here:
The context meter becomes a per-session fact instead of a function of mount history. A preset can no longer opt out of token accounting; no shipped preset did, and `minimal` now says it drops auto-compaction rather than the accounting.
The warning is advisory, so a deployment that adds a roster to the ACP or SDK-server entry points still starts agents with no tools — it just says so once per agent instead of silently. The invariant reaches only compositions that load `dsh-invariants`, which fences package tests and development hosts, not a shipped one.
The invariant reaches compositions loading `dsh-invariants`. Production entry points must compose Agents explicitly; installing the registry alone does not bind them.
@@ -6,7 +6,7 @@ Status: implemented
## 问题
[逐会话 agent preset](2026-08-03-per-session-agent-presets.zh.md) 把每一个面向模型的行搬上了 agent 平面,此后的每一处修复都是一个仍按搬迁之前的世界写成的读取点。`tasks` 因为 realm 之外的 preset 行要解析它而搬回宿主;`goals` 因为同样的理由从未离开;而当所有面向模型的工具都变成祖先贡献之后,子 agent 的 `toolFilter` 也已被修好([子 agent 加入父方 preset](../bug-fix/2026-08-10-child-agents-join-their-parent-preset.zh.md))。
[逐会话 agent preset](2026-09-18-declarative-agent-presets.zh.md) 把每一个面向模型的行搬上了 agent 平面,此后的每一处修复都是一个仍按搬迁之前的世界写成的读取点。`tasks` 因为 realm 之外的 preset 行要解析它而搬回宿主;`goals` 因为同样的理由从未离开;而当所有面向模型的工具都变成祖先贡献之后,子 agent 的 `toolFilter` 也已被修好([子 agent 加入父方 preset](../bug-fix/2026-08-10-child-agents-join-their-parent-preset.zh.md))。
还有两个读取点仍站在这条线的错误一侧。
@@ -18,15 +18,15 @@ Status: implemented
**meter 属于宿主平面。** `dsh-token-meter` 回到宿主组装,并离开各 preset 的 `isolate` 映射,于是 `compaction-basic` 与 `tool-result-pruner` 在自己的 realm 内部解析到那一份宿主实例。preset 保留 realm 与压缩后端——preset 选择的是它的 agent 是否压缩,而不是它的 token 是否被计。这正是 `tasks` 与 `goals` 已经采用的判据,只是这次适用于一个因**投影**触达面而不该归 preset 所有的 Service:当一个单元的空值与真实值无法区分时,只要它注册进的那张表是进程级的,它就不能是逐组装的。
**未加入的 agent 在两个不同的点上被指出两次。** 在配置了名单的前提下,`AgentPresets` 对每个作用域链长度为一就发布的 agent 记录一条警告。invariant 配套则直接失败——并且发生在 `system-prompt/assemble` 而非发布时,因为一个未加入的 agent 在它对模型说话之前都是合法的:`recompose` 绑定的正是这样一个 agent 作为它的首次链接;而提示词组装是唯一会提供 agent 作用域的调用方,因此宿主组装与常驻挂载都正确地落在检查范围之外。
**未加入的 Agent 在使用模型时失败。** 不变量 companion 检查 `system-prompt/assemble`,因为未绑定的 Agent 可以合法地稍后通过 `recompose` 绑定。纯 Host 和历史 scope 提示词读取没有 Agent,不属于此检查范围。
有三处限制不在此处修复,而是记录在会咬到它们的地方:投影 key 是否存在不能当作逐会话的能力信号([`dsh-session-projection`](../../../../packages/session/session-projection/README.zh.md));被替代的常驻代际永不回收,而设置页的编写流程把它变成每次保存的代价([`dsh-agent-presets`](../../../../packages/preset/agent-presets/README.zh.md));通过 `cordis_mount` 挂上的临时插件属于组装而非挂载它的会话([`dsh-tool-cordis`](../../../../packages/extensions/tool-cordis/README.zh.md))。
投影 key 是否存在不能代表逐会话能力([会话投影](../../../../packages/session/session-projection/README.zh.md))。通过 `cordis_mount` 挂载的临时插件属于组装,而非调用会话([Cordis 工具](../../../../packages/extensions/tool-cordis/README.zh.md))。[声明式 preset](2026-09-18-declarative-agent-presets.zh.md) 负责代际保留,在最后一个引用释放后回收退役树。
## 测试
`apps/cli/tests/web-agent-presets.e2e.ts` 在本文件中任何 preset 挂载**之前**,于已启动的 Web 组装上读取 `ctx.get('tokenMeter')`——preset 侧的 meter 会待在 `isolate` realm 里,对 `ctx.get` 不可见,因此这次读取是一次所有权断言而不是挂载顺序的巧合——随后断言一个 `minimal` 会话的快照带齐三个单元。
`apps/cli/tests/web-agent-presets.e2e.ts` 在创建任何 Agent **之前**,于已启动的 Web 组装上读取 `ctx.get('tokenMeter')`——preset 侧的 meter 会待在 `isolate` realm 里,对 `ctx.get` 不可见,因此这次读取是一次所有权断言而不是挂载顺序的巧合——随后断言一个 `minimal` 会话的快照带齐三个单元。
`packages/preset/agent-presets/tests/mount.spec.ts` 断言警告对裸 agent 恰好触发一次、对已加入的 agent 完全不触发。`tests/invariant.spec.ts` 承担负控:未加入 agent 的组装被拒绝,而已加入 agent 的组装与不带作用域的宿主组装都通过。
`packages/preset/agent-preset-registry/tests/invariant.spec.ts` 拒绝未加入的 Agent 组装,并接受已加入的 Agent、Host 读取和历史 scope 读取。
## 考虑过的替代方案
@@ -42,4 +42,4 @@ Status: implemented
context meter 成为逐会话的事实,而不再是挂载历史的函数。代价是 preset 不能再选择不做 token 记账;随附的 preset 没有一个这么做,`minimal` 现在也写明它放弃的是自动压缩而非记账。
那条警告是建议性的,因此给 ACP 或 SDK server 入口加上名单的部署依然会启动没有工具的 agent——只是每个 agent 会说一次,而不再静默。invariant 只触达装载了 `dsh-invariants` 的组装,因此它把关的是包测试与开发宿主,不是随附宿主。
不变量覆盖加载了 `dsh-invariants` 的组装。生产入口必须显式组装 Agent;仅安装注册表不会自动绑定。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.md
2026-08-10-remote-event-delivery.md: 1706cc525bd7707874353a6658ec2f52597044a9
2026-08-10-remote-event-delivery.zh.md: ac79642e90791892870c8920e02867faea180b07
2026-08-10-remote-event-delivery.md: 3eaa26fb92c40b3a20cab11d4522bcf68b8a207f
2026-08-10-remote-event-delivery.zh.md: d41589f1b800da98342210d2e429a2b67b6c61f2
@@ -195,3 +195,7 @@ The few required Client symbols are mirrored on the test side: `scaffold.ts` exp
- **Capabilities deliberately omitted.** Payload projection and redaction are unsupported, scopes other than Agent are unsupported, and ordinary notifications are not replayed. Recoverable state needs a query, cursor, or opening baseline; a waterfall is replayed only while its original Host invocation remains pending.
- **Some Client packages remain in the Host graph.** Twelve projects, including `connection`, `runtime`, and `ui-slots`, remain reachable through unsplit `directory-picker-browse`/`-native` and `api/gateway → client/connection`. They compile and no longer pull in API Remotes' Client face, so this change does not split them. Direct `dsh-client-runtime/client` imports in two chat e2e files rely on Runtime's current presence in that graph rather than a general guarantee.
- **The package intentionally publishes no invariant companion.** A prior revision asserted delivery form on the live event bus, coupling diagnostics to the allowlist and causing Rolldown to emit a third bundle chunk omitted by the mechanically derived publication list. The Host-face `TypertForwardableEventEntry` assertion already rejects those mismatches at compile time, and the package README records why no independent runtime relation remains.
## Related
- [Remote duplex stream](2026-09-19-remote-duplex-stream.md): every Remote stream carries a Client uplink on the same logical stream; the duplex `$events` listed under its Deferred section replaces the unary `$events/result` path once it lands.
@@ -197,3 +197,7 @@ Client 要求首项是带非空 `clientId` 与 `host.home` 的 `ready`;后续
- **放弃的能力**:不支持投影或脱敏载荷,不支持 Agent 以外的 Scope,也不为普通通知提供重放。需要可靠恢复的状态必须拥有查询、cursor 或 opening baseline;waterfall 只重放仍处于同一次 Host 调用生命周期内的 pending request。
- **仍有 client 包留在 host 图里**:12 个工程(`connection`、`runtime`、`ui-slots` 等)经未拆分的 `directory-picker-browse`/`-native` 与 `api/gateway → client/connection` 仍可达 host 图。它们都能编译且不再牵连 api/remotes 的 client face,因此没有阻塞本次改动;拆分那些包能减少几个,但经评估后不做。两个 chat e2e 直接引 `dsh-client-runtime/client` 依赖 `runtime` 本来就在图里——属偶然而非保证。
- **本包不发布 invariant companion**:早先的修订曾在活事件总线上断言投递形状(`thisArg === null`、`mode === 'emit'`),这让诊断逻辑与名单值耦合,并使 rolldown 把它提成第三个 bundle chunk——而机械推导的发布文件清单并不携带它。Host 面的 `TypertForwardableEventEntry` 断言已在编译期拒绝这些偏离,包 README 也记录了不再存在独立运行时关系的原因。
## 相关
- [Remote 双工流](2026-09-19-remote-duplex-stream.zh.md):所有 Remote 流在同一逻辑流上承载客户端上行;其「后续」列出的双工 `$events` 落地后取代 unary `$events/result` 路径。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md
2026-08-10-session-log-version-mechanism.md: 513b126d3b00841f71893715cc296cb67619543e
2026-08-10-session-log-version-mechanism.zh.md: 919bfb077c7037da2ebf7eb81b75745d6b4fe4d0
2026-08-10-session-log-version-mechanism.md: 9cb0940f767aa27d5a5106fb78b528c1e3e2695b
2026-08-10-session-log-version-mechanism.zh.md: e1424f24e5dd74b243fe1f0aebceb5d420b9b8c3
@@ -18,6 +18,8 @@ Session logs must be upgradable after release, and the runtime that ships first
**A per-event `ignorable` marker covers vocabulary growth, so ordinary event additions never bump the version.** The event vocabulary is decided by which plugins are mounted, which a single version integer cannot describe. A reader meeting an unrecognized event type refuses to interpret the log unless the event carries `ignorable: true` in its envelope. The default is *required*: forgetting the marker over-refuses a resumable session (an inconvenience), while a default of ignorable would make the same mistake silently resume a gutted one (a safety failure). The architecture makes this sound: model-visible content flows only through the four `surfaceOp`-marked surface event types plus the `request/header`/`request/context` folds, so the dangerous unknowns are exactly the non-surface events that change how the rest of the log is read (`session/end-seed` is the existing example).
The [accepted compatibility checkpoint](../../../../docs/session-format-status.md#finalization-record) prevents an accepted transition from reusing its header increase for later breaking changes. Backward-compatible evolution uses new acknowledgement records at the same Session version; breaking changes require a higher writer. The checkpoint does not change the unknown-event protocol.
### Writer and publication authority
`SESSION_FORMAT_VERSION` owns the checkout writer number; the [release-status reference](../../../../docs/session-format-status.md) owns one bilingual `latestReleasedVersion` and `evidenceTag` record. Publication changes independently of source development, so status is derived by comparing those facts rather than maintaining a second `released` boolean. General documentation links to these authorities; fixed-version contracts and historical evidence keep their explicit numbers.
@@ -18,6 +18,8 @@ Session log 在发布后必须能升级格式,而最先发布的运行时决
**逐事件的 `ignorable` 标记吸收词汇表增长,普通的新增事件永远不用升版本。**事件词汇表由挂载了哪些插件决定,单个版本整数描述不了它。读取器遇到不认识的事件类型时拒绝解读日志,除非该事件的信封带 `ignorable: true`。默认为必需:忘写标记的后果是把一个本可恢复的会话拒绝过头(体验问题),而默认可忽略会让同样的疏忽静默恢复出残缺会话(安全事故)。架构保证了这条规则成立:模型可见内容只经四种带 `surfaceOp` 标记的 surface 事件加 `request/header`、`request/context` 折叠进入重建,危险的未知事件恰好是那些不进 surface 但改变日志其余部分解读方式的事件(`session/end-seed` 是现存例子)。
[已接受兼容性检查点](../../../../docs/session-format-status.zh.md#finalization-record)阻止已接受转换复用原来的头版本递增来授权后续破坏性变更。向后兼容的演进在相同 Session 版本上使用新的确认记录;破坏性变更要求更高的写入器版本。检查点不改变未知事件协议。
### 写入器与发布真源
`SESSION_FORMAT_VERSION` 拥有工作区写入器版本号;[发布状态参考](../../../../docs/session-format-status.zh.md)拥有唯一的双语 `latestReleasedVersion` 与 `evidenceTag` 记录。发布状态独立于源码开发而变化,因此通过比较这两个事实推导状态,而不另行维护 `released` 布尔值。一般文档链接到这些真源;固定版本约定与历史证据保留明确版本号。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.md
2026-08-18-experimental-agent-teams-packages.md: 4a78a60c2ad7463c06b670e6ec664579ffd767b9
2026-08-18-experimental-agent-teams-packages.zh.md: 8056cd8194695e8ce41acb359e51c040d4ed3aec
2026-08-18-experimental-agent-teams-packages.md: 92eba148b328135301b196c9a17e49ee23ddb3f6
2026-08-18-experimental-agent-teams-packages.zh.md: 6f4d004a78e32e2c27ad65648c446b3cd54c07ad
@@ -12,17 +12,17 @@ Moving the packages into product-role groups would remove their experimental nam
## Decision
`packages/experimental/agent-team`, `packages/experimental/tool-agent-team`, `packages/experimental/agent-team-profile`, `packages/experimental/client-ui-agent-team`, and `packages/experimental/agent-team-web-profile` are public workspace packages. They retain their existing `@deepseek-ai/dsh-experimental-*` names and join the dsh release family. The [publication denylist decision](../process/2026-09-12-experimental-publication-denylist.md) owns the public default and private exceptions; the [experimental package rules](../../../../packages/experimental/AGENTS.md) own dependency isolation and later promotion.
`packages/experimental/agent-team`, `packages/experimental/tool-agent-team`, `packages/experimental/agent-team-profile`, and `packages/experimental/client-ui-agent-team` are public workspace packages. They retain their existing `@deepseek-ai/dsh-experimental-*` names and join the dsh release family. The [publication denylist decision](../process/2026-09-12-experimental-publication-denylist.md) owns the public default and private exceptions; the [experimental package rules](../../../../packages/experimental/AGENTS.md) own dependency isolation and later promotion.
The dsh pack and publish set and the local baseline publisher include these five Agent Teams directories and the [Cua Driver providers](2026-09-12-computer-use-provider-registration.md). Workspace constraints require them to omit `private`, set `publishConfig.access` to `public`, and keep the experimental npm prefix. Release packages and apps outside the experimental group, plus the Python runtime, cannot name experimental packages in `dependencies`, `optionalDependencies`, or `peerDependencies`; experimental packages may depend on release packages and each other.
The dsh pack and publish set and the local baseline publisher include these four Agent Teams directories and the [Cua Driver providers](2026-09-12-computer-use-provider-registration.md). Workspace constraints require them to omit `private`, set `publishConfig.access` to `public`, and keep the experimental npm prefix. Release packages and apps outside the experimental group, plus the Python runtime, cannot name experimental packages in `dependencies`, `optionalDependencies`, or `peerDependencies`; experimental packages may depend on release packages and each other.
The generic caller-reserved continuable child identity and selective direct-child drain remain in the stable Subagent service. They own Subagent identity and Activation lifecycle without importing or naming Agent Teams; the experimental Team service consumes them in the permitted direction.
The published Host-side Agent Teams profile bundle depends on the Team packages and applies after `dsh-base`. It inserts the Team rows and disables the global continuable-child controls whose model-visible names overlap the Team tools. The separate published Web profile applies after `dsh-web-app` and the Host profile; it inserts the Team UI, which mounts the Remote contribution generated by the Team package. Both layers remain opt-in and leave the shipped base, CLI, Web, and Python runtime dependency graphs unchanged.
The [single-bundle decision](2026-09-18-agent-teams-single-bundle.md) supersedes the separate Host and Web bundle composition. This note retains publication, dependency isolation, and promotion rationale. The current Team profile remains opt-in and disables the global continuable-child controls whose model-visible names overlap the Team tools; the [optional-bundle decision](../process/2026-09-15-shipped-optional-bundles.md) owns its inclusion in the installation.
Profile startup resolves selected bundles before computing the [immutable profile resolution generation](2026-09-09-profile-resolution-generations.md). The generation retains installation-first precedence, traverses each explicit bundle root completely in profile order, and keeps pnpm-managed profile packages authoritative. Runtime mode enforces the result in memory; retained link and dual modes materialize the same result as shared and profile-owned projections. A private profile layer can therefore carry experimental plugin rows without adding those plugins to a release app, requiring profile users to install transitive packages directly, weakening packaged-runtime module identity, or changing another profile's resolution.
Profile startup resolves selected bundles before computing the [immutable runtime resolution](2026-09-09-profile-resolution-generations.md). The runtime resolution retains installation-first precedence, traverses each explicit bundle root completely in profile order, and keeps pnpm-managed profile packages authoritative. The runtime interception enforces the result in memory without materializing fallback links. A private profile layer can therefore carry experimental plugin rows without adding those plugins to a release app, requiring profile users to install transitive packages directly, weakening packaged-runtime module identity, or changing another profile's resolution.
Experimental status changes compatibility and support expectations, not publication for these five packages. They retain the repository's ordinary documentation, invariant, lifecycle, security, unit, real-composition, and snapshot requirements. Promotion still requires review of the public contracts, limitations, test evidence, runtime dependents, and a named owner accepting stable-package obligations.
Experimental status changes compatibility and support expectations, not publication for these four packages. They retain the repository's ordinary documentation, invariant, lifecycle, security, unit, real-composition, and snapshot requirements. Promotion still requires review of the public contracts, limitations, test evidence, runtime dependents, and a named owner accepting stable-package obligations.
## Alternatives considered
@@ -36,6 +36,6 @@ Experimental status changes compatibility and support expectations, not publicat
## Consequences
Agent Teams publishes as five installable tarballs in the dsh release family without changing package names or enabling Team in a shipped profile. Public availability does not make the packages stable or supported by default, and stable release packages cannot take runtime dependencies on them.
Agent Teams publishes as four installable tarballs in the dsh release family without changing package names or enabling Team in a shipped profile. Public availability does not make the packages stable or supported by default, and stable release packages cannot take runtime dependencies on them.
The release family carries the experimental npm names. Promotion still creates path and npm-name churn as specified by the experimental package rules.
@@ -12,17 +12,17 @@ Agent Teams 的服务与工具约定仍在变化,但它需要使用真实 Sess
## 决策
`packages/experimental/agent-team`、`packages/experimental/tool-agent-team`、`packages/experimental/agent-team-profile`、`packages/experimental/client-ui-agent-team` 与 `packages/experimental/agent-team-web-profile` 是公开 workspace 包。它们保留现有 `@deepseek-ai/dsh-experimental-*` 名称并加入 dsh 发布系列。[发布拒绝列表决策](../process/2026-09-12-experimental-publication-denylist.zh.md)负责默认公开与私有例外;[实验性包规则](../../../../packages/experimental/AGENTS.md)负责依赖隔离与后续 promotion。
`packages/experimental/agent-team`、`packages/experimental/tool-agent-team`、`packages/experimental/agent-team-profile` 与 `packages/experimental/client-ui-agent-team` 是公开 workspace 包。它们保留现有 `@deepseek-ai/dsh-experimental-*` 名称并加入 dsh 发布系列。[发布拒绝列表决策](../process/2026-09-12-experimental-publication-denylist.zh.md)负责默认公开与私有例外;[实验性包规则](../../../../packages/experimental/AGENTS.md)负责依赖隔离与后续 promotion。
dsh 打包与发布集合以及本地基线发布器包含这五个 Agent Teams 目录和 [Cua Driver 提供方](2026-09-12-computer-use-provider-registration.zh.md)。workspace 约束要求它们省略 `private`、将 `publishConfig.access` 设为 `public`,并保留实验性 npm 前缀。实验组之外的发布包、应用和 Python 运行时不能在 `dependencies`、`optionalDependencies` 或 `peerDependencies` 中引用实验包;实验包可以依赖发布包和彼此。
dsh 打包与发布集合以及本地基线发布器包含这四个 Agent Teams 目录和 [Cua Driver 提供方](2026-09-12-computer-use-provider-registration.zh.md)。workspace 约束要求它们省略 `private`、将 `publishConfig.access` 设为 `public`,并保留实验性 npm 前缀。实验组之外的发布包、应用和 Python 运行时不能在 `dependencies`、`optionalDependencies` 或 `peerDependencies` 中引用实验包;实验包可以依赖发布包和彼此。
通用的调用方预留 continuable child 身份和精确 direct-child drain 仍属于稳定 Subagent 服务。它们负责 Subagent 身份与 Activation 生命周期,不 import 或命名 Agent Teams;实验性 Team 服务沿允许的方向消费这些能力。
公开发布的 Host 侧 Agent Teams profile bundle 依赖 Team 包,并在 `dsh-base` 之后应用。它会插入 Team 配置行,并禁用模型可见名称与 Team 工具重叠的全局 continuable-child control。独立公开发布的 Web profile 在 `dsh-web-app` 与 Host profile 之后应用;它会插入 Team UI,后者挂载 Team package 生成的 Remote contribution。两个层都保持显式启用,不改变随附 base、CLI、Web 与 Python runtime 的依赖图。
[单组合包决策](2026-09-18-agent-teams-single-bundle.zh.md)取代了独立 Host 与 Web 组合包的组合方式。本记录保留发布、依赖隔离与 promotion 的理由。当前 Team profile 仍须显式启用,并禁用模型可见名称与 Team 工具重叠的全局 continuable-child control;[可选组合包决策](../process/2026-09-15-shipped-optional-bundles.zh.md)负责其随安装交付的规则。
profile 启动会先解析所选 bundle,再计算[不可变 profile resolution generation](2026-09-09-profile-resolution-generations.zh.md)。generation 保留安装优先顺序,按 profile 顺序完整遍历每个显式 bundle 根,并让 pnpm 管理的 profile 包保持优先。runtime 模式在内存中强制该结果;保留的 link 与 dual 模式把同一结果物化为共享和 profile 自有投影。因此,私有 profile 层可以携带实验性 plugin 配置行,而无需把这些 plugin 加入发布 app、要求 profile 用户直接安装传递依赖、破坏 packaged-runtime 的模块身份,或改变其他 profile 的解析结果。
profile 启动会先解析所选 bundle,再计算[不可变 runtime resolution](2026-09-09-profile-resolution-generations.zh.md)。runtime resolution 保留安装优先顺序,按 profile 顺序完整遍历每个显式 bundle 根,并让 pnpm 管理的 profile 包保持优先。运行时拦截在内存中强制该结果,不物化 fallback 链接。因此,私有 profile 层可以携带实验性 plugin 配置行,而无需把这些 plugin 加入发布 app、要求 profile 用户直接安装传递依赖、破坏 packaged-runtime 的模块身份,或改变其他 profile 的解析结果。
对这五个包而言,实验性状态改变兼容性与支持预期,而不阻止发布。这些包仍须满足仓库的一般文档、不变式、生命周期、安全、单元测试、真实组合测试和快照要求。promotion 前仍须评审公开约定、限制、测试证据、运行时依赖方,并由一名具名 owner 接受稳定包义务。
对这四个包而言,实验性状态改变兼容性与支持预期,而不阻止发布。这些包仍须满足仓库的一般文档、不变式、生命周期、安全、单元测试、真实组合测试和快照要求。promotion 前仍须评审公开约定、限制、测试证据、运行时依赖方,并由一名具名 owner 接受稳定包义务。
## 曾考虑的替代方案
@@ -36,6 +36,6 @@ profile 启动会先解析所选 bundle,再计算[不可变 profile resolution
## 后果
Agent Teams 会作为 dsh 发布系列中的五个可安装 tarball 发布,同时保持包名不变,也不会在随附 profile 中启用 Team。公开可用不代表这些包稳定或默认受支持,稳定发布包也不能对其建立运行时依赖。
Agent Teams 会作为 dsh 发布系列中的四个可安装 tarball 发布,同时保持包名不变,也不会在随附 profile 中启用 Team。公开可用不代表这些包稳定或默认受支持,稳定发布包也不能对其建立运行时依赖。
发布系列保留实验性 npm 名称。promotion 仍会按照实验性包规则产生路径和 npm 名改动。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.md
2026-08-23-client-derived-tool-presentation.md: 703960cb24f90eb678fda381f929efb3f94bbd28
2026-08-23-client-derived-tool-presentation.zh.md: 7f161379553f3aca33c72c2edf93e114d27eb7f9
2026-08-23-client-derived-tool-presentation.md: 931276fa6d4c0590746b6c59eeae9f998f05629c
2026-08-23-client-derived-tool-presentation.zh.md: b930f98da17a1cb6ecbf605e3a9f37255ba75fc9
@@ -22,7 +22,7 @@ The required result is one raw Session journal and one Client presentation owner
## Decision
The visual-equivalence requirements below exclude the separately approved [nested terminal-card fix](../bug-fix/2026-09-05-nested-terminal-cards.md); all other presentation and ownership constraints remain.
The visual-equivalence requirements below exclude the separately approved [nested terminal-card fix](../bug-fix/2026-09-05-nested-terminal-cards.md) and [compact tool details](2026-09-10-compact-tool-details.md); all other presentation and ownership constraints remain.
The Session Remote journal sends only raw, validated, persistable Session events. `session.page` and `session.follow` do not parse tool arguments, query the Tools registry, restore a presenter scope, execute `presentCall` or `presentResult`, or construct or clone any tool view.
@@ -51,7 +51,7 @@ The Host `ToolDefinition.presentCall`, `ToolDefinition.presentResult`, `ToolCall
| Retained | the Session log format, Remote journal lifecycle, and Conversation identity/topology |
| Retained | the existing keyed slot, Generic fallback, and Chat, Details, and Trajectory structure |
| Forbidden | a new Client presenter service, parallel registry, or wire renderer id |
| Forbidden | new cards, visual redesign, interaction redesign, or PTC dispatch rich-card enhancements except the [nested terminal-card exception](../bug-fix/2026-09-05-nested-terminal-cards.md) |
| Forbidden | new cards, visual redesign, interaction redesign, or PTC dispatch rich-card enhancements except the [nested terminal-card exception](../bug-fix/2026-09-05-nested-terminal-cards.md) and [compact tool details](2026-09-10-compact-tool-details.md) |
| Forbidden | compatibility dual-writing, version negotiation, or retention of the old `view` field |
## Terminology
@@ -64,7 +64,7 @@ The Host `ToolDefinition.presentCall`, `ToolDefinition.presentResult`, `ToolCall
**Client card model** means the pure props data under `ui-tool/src/client/tool/models/` consumed directly by `TerminalBlock`, `DiffBlock`, `ReadBlock`, `SearchBlock`, `WebBlock`, or `ToolRow`.
**Specialized card** means the structured terminal, diff, read, search, or web body. Titles, summaries, status dots, and ordinary IN/OUT text remain part of the generic tool row.
**Specialized card** means the structured terminal, diff, read, search, or web body. Titles, summaries, lifecycle styling, business glyphs, and ordinary IN/OUT text remain part of the generic tool row.
**Equivalent** means that the same supported input produces the user-visible result and interaction pinned by the existing component, assembly, and browser evidence. It does not require the same intermediate TypeScript types or internal calls.
@@ -301,7 +301,7 @@ The Client terminal model derives existing `TerminalBlock` props from the tool n
| Input | Preserved result |
|---|---|
| running standard `bash`/`pwsh` foreground call | terminal prompt, description, cwd, and running state |
| successful standard foreground call | terminal output, exit code/signal, and success or failure status dot |
| successful standard foreground call | terminal output, exit code/signal, and settled state presentation |
| `run_in_background:true` | Generic row and raw result |
| tool execution error | Generic IN/OUT and error summary |
| running persistent `bash`/`pwsh` | terminal prompt |
@@ -22,7 +22,7 @@ Host presenter 与 Client keyed renderer 分担展示会形成对同一事件的
## Decision
下述展示对等要求不包含已独立批准的[嵌套 terminal 卡片修复](../bug-fix/2026-09-05-nested-terminal-cards.zh.md);其他展示与所有权约束全部保留。
下述展示对等要求不包含已独立批准的[嵌套 terminal 卡片修复](../bug-fix/2026-09-05-nested-terminal-cards.zh.md)和[紧凑工具详情](2026-09-10-compact-tool-details.zh.md);其他展示与所有权约束全部保留。
Session Remote journal 只下发原始、已验证、可持久化的 Session event。`session.page` 和 `session.follow` 不解析工具参数,不查询 Tools registry,不恢复 presenter scope,不执行 `presentCall`/`presentResult`,也不构造或克隆任何 tool view。
@@ -51,7 +51,7 @@ Host 的 `ToolDefinition.presentCall`、`ToolDefinition.presentResult`、`ToolCa
| 保留 | Session 日志格式、Remote journal 生命周期与 Conversation identity/topology |
| 保留 | 现有 keyed slot、Generic fallback、Chat、Details 与 Trajectory 结构 |
| 禁止 | 新 Client presenter service、平行 registry 或 wire renderer id |
| 禁止 | 新卡片、视觉改版、交互改版或 PTC dispatch rich-card 增强,[嵌套 terminal 卡片例外](../bug-fix/2026-09-05-nested-terminal-cards.zh.md)除外 |
| 禁止 | 新卡片、视觉改版、交互改版或 PTC dispatch rich-card 增强,[嵌套 terminal 卡片例外](../bug-fix/2026-09-05-nested-terminal-cards.zh.md)和[紧凑工具详情](2026-09-10-compact-tool-details.zh.md)除外 |
| 禁止 | 为兼容保留双写、版本协商或旧 `view` 字段 |
## 术语
@@ -64,7 +64,7 @@ Host 的 `ToolDefinition.presentCall`、`ToolDefinition.presentResult`、`ToolCa
**Client card model**指 `ui-tool/src/client/tool/models/` 下直接供 `TerminalBlock`、`DiffBlock`、`ReadBlock`、`SearchBlock`、`WebBlock` 或 `ToolRow` 使用的纯 props 数据。
**专用卡片**指 terminal、diff、read、search 与 web 的结构化正文;标题、摘要、状态点和普通 IN/OUT 文本仍属于通用工具行。
**专用卡片**指 terminal、diff、read、search 与 web 的结构化正文;标题、摘要、生命周期样式、业务图形和普通 IN/OUT 文本仍属于通用工具行。
**对等**指同一受支持输入产生由现有组件、组装与浏览器证据固定的用户可见结果和交互,不要求相同的中间 TypeScript 类型或内部函数调用。
@@ -301,7 +301,7 @@ Client terminal model 从工具名称、调用参数、结果 content、error
| 输入 | 保持的结果 |
|---|---|
| 标准 `bash`/`pwsh` 前台 running | terminal prompt、description、cwd、running 状态 |
| 标准前台 success | terminal output、exit code/signal、成功或失败状态点 |
| 标准前台 success | terminal output、exit code/signal 与结算状态呈现 |
| `run_in_background:true` | Generic 行与原始结果 |
| 工具执行 error | Generic IN/OUT 与错误摘要 |
| persistent `bash`/`pwsh` running | terminal prompt |
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-24-browser-token-authentication.md
2026-08-24-browser-token-authentication.md: cdfed1f250de6def39e386cf9fcd2e536c6c97e4
2026-08-24-browser-token-authentication.zh.md: 29f3a6c0a2d83ed1c17b9c0bc5988c9472741f3f
2026-08-24-browser-token-authentication.md: 47d6afe1bf4cd8169eb4e8ec92a30bf0fee32c8a
2026-08-24-browser-token-authentication.zh.md: 13ecf55e41c7bb1455177bdd6a87a3a83d94ca6f
@@ -12,9 +12,9 @@ The Web Host runs tool-capable Sessions with the current operating-system user's
`dsh-client-connection` authenticates the complete Host API before dispatch. Every API Proxy method, Remote unary call, generic Connection channel, and Remote WebSocket stream requires the same browser session; endpoint ownership and method names do not alter authority. The existing Host/Origin checks run first and retain their DNS-rebinding and cross-site-request role, returning 403 when they fail. A trusted Host without a valid browser session receives 401. The browser-trust rules remain owned by the [carrier-level browser trust decision](2026-07-28-api-browser-trust-boundary.md).
Each Host process generates a random launch token, retained by the root application context across Connection hot reloads. `dsh-web-app` prints and opens the normal root URL with that token in the query once per process. `frontend-static` asks Connection to authorize index responses: only `GET /?token=...` exchanges the process token for a cookie, then redirects to clean `/`; the token is not accepted on API paths or in an Authorization header. An obsolete token paired with a valid cookie redirects to clean `/`. Missing and invalid credentials receive one minimal 401 response. Static non-index assets remain public.
Each Host process generates a random launch token, retained by the root application context across Connection hot reloads. `dsh-web-app` prints and opens the normal root URL with that token in the query once per process. `frontend-static` asks Connection to authorize index responses: only `GET /?token=...` exchanges the process token for a cookie, then redirects to clean `./`; the token is not accepted on API paths or in an Authorization header. An obsolete token paired with a valid cookie redirects to clean `./`. Missing and invalid credentials receive one minimal 401 response. Static non-index assets remain public.
The cookie is a signed, authority-bound bearer. Its deterministic name and signed payload both include the normalized hostname plus port, so one Harness home can run independent Web ports without cookie collisions. The payload carries safe-integer issue and expiry times under an absolute lifetime; `cookieMaxAgeDays` defaults to 30. The cookie is host-only, `Path=/`, `HttpOnly`, and `SameSite=Strict`. It omits `Secure` because the shipped server uses loopback HTTP. There is no logout operation or reverse-proxy-specific handling.
The cookie is a signed, authority-bound bearer. Its deterministic name and signed payload both include the normalized hostname plus port, so one Harness home can run independent Web ports without cookie collisions. The payload carries safe-integer issue and expiry times under an absolute lifetime; `cookieMaxAgeDays` defaults to 30. The cookie is host-only, `Path=/`, `HttpOnly`, and `SameSite=Strict`. It omits `Secure` because the shipped server uses loopback HTTP. There is no logout operation.
The HMAC secret is a versioned `grant` record at `client-connection/browser-session` in `ctx.credentials`; the local provider stores it in `$DSH_HOME/.credentials.yaml`. Connection loads or creates the record during activation and retains the secret for synchronous request verification. An active Connection continues using its loaded secret if the durable record changes; the next activation loads the replacement or creates a missing record, so deleting the record and restarting the process revokes every existing cookie. Invalid owner payloads fail loud instead of being replaced. The launch token itself is never persisted and changes on every process start, while an unexpired cookie remains valid across restarts on the same authority.
@@ -12,9 +12,9 @@ Web Host 以当前操作系统用户的权限运行具有工具能力的 Session
`dsh-client-connection` 在分发前认证完整 Host API。每个 API Proxy 方法、Remote 一元调用、通用 Connection channel 和 Remote WebSocket stream 都要求同一个浏览器会话;endpoint 所有权与方法名称不改变 authority。既有 Host/Origin 校验先执行,继续负责 DNS rebinding 和跨站请求防御,失败时返回 403。Host 可信但没有有效浏览器会话时返回 401。浏览器信任规则仍由[载体级浏览器信任决策](2026-07-28-api-browser-trust-boundary.zh.md)持有。
每个 Host 进程生成随机启动令牌,并由应用根 context 跨 Connection 热重载保留。`dsh-web-app` 每个进程只打印并打开一次 query 中带该令牌的普通根 URL。`frontend-static` 请求 Connection 授权 index 响应:只有 `GET /?token=...` 会把进程令牌交换为 cookie,再重定向到干净的 `/`;API 路径和 Authorization header 都不接受该令牌。过时令牌如果同时带有有效 cookie,会重定向到干净的 `/`。缺失与无效凭据得到同一份最小 401 响应。非 index 静态资产保持公开。
每个 Host 进程生成随机启动令牌,并由应用根 context 跨 Connection 热重载保留。`dsh-web-app` 每个进程只打印并打开一次 query 中带该令牌的普通根 URL。`frontend-static` 请求 Connection 授权 index 响应:只有 `GET /?token=...` 会把进程令牌交换为 cookie,再重定向到干净的 `./`;API 路径和 Authorization header 都不接受该令牌。过时令牌如果同时带有有效 cookie,会重定向到干净的 `./`。缺失与无效凭据得到同一份最小 401 响应。非 index 静态资产保持公开。
cookie 是签名且绑定 authority 的 bearer。确定性名称与签名 payload 都包含规范化 hostname 和 port,因此同一 Harness home 可以在不同 Web port 运行而不发生 cookie 冲突。payload 在绝对有效期内携带安全整数形式的签发与过期时间;`cookieMaxAgeDays` 默认为 30。cookie 是 host-only、`Path=/`、`HttpOnly`、`SameSite=Strict`。随附服务器使用 loopback HTTP,因此不设置 `Secure`。这里没有 logout 操作或反向代理专用处理。
cookie 是签名且绑定 authority 的 bearer。确定性名称与签名 payload 都包含规范化 hostname 和 port,因此同一 Harness home 可以在不同 Web port 运行而不发生 cookie 冲突。payload 在绝对有效期内携带安全整数形式的签发与过期时间;`cookieMaxAgeDays` 默认为 30。cookie 是 host-only、`Path=/`、`HttpOnly`、`SameSite=Strict`。随附服务器使用 loopback HTTP,因此不设置 `Secure`。这里没有 logout 操作。
HMAC 密钥是 `ctx.credentials` 中位于 `client-connection/browser-session` 的版本化 `grant` 记录;本地提供方将其存入 `$DSH_HOME/.credentials.yaml`。Connection 在激活期间加载或创建该记录,并保留密钥以同步校验请求。持久记录发生变化后,当前 Connection 继续使用已加载的密钥;下一次激活会加载替换记录或创建缺失记录,因此删除记录并重启进程会撤销全部既有 cookie。无效 owner payload 会明确失败,而不是被覆盖。启动令牌本身绝不持久化并在每次进程启动时变化;未过期 cookie 则能在相同 authority 上跨重启继续有效。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.md
2026-08-25-electron-desktop-packaging-and-updates.md: c4940942a8d5188faf1846158873c052a43e1fc1
2026-08-25-electron-desktop-packaging-and-updates.zh.md: 011624980f6f7239a33fb2b6c6803646b7d41dd3
2026-08-25-electron-desktop-packaging-and-updates.md: a985e1c91a1ccdb9c4aab0bc519974f9b6fafd4a
2026-08-25-electron-desktop-packaging-and-updates.zh.md: 17ace865477d4fae3650b56d6725be07c7bd0e0f
@@ -40,6 +40,8 @@ The Desktop Host exposes the shared Web plugin manager for its reserved profile
The renderer uses `nodeIntegration: false`, `contextIsolation: true`, and `sandbox: true`. Preload provides boot readiness and failure reporting, native directory selection, theme synchronization, and Windows menu and appearance adapters. It exposes no raw `ipcRenderer`, filesystem access, shell commands, or pnpm arguments. Electron menus and native dialogs use typed English or Chinese copy with English fallback; Windows follows the main document’s language. The shared Web plugin manager owns its client copy.
Update confirmations use the shell static origin because they must remain available without a ready Web Host. Their packaged documents and assets retain the same method and path restrictions as other local static assets.
## Filesystem layout
```text
@@ -73,6 +75,8 @@ The [immediate-window decision](2026-09-09-desktop-immediate-window-and-direct-s
`DSH_DESKTOP_AUTO_UPDATE_ENV` selects the test deployment by default or the production deployment for both the target-specific generic-provider URL and COS destination. Release automation supplies the test HTTPS origin through `DOWNLOAD_TEST_ORIGIN` and each deployment's bucket through `DOWNLOAD_TEST_COS_BUCKET` or `DOWNLOAD_PROD_COS_BUCKET`; keeping mutable test routing and COS storage identities out of source lets deployment infrastructure change without a code release, while the public production origin remains fixed. Packaging resolves only the public updater URL, disables electron-builder publishing, removes every COS credential field from its subprocess environment, and writes a completion record only after electron-builder and every signing or notarization hook succeeds. Target upload additionally requires the selected bucket, then requires the completion record, root dsh version, Desktop version, version-derived channel metadata, artifact names, sizes, and SHA-512 values to agree before it reads the selected credentials or sends data. It uploads immutable versioned updater payloads and any separate blockmaps before replacing the channel metadata emitted by electron-builder, and it never deletes historical objects. Stable versions use the `latest` metadata name; prereleases use the first semantic-version prerelease identifier. NSIS embeds its blockmap in the signed executable; the macOS ZIP carries a separate blockmap. Both let electron-updater download changed blocks when supported, while application replacement and the local pnpm package operation remain separate operations.
Test releases require `DOWNLOAD_TEST_RELEASE_ID` from the platform dotenv file. Their feeds and binaries share `dsh-desk/<release-id>/`; production retains its fixed directories. The ID is part of the packaged update URL, so upload rejects a different ID. Internal distribution can supply a new installer after rotation; installed clients retain their existing feed. Random directories reduce guessing but provide no authorization for link holders. The [release configuration](../../../../apps/desktop/README.md#upload-updates) defines generation and reuse.
## Security and release policy
Core dsh and the private Desktop Host come only from the signed application resource tree. Plugin installation forwards package specs to pnpm, including local and remote sources, but never accepts raw pnpm commands. pnpm owns dependency resolution and the profile’s `allowBuilds` policy; the Host loads activated bundles.
@@ -40,6 +40,8 @@ Desktop Host 为保留 profile 提供共享 Web 插件管理器,并通过启
渲染进程使用 `nodeIntegration: false`、`contextIsolation: true` 和 `sandbox: true`。Preload 提供启动就绪与失败报告、原生目录选择、主题同步,以及 Windows 菜单和外观适配。它不暴露原始 `ipcRenderer`、文件系统访问、shell 命令或 pnpm 参数。Electron 菜单与原生对话框使用类型化中英文文案,并以英文回退;Windows 跟随主文档语言。共享 Web 插件管理器负责自身客户端文案。
更新确认使用 shell 静态 origin,因为 Web Host 尚未就绪时也必须可用。其打包文档和资源保留与其他本地静态资源相同的方法和路径限制。
## 文件系统布局
```text
@@ -73,6 +75,8 @@ Electron 更新只使用一个 `electron-updater` 发布流和签名 `electron-b
`DSH_DESKTOP_AUTO_UPDATE_ENV` 默认为测试部署,也可以选择生产部署,并同时决定目标专用的 generic-provider URL 与 COS 目标。发布自动化通过 `DOWNLOAD_TEST_ORIGIN` 提供测试 HTTPS origin,并通过 `DOWNLOAD_TEST_COS_BUCKET` 或 `DOWNLOAD_PROD_COS_BUCKET` 提供各部署的 bucket;可变的测试路由与 COS 存储身份不写入源码,部署基础设施变更时无需发布新代码,而公开的生产 origin 仍固定。打包只解析公开更新 URL、禁止 electron-builder 发布、从子进程环境中删除每个 COS 凭据字段,并且只有在 electron-builder 以及每个签名或公证 hook 成功后才写入完成记录。目标上传还必须提供所选 bucket,随后会先要求完成记录、根 dsh 版本、Desktop 版本、根据版本得出的频道元数据、产物名称、大小与 SHA-512 全部一致,再读取所选凭据或发送数据。它先上传不可变且带版本的更新载荷与所有独立 blockmap,最后替换 electron-builder 生成的频道元数据,并且不会删除历史对象。稳定版本使用 `latest` 元数据名称,预发布版本则使用语义化版本的第一个预发布标识符。NSIS 把 blockmap 嵌入已签名的可执行文件,macOS ZIP 则使用独立 blockmap;两者都让 electron-updater 在平台支持时只下载变化的数据块,而应用替换与本地 pnpm 包操作仍是两个独立操作。
测试发布必须从平台 dotenv 文件提供 `DOWNLOAD_TEST_RELEASE_ID`。清单和安装包共用 `dsh-desk/<release-id>/`,生产环境保留固定目录。ID 是打包更新 URL 的一部分,因此上传会拒绝不同的 ID。轮换后可通过内部分发提供新安装包,已安装客户端保留原清单地址。随机目录降低被猜中的概率,但不对持有链接者进行鉴权。[发布配置](../../../../apps/desktop/README.zh.md#upload-updates)定义生成与复用方式。
## 安全与发布策略
核心 dsh 和私有 Desktop Host 只来自签名应用的资源树。插件安装把包规格交给 pnpm,包括本地和远程来源,但不接受原始 pnpm 命令。pnpm 负责依赖解析和 profile 的 `allowBuilds` 策略;Host 加载已启用的 bundle。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-25-session-observations-and-projection-owned-client-state.md
2026-08-25-session-observations-and-projection-owned-client-state.md: ff5628c3051086159d532b71ca3e98c1b41a51f8
2026-08-25-session-observations-and-projection-owned-client-state.zh.md: e433df28fec86564fe1c1e9e88686a5abe6b9549
2026-08-25-session-observations-and-projection-owned-client-state.md: 8573ea89fae6f52a9d28cc75261aea529df4f075
2026-08-25-session-observations-and-projection-owned-client-state.zh.md: 55c477e994df72ef8f6b6e4bb97b8b35e5783c9b
@@ -108,11 +108,11 @@ These distinctions prevent one overloaded `undefined` from representing cache mi
| Follow opening baseline | Complete for the Host composition | Exact opening cursor | Capability absent |
| Projection frame | One whole key | Event sequence carried by the frame | Not applicable |
The Client stores one row per key with its sequence number. A newer hint, baseline, or frame replaces a row; an equal or older input is ignored. Reconnect can therefore replace the event window without rolling back a projection frame that was already accepted at a later sequence.
The Client stores one row per key. A row the connected Host computed carries its sequence number: a newer baseline or frame replaces it, an equal or older input is ignored, and reconnect can therefore replace the event window without rolling back a projection frame that was already accepted at a later sequence. A list hint viewed from the persisted cache carries no comparable sequence number and yields to every Host-sequenced write ([projection cache listing identity and cached rows](2026-09-19-projection-cache-listing-identity-and-cached-rows.md)).
The list view reads the same per-Session store as the opened Session. Hints can populate title, preset, and other list presentation before follow completes; the opening baseline then converges that state without creating a second summary-only authority.
The per-Session Client projection store accepts list hints, the follow baseline, and later whole-value frames under one higher-sequence-wins rule. It never folds Session events. A baseline or frame may advance a hinted value, while an older cut cannot overwrite a newer row.
The per-Session Client projection store accepts list hints, the follow baseline, and later whole-value frames. Higher-sequence-wins applies among Host-sequenced values; cached list hints sit below all of them. It never folds Session events. A baseline or frame may advance a hinted value, while an older cut cannot overwrite a newer sequenced row.
Data that is not derived from one Session remains outside projections. `session/modelCatalog` owns the Host-generation model catalog, and `agentPresets/list` owns the configurable preset roster. A selector combines the relevant catalog with the Session's `modelSelection` or `agentPreset` projection only when both inputs are ready. During refresh it may retain the last complete catalog; before the first complete pair it reports loading instead of rendering a guessed name or availability verdict.
@@ -120,7 +120,7 @@ Client-local interaction state also remains local: loading and error status, an
### Domain applications
- **Title and list metadata.** Cached projection hints may render an existing title and determine blankness or recency. Missing hints leave those facts unknown; only the bounded small-log policy may resolve them during listing.
- **Title and list metadata.** Cached projection hints may render an existing title and determine blankness or recency. Missing hints leave those facts unknown; only the bounded small-log policy may resolve them during listing. The Client reconciles list rows with the current metadata projection: nonblank evidence excludes a Session from blank reuse, and the later prompt timestamp supplies recency. Projection stores outlive lazy Client Session instances, so instantiation reads retained metadata even before a list row is available. A stale list response cannot override a newer history or control projection.
- **Model selection.** `model/selection` records a complete provider, model, and optional reasoning effort. `modelSelection` distinguishes the last request's route from a later selection pending consumption by a request header.
- **Agent preset.** The projection initializes from immutable Session metadata and advances on preset-selection events. A missing or `null` value is not replaced with the deployment default for an existing Session.
- **Subagent identity.** The `subagent` unit remains the sole descriptor interpreter. Listing obtains candidates from the shared corpus and resolves values through live state, projection cache, or an observation rather than scanning events itself.
@@ -108,11 +108,11 @@ Projection 的三种交付状态含义不同:
| Follow opening baseline | 对当前 Host composition 完整 | 精确 opening cursor | Capability 不存在 |
| Projection frame | 单个完整 key | Frame 携带的 event sequence | 不适用 |
Client 为每个 key 保存带 sequence number 的一行。更新的 hint、baseline 或 frame 会替换 row;相同或更旧的输入被忽略。因此 reconnect 可以替换 event window,而不会回退已经在更晚 sequence 接受的 projection frame。
Client 为每个 key 保存一行。由建连 Host 算出的行带 sequence number:更新的 baseline 或 frame 会替换它,相同或更旧的输入被忽略,因此 reconnect 可以替换 event window,而不会回退已经在更晚 sequence 接受的 projection frame。从持久化缓存看出来的 list hint 不带可比的 sequence number,让位于所有 Host 排序过的写入([Projection cache 只读面按 lifecycle 身份匹配,客户端 store 区分 cached 与 sequenced 行](2026-09-19-projection-cache-listing-identity-and-cached-rows.zh.md))。
List view 与已打开 Session 读取同一个 per-Session store。Hints 可以在 follow 完成前填充 title、preset 和其他 list presentation;opening baseline 随后收敛这份状态,而不会建立第二套 summary-only authority。
每个 Session 的 Client projection store 按一条 higher-sequence-wins 规则接收 list hints、follow baseline 和后续 whole-value frame。它从不折叠 Session event。Baseline 或 frame 可以推进 hinted value,较旧切面不能覆盖较新的 row。
每个 Session 的 Client projection store 接收 list hints、follow baseline 和后续 whole-value frame。higher-sequence-wins 只在 Host 排序过的值之间生效;cached list hint 排在它们全部之下。它从不折叠 Session event。Baseline 或 frame 可以推进 hinted value,较旧切面不能覆盖较新的 sequenced row。
不由单个 Session 派生的数据不进入 projection。`session/modelCatalog` 持有当前 Host generation 的 model catalog,`agentPresets/list` 持有可配置 preset roster。Selector 只在相应 catalog 与 Session 的 `modelSelection` 或 `agentPreset` projection 均就绪后组合两者。刷新时可以保留上一份完整 catalog;第一次获得完整输入前显示 loading,而不是展示猜测的名称或可用性结论。
@@ -120,7 +120,7 @@ Client 本地交互状态也继续留在本地:loading 和 error 状态、打
### 领域应用
- **Title 与 list metadata。** Cached projection hints 可以渲染已有 title,并判断 blankness 或 recency。Hints 缺失时这些事实保持未知;listing 期间只有有界小日志策略可以解析它们。
- **Title 与 list metadata。** Cached projection hints 可以渲染已有 title,并判断 blankness 或 recency。Hints 缺失时这些事实保持未知;listing 期间只有有界小日志策略可以解析它们。Client 使用当前 metadata projection 对账列表行:非空白事实会将 Session 排除在空白复用之外,较晚的 prompt 时间用于最近活动排序。Projection store 的生命周期长于惰性创建的 Client Session 实例,因此实例化时即使尚无列表行,也会读取已保留的元数据。过期列表响应不能覆盖较新的 history 或 control projection。
- **Model selection。** `model/selection` 记录完整 provider、model 和可选 reasoning effort。`modelSelection` 区分上一请求使用的 route,以及等待 request header 消费的较晚 selection。
- **Agent preset。** Projection 从不可变 Session metadata 初始化,并随 preset-selection event 推进。对于现有 Session,缺失或 `null` 值不会替换成部署默认值。
- **Subagent identity。** `subagent` unit 仍是唯一 descriptor interpreter。Listing 从共享 corpus 获得 candidate,并通过 live state、projection cache 或 observation 解析值,不自行扫描 event。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.md
2026-08-28-subprocess-native-containment.md: 64fd59e13ea38d7f6e066d5dadec4be830c9c0af
2026-08-28-subprocess-native-containment.zh.md: 59e51c5a5c19f55058eb3881928d4f218a22a59c
2026-08-28-subprocess-native-containment.md: 88acee2d41fab878a0e50380cbb87eaece8e7296
2026-08-28-subprocess-native-containment.zh.md: 53406f99531953a0330abf08be150da845f37aab
@@ -46,6 +46,8 @@ Linux falls back before target execution when the exact bootstrap, modern readab
Normal Cordis disposal starts direct-result and range observation independently, requests termination, and waits for every owned range. Consumer teardown does not inspect an ordinary PID; it retains the original operation or startup error while attempting terminate and final wait, and preserves cleanup failures in the consumer's existing error order. A confirmed empty range permanently disables later signalling against stale identities.
Native Windows fixtures retain the same Job-owned termination and final wait on success and failure. A descendant PID is observation-only: issuing `taskkill` after its exit can terminate an unrelated process that has reused the number, including another concurrent test worker.
During a JavaScript-observable host exit, `LocalSubprocessRuntime` synchronously force-terminates every still-live handle without promises or timers. Linux sends the existing direct fallback kill and the exact scope kill; Windows kills the runner so its only Job handle closes; PTY fallback scans remain best effort. Per-handle failures are contained and do not change the host's exit result. Termination modes in which JavaScript cannot run remain outside this listener's guarantee.
## Existing decisions and supersession
@@ -46,6 +46,8 @@ selector 是 per-spawn locator 或 sentinel,不是凭据或持久格式。Linu
正常 Cordis dispose 会独立启动 direct-result 与 range observation、请求终止,并等待每个自有 range。消费方 teardown 不检查普通 PID;它会保留原始 operation 或 startup error,同时尝试 terminate 与 final wait,并按消费方既有错误顺序保留 cleanup failure。range 一旦被确认为空,就会永久禁止后续向陈旧 identity 发送信号。
原生 Windows fixture 在成功和失败时都通过同一个 Job owner 请求终止并完成最终等待。后代 PID 仅用于观察:进程退出后再执行 `taskkill`,可能终止已复用该编号的无关进程,包括并发运行的其他测试 worker。
在 JavaScript 可观察的 host exit 期间,`LocalSubprocessRuntime` 会同步强制终止每个仍存活的句柄,不使用 Promise 或 timer。Linux 会发送既有 direct fallback kill 与准确 scope kill;Windows 会终止 runner,使其唯一 Job handle 关闭;PTY fallback 扫描仍是 best effort。每个句柄的失败相互隔离,也不改变宿主退出结果。JavaScript 无法运行的终止形态不属于该 listener 的保证。
## Existing decisions and supersession
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md
2026-08-31-released-session-format-migrations.md: d566459af64f4177ed0135813e75aaf77480823c
2026-08-31-released-session-format-migrations.zh.md: f82b406117691197d808111f1c8aa4c722d4bab2
2026-08-31-released-session-format-migrations.md: 00672af920cf8369022b4274c1c50552f5407a7e
2026-08-31-released-session-format-migrations.zh.md: 9404d8f454b7c17be69ae064c405c4dcc0794f88
@@ -59,6 +59,7 @@ JSONL record
→ v0-to-v1 stage
→ v1-to-v2 stage
→ v2-to-v3 stage
→ v3-to-v4 stage
→ current event collector
```
@@ -66,6 +67,8 @@ The chain contains no `flatMap`, spread expansion, intermediate event array, or
### Adjacent version ownership
The [V3-to-V4 specification](../../../../packages/session/session-format-v3-to-v4/README.md#v3-to-v4-specification) specifies parent catalog completion: it advances the header version, preserves admitted events and inherited cuts, and appends missing own catalog entries from child evidence. It reuses the frozen V3 codec from V2→V3. Generation-aware delivery validation prevents a historical acknowledgement from acquiring a current watermark meaning solely through the header change; earlier edges retain their own source-admission policies.
The [V2-to-V3 delivery guards](../../../../packages/session/session-format-v2-to-v3/README.md#delivery-guards) prevent a marker ignored in the source generation from becoming an active upload watermark merely because the header changes. Python release smoke checks generated logs against the source `SESSION_FORMAT_VERSION` independently of generation-neutral golden comparison, so coherent filenames and headers cannot conceal an outdated writer.
The [V2-to-V3 README](../../../../packages/session/session-format-v2-to-v3/README.md#v2-to-v3-specification) is the single specification for that edge's transformations, preservation, and refusal; its separate [native admission section](../../../../packages/session/session-format-v2-to-v3/README.md#native-v3-admission) prevents current-only capabilities from being mistaken for historical transformations. The released V2 codec remains owned by V1→V2 and is reused, not copied. The [system-prompt](2026-09-02-system-prompt-as-surface-node.md), [PTC](../feature/2026-06-15-ptc.md), and [canonical-envelope](2026-09-06-v3-canonical-session-envelopes.md) notes retain their independent rationale, not duplicate conversion specifications. The [format-version cookbook](../../../../docs/cookbook/adding-a-session-format-version.md) owns package wiring, current consumers, snapshot successors, and validation commands.
@@ -76,7 +79,7 @@ Preset renames cover the creation header and every selection event because the l
A source inherited count can be unknown before EOF: V2 derives it from seed markers, and V1→V2 can change cardinality. The chain passes that absence to the next stage instead of fabricating a count. The [V2-to-V3 inheritance rules](../../../../packages/session/session-format-v2-to-v3/README.md#sequence-references) support this case; older stages that require a header-supplied count still refuse when it is absent. This permits seeded multi-hop restoration without retaining an intermediate artifact array.
The [version and release-status reference](../../../../docs/session-format-status.md) owns the published-format record and identifies the code’s writer authority. Released formats retain their semantics; committed generations remain byte-preserved during migration. A subsequent structural change requires the next adjacent edge under the [versioning rule](2026-08-10-session-log-version-mechanism.md), not an amendment to a released conversion. Ordinary event additions follow that rule’s required-event refusal mechanism rather than automatically allocating a version. A current-format file does not rerun its incoming migration; integration tests use isolated disposable homes and unchanged historical inputs.
The [version and release-status reference](../../../../docs/session-format-status.md) owns the published-format record and identifies the code’s writer authority. Released formats retain their semantics; committed generations remain byte-preserved during migration. A breaking change to the released target format requires the next adjacent edge under the [versioning rule](2026-08-10-session-log-version-mechanism.md). Incoming-converter fixes affect later historical conversions, not existing target files; the [cookbook](../../../../docs/cookbook/adding-a-session-format-version.md) owns compatibility review and alpha support policy. Ordinary event additions follow that rule’s required-event refusal mechanism rather than automatically allocating a version. Integration tests use isolated disposable homes and unchanged historical inputs.
The [committed-corpus inventory](../../../../packages/test-support/llm-replay/tests/session-format-corpus-inventory.ts) identifies deliberately unsupported historical conversions by source path, generation, and exact refusal reason. Retaining those artifacts must not force chronology-changing migration or permit a blanket skip: every listed artifact must still raise the typed migration refusal, and unlisted artifacts must restore. Native current-generation fixtures cannot be classified as unsupported, because they do not traverse an incoming edge. Headerless test-harness protocol examples remain a separate explicit class. The corpus test checks source bytes after both successful and refused restoration; it does not rewrite historical evidence to satisfy the current reader.
@@ -86,6 +89,8 @@ Each released codec creates a row decoder with explicit `strict` or `recoverable
The v0-to-v1 edge preserves logical headers, sequence numbers, references, timestamps, and payloads except for bounded released-v0 normalizations. It translates the retired `steering/message` and `compact/*` event names, accepts a released `llm/retry` after its matching `step/end`, deterministically supplies a missing `llm/retry.retryId` per turn/step/provider/policy chain, and supplies one deterministic `compactionId` across a legacy compaction group that omitted it. The v1-to-v2 edge owns attempt folding and reference remapping, and emits only settled v2 events. It splits a legacy goal-sourced user message into `goal/change` plus the original model-visible message. It also inserts an interrupted `turn/end` for the bounded released restart in which an open turn with no open step is followed by a non-empty `next-turn` inbox splice and the next numbered `turn/start`.
The V3-to-V4 edge applies the same bounded restart repair to logs written directly as V3. Keeping this repair in the incoming edge preserves [native V4 lifecycle validation](2026-09-17-native-v4-read-validation.md); accepting repeated starts there would admit producer corruption in new logs. Insertions require local-reference and inherited-cut remapping, while captured generations retain their original coordinates. The [format specification](../../../../packages/session/session-format-v3-to-v4/README.md#sequence-references) owns the exact evidence and fields.
The catalog exposes one `createRestore()` operation for production, Worker, fixture, and replay callers. Recovery policy and final validation policy are chosen once at restore creation. Historical production uses recoverable source parsing with transformed-current validation; this validates the released current result after migration, while input that is already current receives only codec validation. Worker and fixture verification use strict parsing with full installed current restoration. A migration-stage or transformed-current validation refusal remains `SessionFormatUnsupportedMigrationError`; physical decoding failures remain corruption. Test support keeps only fixture-specific token and envelope materialization.
### JSONL integration
@@ -96,10 +101,14 @@ Current encoding is record based. The provider serializes about 1 MiB of plainte
The shipped `lib/worker.cjs` bundles its JavaScript workspace dependencies so each fresh verifier avoids resolving and compiling their runtime module graph. This is safe because the Worker communicates through plain request/result messages and shares no service or class identity with its host. The Host build applies the existing TypeScript and Typert transforms; the Client pass skips this Node-only package instead of replacing its worker with untransformed source. Native add-ons remain external. Verification, scheduler admission, termination, and durable publication still complete before writable open returns. The built-worker smoke copies the package manifest and worker into an isolated temporary package with ambient module paths removed, accepts a valid generation, and rejects an incorrect event count.
The [browser preview packer](../../../../packages/experimental/webworker-packer/README.md) prepares current successors for bundled Session data in Node, using same-root direct-child descriptors as parent catalog evidence. The browser host does not implement `node:worker_threads`, so this preparation preserves both immutable fixture generations and runtime JSONL’s Worker verification requirement.
Preparation forwards cancellation through source reads and observes it at the existing approximately 500 ms Decode yield boundary. Once `publish()` starts, encode, Worker verification, and publication do not receive caller cancellation and run to settlement; write open checks its caller signal again afterward. A published generation is never rolled back.
The Stage pipeline ends at one prepared current artifact. [Historical Session read preparation](2026-09-05-read-only-session-migration-preparation.md) defines how read open consumes that artifact immediately while write open performs encode, verification, and publication before returning append access.
The bulk V4 migration command shares one JSONL persistence Context across a bounded job queue, retaining the backend’s two-Worker verification limit and cache for reusing recently decoded logs. A child’s concurrent publication can change its parent’s selected child generation; the ordinary revision check refuses that attempt. The command defers only this source-change error for one sequential retry after the initial queue drains, collecting fresh evidence without relaxing publication checks or retrying unrelated failures.
### Durable format and publication rules
Canonical filenames encode physical format generation: v0 is `session.jsonl[.zstd]` and positive generations use `session.vN.jsonl[.zstd]`. Migration never moves, replaces, or deletes a committed generation and writes only the final current target; intermediate versions exist only as stage state.
@@ -122,6 +131,44 @@ Existing write handles retain the process-local claim and kernel-backed cross-pr
| Verification repeats on the main thread | At most two complete-generation Workers | Keeps verification CPU off the main thread |
| Production and fixture migration use different APIs | Catalog `createRestore()` with explicit policies | One decoder/chain implementation |
### Parent catalog prerequisites
Runtime child-failure handling in this paragraph is partially superseded by [session-local catalog preparation](../bug-fix/2026-09-19-session-local-subagent-migration.md): opening the parent still completes its catalog from direct-child reads, but unreadable children do not block healthy parent history. V3→V4 completes parent child discovery from retained direct-child headers and own descriptors. Storage supplies compact descriptor evidence to the catalog assembly, which binds it into the V3→V4 stage factory; the Stage waits for the parent's final inherited cut before validating catalog payloads and deriving available own discovery facts. Nested seed markers discard inherited catalog candidates without interpreting their payloads. An existing catalog entry survives an unavailable descriptor, including a child that failed before its first step. Descriptor v1 denotes continuable mode; v2/v3 explicitly record mode. Available unambiguous discovery fields must agree with the parent; new complete entries require one supported descriptor. Missing, unsupported, or multiple descriptors preserve the logs and retain unknown-mode membership without inventing discovery fields, as recorded in [incomplete child evidence](../bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.md). JSONL rechecks related membership and source revisions when preparing, reusing, and publishing. Unreadable or unsupported headers are excluded from discovery; failures reading a recognized child preserve its header identity as unknown-mode membership. Source drift invalidates preparation; read open retries once automatically, while write open refuses publication. Diagnostics retain the offending child path and distinguish unsupported migration evidence from malformed child data. The collector reports child decoding and descriptor failures as warnings with the child path; cancellation and source-consistency checks still stop preparation. The V0–V3-only catalog avoids recursive child migration. Current V4 reads skip that scan but apply the same own-catalog field, uniqueness, and current delivery ownership checks as strict restoration. See the [format specification](../../../../packages/session/session-format-v3-to-v4/README.md).
Raw parent/child migration tests retain 24 historical creation-time conflicts as rejection evidence. Detached controls align only the child creation time and prove event-preserving restoration, including a published child without a descriptor. Headless/ACP and SDK snapshot adapters validate live parent/child clocks before normalization and preserve their equality during refresh; committed predecessor generations remain unchanged.
Historical public revisions combine the parent’s physical revision with a fingerprint of every selected canonical path and its filesystem revision. Parent-only tokens cannot identify catalog changes supplied by children; a root fingerprint preserves metadata-only `stat`/`list` and includes unreadable or unsupported members without a new persistent index. Unrelated changes also invalidate historical caches, and obtaining the token costs a root scan. Current tokens stay file-local. The preparation memo keeps physical parent revisions and separately validates child membership and revisions.
The generic format interfaces carry only the artifact being restored. Parent-specific catalog assembly replaces the V3→V4 declaration with a closure over the collected child evidence; it reuses the generated codec, migration, and validation inventory. The assembly compiles one short migration chain per parent preparation, with independent stage state per restore. Binding at assembly keeps child evidence out of generic restore options and stage inputs without moving discovery or source revalidation into the migration. Static header and native current reads need no child evidence; an unbound historical body restore refuses rather than assuming an empty child set.
<a id="catalog-scan-measurements"></a>
### Catalog scan measurements
The package-local [diagnostic](../../../../packages/session/session-persistence-jsonl/tests/catalog-migration.perf.ts) measures one raw V3 parent, four direct children with 1,000 events each, and 0/100/1,000 unrelated header-only V3 Sessions. Each sample creates its own temporary corpus and Cordis context in a fresh process, runs stat → cold read → memo read → write publication → current warm read, and disposes the context and files. It uses built workspace exports under plain Node; fixture creation is outside the measured intervals. These are filesystem-cache-warm observations after fixture creation, not disk-cold latency or a performance gate.
On macOS arm64 with Node v26.0.0, all three samples per size are below in milliseconds. Read-only opens do not publish: membership scanning remains proportional to corpus size even when decoding is memoized. The 1,000-unrelated medians are 84.14 ms for stat, 237.97 ms for cold read, 110.98 ms for memo read, 268.46 ms for publication, and 0.41 ms for the same-process current read. These measurements document the cost; they establish no speedup claim or cross-host threshold.
| Unrelated Sessions | Historical stat | Cold read | Memo read | Publish | Current warm read |
|---:|---:|---:|---:|---:|---:|
| 0 | 5.17 | 19.00 | 1.36 | 42.88 | 0.47 |
| 0 | 3.30 | 14.87 | 1.45 | 35.75 | 0.41 |
| 0 | 3.18 | 16.71 | 1.50 | 34.51 | 0.47 |
| 100 | 29.44 | 77.48 | 17.01 | 60.66 | 0.43 |
| 100 | 12.42 | 42.71 | 13.69 | 60.19 | 0.43 |
| 100 | 11.19 | 41.06 | 12.61 | 57.18 | 0.39 |
| 1000 | 84.14 | 237.97 | 109.86 | 284.96 | 0.48 |
| 1000 | 99.41 | 259.88 | 123.50 | 268.46 | 0.41 |
| 1000 | 75.40 | 236.64 | 110.98 | 247.69 | 0.39 |
Run from the repository root after building the runtime:
```sh
pnpm run build:lib:host
cd packages/session/session-persistence-jsonl
node --input-type=module -e 'import { build } from "tsdown"; await build({ config: false, entry: ["tests/catalog-migration.perf.ts"], outDir: ".artifacts/perf", tsconfig: false, dts: false, deps: { neverBundle: [/^@deepseek-ai\//], onlyBundle: false } })'
node .artifacts/perf/catalog-migration.perf.mjs
```
## Verification
The migration specification requires evidence for transformations, preservation, and refusal separately. Direct-edge and native V3 tests cannot establish seeded multi-hop publication: preceding assistant-stream folding changes source coordinates before V3 inserts system events. Tests through the real catalog and JSONL provider therefore need raw and compressed V0/V1 inputs, mapped references and inherited cuts, publish/reopen equivalence, unchanged predecessor bytes, and no intermediate generations. Coverage percentages alone cannot prove those cross-stage relationships; combined assertions must compare the resulting history and refusal effects.
@@ -59,6 +59,7 @@ JSONL record
→ v0-to-v1 stage
→ v1-to-v2 stage
→ v2-to-v3 stage
→ v3-to-v4 stage
→ current event collector
```
@@ -66,6 +67,8 @@ Chain 中不存在 `flatMap`、spread expansion、中间 event array 或 schedul
### 相邻版本所有权
[V3 到 V4 规范](../../../../packages/session/session-format-v3-to-v4/README.zh.md#v3-to-v4-specification)规定父目录补全:它推进 header 版本,保留已接纳的事件与继承切点,并根据子 Session 证据追加缺失的自身目录记录。它复用 V2→V3 中冻结的 V3 codec。按 generation 校验 delivery 可防止历史确认仅因 header 变化而获得当前水位含义;更早的 migration edge 保留各自的来源准入策略。
[V2 到 V3 投递保护](../../../../packages/session/session-format-v2-to-v3/README.zh.md#delivery-guards)防止源代中被忽略的标记仅因头部变化就成为有效上传水位。Python 发布冒烟测试独立于跨代 golden 比较,按源代码中的 `SESSION_FORMAT_VERSION` 检查生成日志,因此文件名与 header 自洽不能掩盖过期 writer。
[V2 到 V3 README](../../../../packages/session/session-format-v2-to-v3/README.zh.md#v2-to-v3-specification)是该迁移边转换、保留与拒绝规则的单一规范真源;单列的[原生准入章节](../../../../packages/session/session-format-v2-to-v3/README.zh.md#native-v3-admission)避免将仅当前版本支持的能力误认为历史转换。已发布 V2 codec 仍归 V1→V2 所有,并被复用而非复制。[系统提示词](2026-09-02-system-prompt-as-surface-node.zh.md)、[PTC](../feature/2026-06-15-ptc.zh.md)和[规范信封](2026-09-06-v3-canonical-session-envelopes.zh.md)记录保留各自独立依据,而非重复转换规范。[格式版本实操手册](../../../../docs/cookbook/adding-a-session-format-version.zh.md)负责包接线、当前消费方、快照后继代际与验证命令。
@@ -76,7 +79,7 @@ Chain 中不存在 `flatMap`、spread expansion、中间 event array 或 schedul
源继承数量在 EOF 前可能未知:V2 从种子标记推导它,而 V1→V2 可以改变事件数量。迁移链将这种缺失传递给下一个 Stage,而不伪造数量。[V2 到 V3 继承规则](../../../../packages/session/session-format-v2-to-v3/README.zh.md#sequence-references)支持此情况;需要 header 提供数量的旧 Stage 仍在数量缺失时拒绝。这使有种子的多跳恢复无需保留中间产物数组。
[版本与发布状态参考](../../../../docs/session-format-status.zh.md)拥有已发布格式记录,并指明代码中的写入器真源。已发布格式保留其语义;迁移期间已提交代际的字节保持不变。后续结构性变更必须按[版本规则](2026-08-10-session-log-version-mechanism.zh.md)添加下一条相邻迁移边,而非修改已发布转换。普通事件新增遵循该规则的必需事件拒绝机制,而非自动分配版本。当前格式文件不会重新执行入边迁移;集成测试使用隔离、可丢弃的 home 和未变更的历史输入。
[版本与发布状态参考](../../../../docs/session-format-status.zh.md)拥有已发布格式记录,并指明代码中的写入器真源。已发布格式保留其语义;迁移期间已提交代际的字节保持不变。对已发布目标格式的破坏性变更必须按[版本规则](2026-08-10-session-log-version-mechanism.zh.md)添加下一条相邻迁移边。传入转换器修复影响之后的历史转换,不影响已有目标格式文件;[cookbook](../../../../docs/cookbook/adding-a-session-format-version.zh.md)拥有兼容性评审和 alpha 支持政策。普通事件新增遵循该规则的必需事件拒绝机制,而非自动分配版本。集成测试使用隔离、可丢弃的 home 和未变更的历史输入。
[已提交语料清单](../../../../packages/test-support/llm-replay/tests/session-format-corpus-inventory.ts) 按源路径、代际与精确拒绝原因标识有意不支持的历史转换。保留这些产物不能迫使迁移改变时序,也不能允许统一跳过:每个清单中的产物仍必须抛出类型化迁移拒绝,未列入的产物必须还原。原生当前代际 fixture 不经过入边,因此不能被归为不支持。没有版本 header 的测试框架协议示例保持为独立的显式类别。语料测试在还原成功和拒绝后都检查源字节;它不通过改写历史证据来满足当前 reader。
@@ -86,6 +89,8 @@ Chain 中不存在 `flatMap`、spread expansion、中间 event array 或 schedul
v0-to-v1 除了有限的 released-v0 归一化外,会保留逻辑 header、seq、引用、时间戳与 payload。它转换已移除的 `steering/message` 与 `compact/*` 事件名称,接受出现在对应 `step/end` 之后的已发布 `llm/retry`,按 turn/step/provider/policy chain 为缺失的 `llm/retry.retryId` 确定性补值,并为省略 id 的旧 compaction group 确定性补充同一个 `compactionId`。v1-to-v2 负责 attempt folding 与引用重写,并且只 emit 已结算的 v2 event。它会把旧的 goal 来源 user message 拆成 `goal/change` 与原本的模型可见 message。它还会为一种有限的已发布 restart 插入 interrupted `turn/end`:一个没有 open step 的 open turn 后出现非空 `next-turn` inbox splice,随后直接开始编号连续的下一轮。
V3-to-V4 边对直接写成 V3 的日志应用同样的有限 restart 修复。将修复保留在迁入边,可以维持[原生 V4 生命周期校验](2026-09-17-native-v4-read-validation.zh.md);若在那里接受重复 start,新日志也会允许生产者损坏。插入事件要求重映射本地引用与继承截点,而捕获代际保留原坐标。[格式规范](../../../../packages/session/session-format-v3-to-v4/README.zh.md#sequence-references)负责定义具体证据和字段。
Catalog 为 production、Worker、fixture 与 replay 暴露同一个 `createRestore()`。Recovery policy 与最终 validation policy 在 restore 创建时一次确定。Historical production 使用 recoverable source parsing 与 transformed-current validation;这种策略会在迁移后校验已发布 current 结果,而已经是 current 的输入只接受 codec 校验。Worker 与 fixture verification 使用 strict parsing 与已安装 current 格式的完整 restoration。Migration stage 或 transformed-current validation 的拒绝会保持为 `SessionFormatUnsupportedMigrationError`;物理解码失败仍是 corruption。Test support 只保留 fixture 自身需要的 token 和 envelope materialization。
### JSONL 串联
@@ -96,10 +101,14 @@ Current encode 以单条 record 为单位。Provider 在主线程每个 slice
发布的 `lib/worker.cjs` 将 JavaScript workspace 依赖一起打包,使每个新 verifier 无需解析并编译它们的运行时模块图。Worker 只通过普通 request/result 消息通信,与 host 不共享 service 或 class identity,因此可以这样处理。Host build 应用现有 TypeScript 与 Typert 转换;Client pass 跳过这个 Node-only package,不会用未经转换的源代码覆盖 worker。Native add-on 保持 external。Verification、scheduler admission、termination 与 durable publication 仍在 writable open 返回前完成。Built-worker 冒烟测试把 package manifest 与 worker 复制到隔离的临时 package,移除环境中的模块搜索路径,接受有效 generation,并拒绝错误的 event count。
[浏览器 preview 打包器](../../../../packages/experimental/webworker-packer/README.zh.md)在 Node 中为随包 Session 数据准备当前代际后继,并使用同一根目录中直属子 Session 的 descriptor 作为父目录证据。浏览器 host 未实现 `node:worker_threads`,因此打包阶段的准备同时保留不可变 fixture 代际与运行时 JSONL 的 Worker 校验要求。
Preparation 会把 cancellation 传给 source read,并在现有的约 500 ms Decode yield 边界观察它。`publish()` 一旦开始,encode、Worker verification 与 publication 不接收 caller cancellation,并运行到终态;write open 会在之后再次检查 caller signal。已经发布的 generation 绝不会回滚。
Stage pipeline 终止于一份 prepared current artifact。[历史 Session 只读迁移准备](2026-09-05-read-only-session-migration-preparation.zh.md)定义 read open 如何立即消费该 artifact,以及 write open 如何在返回 append 权限前完成 encode、verification 与 publication。
批量 V4 迁移命令在有界任务队列中共享一个 JSONL persistence Context,保留 backend 的双 Worker 校验上限,以及复用近期已解码日志的缓存。子 Session 并发发布可能改变父 Session 选中的子代际;普通修订检查会拒绝这次尝试。命令只把这种源变化错误延后到初始队列清空后串行重试一次,重新收集证据,不放宽发布检查,也不重试无关失败。
### Durable format 与 publication 规则
规范文件名编码 physical format generation:v0 使用 `session.jsonl[.zstd]`,正 generation 使用 `session.vN.jsonl[.zstd]`。Migration 不会移动、覆盖或删除任何 committed generation,并且只写最终 current target;中间版本只存在于 stage state。
@@ -122,6 +131,44 @@ POSIX publication 使用 hard-link creation 加目录 sync;Windows 使用 no-o
| 主线程重复执行完整 verification | 最多两个 complete-generation Worker | Verification CPU 不占用主线程 |
| Production 与 fixture 使用不同 migration API | Catalog `createRestore()` 加显式 policy | 只保留一套 decoder/chain 实现 |
### 父目录前置事实
本段的运行时子会话错误处理部分由[逐会话目录准备](../bug-fix/2026-09-19-session-local-subagent-migration.zh.md)取代:打开父会话仍通过读取直属子会话补齐目录,但不可读子会话不再阻断健康父历史。V3→V4 使用保留的直属子 Session header 和自身 descriptor 补齐父 Session 的子代理发现记录。存储将紧凑的 descriptor 证据提供给 catalog 组装处,由其绑定到 V3→V4 stage 工厂;Stage 等待父 Session 的最终继承截点确定后,才校验目录 payload 并导出可获得的自身发现事实。嵌套的 seed 标记丢弃继承目录候选,不解释其 payload。已有目录记录允许 descriptor 不可用,包括在首次 step 之前失败的子 Session。Descriptor v1 表示 continuable 模式;v2/v3 显式记录模式。可获得且明确的发现字段必须与父目录一致;新增完整记录要求一个受支持的 descriptor。Descriptor 缺失、版本不受支持或存在多个时,保留日志与未知模式成员关系,不编造发现字段,理由见[不完整子目录证据](../bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.zh.md)。JSONL 在准备、复用和发布时重新检查关联成员与来源修订。不可读或不支持的 header 不参与发现;读取已识别子会话失败时,其 header 身份保留为未知模式成员关系。来源变化使准备缓存失效;只读打开自动重试一次,写打开则拒绝发布。诊断保留出错子日志的路径,并区分不支持的迁移证据与畸形子数据。收集器将子解码和 descriptor 失败报告为带子路径的警告;取消与来源一致性检查仍中止准备。仅支持 V0–V3 的目录避免递归迁移子 Session。当前 V4 读取跳过该扫描,但会执行与严格恢复相同的自身目录字段、唯一性及当前投递归属检查。见[格式规范](../../../../packages/session/session-format-v3-to-v4/README.zh.md)。
原始父子迁移测试保留 24 个历史创建时间冲突作为拒绝证据。独立的内存对照只对齐子创建时间,证明恢复保留事件,包括已发布但没有 descriptor 的子 Session。Headless/ACP 与 SDK 快照适配器在规范化之前校验实时父子时钟,并在刷新时保持两者相等;已提交的前代文件保持不变。
历史格式的公开修订号组合父日志物理修订号与每个所选规范路径及其文件系统修订号的指纹。仅父日志令牌无法标识子日志提供的目录变化;全库指纹让 `stat`/`list` 保持只读元数据,也纳入不可读或不支持的成员,无需新增持久化索引。无关变化也会使历史缓存失效,获取令牌需要扫描根目录。当前格式令牌仍只取决于自身文件。准备缓存保留父日志物理修订号,并独立校验子成员集合与修订。
通用格式接口只传递正在恢复的 artifact。每个父 Session 的 catalog 组装将 V3→V4 声明替换为捕获已收集子 Session 证据的闭包,并复用生成的 codec、迁移与校验清单。每次父 Session 准备只编译一条短迁移链,每次恢复拥有独立的 stage 状态。在组装处绑定证据,使通用恢复选项和 stage 输入不必携带子 Session 证据,同时保留存储对发现与来源重验的所有权。静态 header 和原生当前格式读取不需要子 Session 证据;未绑定的历史正文恢复会拒绝,不会假定子 Session 集合为空。
<a id="catalog-scan-measurements"></a>
### 目录扫描测量
包内[诊断脚本](../../../../packages/session/session-persistence-jsonl/tests/catalog-migration.perf.ts)测量一个原始 V3 父日志、四个各有 1,000 条事件的直属子日志,以及 0/100/1,000 个只有 header 的无关 V3 Session。每个样本在新进程中创建独立临时日志库与 Cordis 上下文,按 stat → 冷读 → 缓存读取 → 写入发布 → 当前格式热读顺序执行,最后释放上下文并删除文件。脚本在普通 Node 下使用已构建的工作区导出;创建夹具不计入测量区间。这些结果是夹具创建后的文件系统缓存热态观察,不代表磁盘冷读延迟,也不是性能门禁。
以下为 macOS arm64、Node v26.0.0 下各规模的全部三个样本,单位为毫秒。只读打开不发布后继:即使解码结果已缓存,成员扫描仍与日志库规模成正比。1,000 个无关日志时,中位数分别为 stat 84.14 ms、冷读 237.97 ms、缓存读取 110.98 ms、发布 268.46 ms,以及同进程当前格式读取 0.41 ms。这些测量记录成本,不证明性能提升,也不设跨主机阈值。
| 无关 Session | 历史 stat | 冷读 | 缓存读取 | 发布 | 当前格式热读 |
|---:|---:|---:|---:|---:|---:|
| 0 | 5.17 | 19.00 | 1.36 | 42.88 | 0.47 |
| 0 | 3.30 | 14.87 | 1.45 | 35.75 | 0.41 |
| 0 | 3.18 | 16.71 | 1.50 | 34.51 | 0.47 |
| 100 | 29.44 | 77.48 | 17.01 | 60.66 | 0.43 |
| 100 | 12.42 | 42.71 | 13.69 | 60.19 | 0.43 |
| 100 | 11.19 | 41.06 | 12.61 | 57.18 | 0.39 |
| 1000 | 84.14 | 237.97 | 109.86 | 284.96 | 0.48 |
| 1000 | 99.41 | 259.88 | 123.50 | 268.46 | 0.41 |
| 1000 | 75.40 | 236.64 | 110.98 | 247.69 | 0.39 |
在仓库根目录构建运行时后执行:
```sh
pnpm run build:lib:host
cd packages/session/session-persistence-jsonl
node --input-type=module -e 'import { build } from "tsdown"; await build({ config: false, entry: ["tests/catalog-migration.perf.ts"], outDir: ".artifacts/perf", tsconfig: false, dts: false, deps: { neverBundle: [/^@deepseek-ai\//], onlyBundle: false } })'
node .artifacts/perf/catalog-migration.perf.mjs
```
## 验证
迁移规范要求分别提供转换、保留与拒绝的证据。直接迁移边和原生 V3 测试不能证明有种子的多跳发布:前代 assistant 流折叠会在 V3 插入系统事件前改变源坐标。因此,经过真实目录与 JSONL 提供方的测试需要原始及压缩的 V0/V1 输入、映射后的引用和继承切点、发布/重新打开等价性、前代字节不变,以及不产生中间代。覆盖率百分比本身不能证明这些跨阶段关系;组合断言必须比较结果历史与拒绝效果。
@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-01-jobs-absorb-activity-record.md
2026-09-01-jobs-absorb-activity-record.md: 508608f7eb694bf131bad9a25e856d32c7079559
2026-09-01-jobs-absorb-activity-record.zh.md: 2c157d951042f1cd6f8a3a66129773bc99cd5b02
@@ -0,0 +1,48 @@
# Agent Note: The job registry absorbs the observation record; the standalone activity seam is removed
Status: implemented
Superseded: the record declaration (`JobStart.record`, `RecordingJob`, `readRecord`, `pumpJobOutput`) and the split wire (`SessionJob.record` on the session control stream, `ctx.jobOutput`) described below were consolidated into one output ring and direct operations on `ctx.jobs` — see [the jobs seam consolidation](2026-09-03-jobs-seam-consolidation.md). The argument for absorbing the standalone activity seam into `ctx.jobs` still holds.
English | [中文](2026-09-01-jobs-absorb-activity-record.zh.md)
## Problem
The [activity observation seam](../feature/2026-08-24-activity-observation-seam.md) shipped live output streaming as a second registry beside `ctx.jobs`: producers registered the same work twice (a job for lifecycle, an activity for observation), kept the two terminal states consistent by hand (`observeBackgroundActivity` waited for `proc.done` before mapping the outcome so a pump failure could not freeze a wrong terminal state), correlated the rows through `ActivityCorrelation.jobId`, and wrapped every observation call in registry-absent degradation branches. The Web client maintained two rosters (session-control `jobs` frames and the activity control stream) and joined them per row. Measured before the merge: eight pairing call sites, and one producer population — background bash/pwsh (both registries), PTY sends and subagent delegations (jobs only), foreground workflow (activity only).
The one activity without a job was the foreground workflow mirror. Everything else the split enabled — independent observers, absolute-offset reads, bounded retention — is a property of the record, not of the registry split.
## Decision
`ctx.jobs` owns the observation record; `packages/activity/`, `packages/api/activity-controller`, and the correlation vocabulary are removed.
- **`JobStart.record?: true`** declares an observable output record. `run(job)` now receives the job's producer face — `RecordingJob { id, append(text, {channel?, gapBefore?}), updateDetail(detail) }` for a `record: true` start, the plain `RunningJob { id, updateDetail(detail) }` otherwise — so the id is issued before the starter runs (a throwing starter still registers nothing; its ordinal is skipped). Writes staged inside the starter surface at the registration commit. `updateDetail` works for every job and gives `job_list` a live progress line; `append` without a record declaration logs and drops.
- **There is no `end`.** Job settlement — the producer outcome, a kill, or teardown — is the record's only close: it trims retention to the settled cap and fires the final `onOutput` signal. The dual-settlement pairing (`ActivityHandle.end` first-wins against teardown force-ends) is deleted, not reimplemented.
- **`readRecord(id, from, caller)`** is the non-consuming multi-reader view (absolute UTF-8 offsets, `lossy` below the retained window), fenced like every other job read; it never marks the job `reported`. The model-facing `readOutput` cursor is untouched — the two projections serve different readers and stay separate by design.
- **`jobs-local`** absorbs the chunk ring (`retainBytes` 256 KiB live, `settledRetainBytes` 16 KiB settled), and `pumpJobOutput` replaces `pumpActivityOutput` beside the seam. Producers fold the pump's final drain into `hooks.done` so the record holds its last bytes before settlement closes it.
- **The wire splits by role.** `SessionJob.record` (present exactly for record jobs) marks a row observable on the session control stream; `job.follow({sessionId?, jobId, from?})` on the `job` namespace of `api-job-controller` streams anchor/output/status frames with the fenced read resolved from the request's session. `api-activity-controller` is deleted; its roster stream is redundant (the jobs frames are the roster) and its observe machinery lives in `api-job-controller` (`observe.ts`, client `ctx.jobOutput`), which resolves its Remote faces in its own apply.
- **`ui-activity` returns to its upstream name `ui-jobs`** and renders one roster: `jobsBySession` rows, expandable exactly when `record` is present. The two-roster join is deleted.
- **Foreground workflow loses its live panel deliberately.** `tool-workflow`'s activity mirror is removed; a foreground run surfaces through its recorded run/member lifecycle events only, and the per-line `workflow/phase` / `workflow/log` narration has no observer until workflow gains `run_in_background` and registers a record job. Jobs stay a pure background registry — no `foreground` mode bit, no model-invisible rows, no run-less rows.
## Alternatives considered
- **A `foreground: true` job mode** to keep the foreground workflow panel: it needs two coupled enforcement points (reported-at-birth and a `job_list` filter) whose divergence double-delivers or leaks rows to the model, for one edge feature replaceable by `run_in_background`.
- **Serving model reads from the record** (deleting `readOutput`): model reads are producer-formatted (truncation and spill notices, sandbox markers) and consuming; the record is raw, channel-labeled, and non-consuming. Unifying them moves producer-specific formatting into the registry or noise into the observer stream.
- **Keeping the split with shared implementation** (a common registry library): it removes the duplicated skeleton but keeps the real costs — double registration, terminal-state pairing, correlation, a second wire roster, and a second package family.
The [prior seam note](../feature/2026-08-24-activity-observation-seam.md)'s argument against durable session events and control-stream framing for live output still holds and carries over unchanged: the record is process-local observation state, never a session event, so "model-visible ⟺ logged" is untouched.
## Review corrections
Review of the merged design settled five points that the sections above leave implicit:
- **The pump's wait holds constant resources.** `pumpJobOutput` subscribes to the producer's `done` once and keeps one pending timer; settlement clears the timer and wakes the current wait. Racing the same pending promise every poll round retained a reaction per round until the job ended — a day-long job at the default cadence accumulated over a million closures.
- **Client disposal waits for carrier quiescence.** The `ctx.jobOutput` effect disposer is async and awaits every open `RemoteStream.dispose()`, because Cordis awaits async disposers and a fiber that reported unloaded while an old iterator was still closing could overlap the next plugin instance under HMR.
- **The roster flags a record, it does not count bytes.** `SessionJob.record: true` replaced `outputTotal`: `append` commits no roster change (only lifecycle commits fire `onJobsChanged`), so a mirrored count went stale the moment output arrived, and broadcasting the roster per append was rejected as traffic for a number no row needed. The live offsets ride the observation stream's `opened` anchor.
- **The record is a best-effort live preview.** Producers copy stdout and stderr per poll round, so writes inside one window land stdout first; pipe capture cannot recover the true interleaving (only a PTY or `2>&1` can), and the client concatenates chunks regardless of `channel`. `channel` stays on the wire because it is what makes that limit visible and what a distinct stderr rendering would need.
- **The declaration types the face.** `JobStart` is a union discriminated by `record`: `RecordingJobStart.run` receives `RecordingJob`, `PlainJobStart.run` receives `RunningJob` without `append`, and the registry builds the plain face without the method — output the registry has nowhere to keep is unrepresentable instead of warned about and dropped.
- **Web e2e keep-alive is a barrier, not a clock.** The background command holds on a file the test owns in the job's cwd and the scenario kills it explicitly, so no CI stall can settle the job first; each scenario is one test, never a chain of tests sharing a job id.
## Consequences
Every observable row is a killable job; a pure-observation surface with no lifecycle would need a new home (harness diagnostics belong to the inspector plane, not here). A future remote job provider must implement lifecycle and record together. The record's retention config lives on `jobs-local`; the observation wire's cadence config (`observeFlushMs`, `observeMaxFrameBytes`) lives on `api-job-controller`.
@@ -0,0 +1,48 @@
# Agent Note:job 注册表吸收观测 record;独立的 activity seam 被移除
Status: implemented
Superseded:下文描述的 record 声明(`JobStart.record`、`RecordingJob`、`readRecord`、`pumpJobOutput`)与拆分的线路(会话控制流上的 `SessionJob.record`、`ctx.jobOutput`)已收敛为一个输出环与 `ctx.jobs` 上的直接操作——见 [jobs seam 收敛](2026-09-03-jobs-seam-consolidation.zh.md)。把独立 activity seam 吸收进 `ctx.jobs` 的论证仍然成立。
[English](2026-09-01-jobs-absorb-activity-record.md) | 中文
## 问题
[activity 观测 seam](../feature/2026-08-24-activity-observation-seam.zh.md) 把实时输出流做成了 `ctx.jobs` 旁边的第二个注册表:生产者要把同一份工作登记两次(job 管生命周期,activity 管观测),手工保持两个终态一致(`observeBackgroundActivity` 要等 `proc.done` 才映射 outcome,防止 pump 失败把错误终态冻上去),通过 `ActivityCorrelation.jobId` 关联两行,并给每个观测调用包上注册表缺失的降级分支。Web 客户端维护两份名册(session-control 的 `jobs` 帧与 activity control 流)并逐行做 join。合并前实测:八个配对调用点;生产者人口为——后台 bash/pwsh(双注册表)、PTY 发送与 subagent 委托(仅 jobs)、前台 workflow(仅 activity)。
唯一没有 job 的 activity 是前台 workflow 镜像。拆分带来的其他一切——独立观察者、绝对偏移读取、有界保留——都是 record 的性质,不是注册表拆分的性质。
## 决策
`ctx.jobs` 拥有观测 record;`packages/activity/`、`packages/api/activity-controller` 与 correlation 词表被移除。
- **`JobStart.record?: true`** 声明可观测输出 record。`run(job)` 现在收到 job 的生产者面——`record: true` 的 start 收到 `RecordingJob { id, append(text, {channel?, gapBefore?}), updateDetail(detail) }`,其余收到普通的 `RunningJob { id, updateDetail(detail) }`——因此 id 在 starter 运行前签发(starter 抛出仍然不登记任何东西;序号被跳过)。starter 内暂存的写入在登记提交时可见。`updateDetail` 对所有 job 可用,让 `job_list` 也能看到实时进度行;未声明 record 的 `append` 记日志并丢弃。
- **没有 `end`。** job 结算——生产者 outcome、kill 或 teardown——是 record 唯一的关闭:裁剪到结算保留量并发出最后一个 `onOutput` 信号。双结算配对(`ActivityHandle.end` 与 teardown 强结的 first-wins 对账)被删除,而非重新实现。
- **`readRecord(id, from, caller)`** 是非消耗多读者视图(绝对 UTF-8 偏移,低于保留窗口为 `lossy`),围栏与其他 job 读取一致;永不置 `reported`。模型面的 `readOutput` 游标原样保留——两个投影服务不同读者,刻意保持分离。
- **`jobs-local`** 吸收块环(`retainBytes` 运行期 256 KiB,`settledRetainBytes` 结算后 16 KiB),`pumpJobOutput` 在 seam 旁替换 `pumpActivityOutput`。生产者把 pump 的末次排空折进 `hooks.done`,让 record 在结算关闭前握有最后的字节。
- **wire 按角色拆分。** `SessionJob.record`(恰在 record job 上存在)在会话控制流上标记行可观测;`api-job-controller` 的 `job` 命名空间上的 `job.follow({sessionId?, jobId, from?})` 流式发送 anchor/output/status 帧,围栏读取者由请求的 session 解析。`api-activity-controller` 删除;其名册流是冗余的(jobs 帧就是名册),observe 机制位于 `api-job-controller`(`observe.ts`,客户端 `ctx.jobOutput`),它在自己的 apply 里解析所依赖的 Remote 面。
- **`ui-activity` 改回上游名字 `ui-jobs`**,渲染单一名册:`jobsBySession` 行,恰在 `record` 存在时可展开。双名册 join 被删除。
- **前台 workflow 有意失去实时面板。** `tool-workflow` 的 activity 镜像被移除;前台 run 只通过已记录的 run/member 生命周期事件呈现,逐行的 `workflow/phase` / `workflow/log` 叙述在 workflow 获得 `run_in_background` 并登记 record job 之前没有观察者。jobs 保持纯后台注册表——没有 `foreground` 模式位、没有模型不可见行、没有无 run 行。
## 曾考虑的替代方案
- **`foreground: true` job 模式**以保留前台 workflow 面板:它需要两个耦合的执行点(出生即 reported 与 `job_list` 过滤),两者一旦分叉就会双投递或向模型泄漏行,而它换来的只是一个可被 `run_in_background` 替代的边缘特性。
- **用 record 服务模型读取**(删除 `readOutput`):模型读取是生产者格式化的(截断与 spill 提示、sandbox 标记)且消耗型;record 是原始、带 channel 标签、非消耗的。统一二者要么把生产者特有格式化搬进注册表,要么把噪声混入观察者流。
- **保留拆分但共享实现**(公共注册表库):它消除重复骨架,但保留真正的成本——双重登记、终态配对、correlation、第二条 wire 名册与第二个包族。
[先前 seam note](../feature/2026-08-24-activity-observation-seam.zh.md) 反对用持久会话事件或 control 流承载实时输出的论证仍然成立并原样沿用:record 是进程本地观测状态,从不是会话事件,「模型可见 ⟺ 已记录」不受影响。
## 评审修正
对已合并设计的评审确定了上文各节未言明的五点:
- **泵的等待资源恒定。** `pumpJobOutput` 对生产者的 `done` 只订阅一次,同一时刻只保留一个待触发的 timer;结算清掉 timer 并唤醒当前等待。此前每个轮询轮次都对同一个 pending promise 赛跑,每轮留下一个 reaction 直到任务结束——默认节奏下跑一天的任务会累积上百万个闭包。
- **客户端卸载等待载体静止。** `ctx.jobOutput` 的 effect disposer 是异步的,await 每条打开的 `RemoteStream.dispose()`:Cordis 会 await 异步 disposer,而一个在旧 iterator 仍在关闭时就宣告卸载完成的 fiber,在 HMR 下可能与下一个插件实例重叠。
- **名册标记 record,不计字节。** `SessionJob.record: true` 取代了 `outputTotal`:`append` 不提交名册变更(只有生命周期提交触发 `onJobsChanged`),镜像的计数在输出到达的瞬间就陈旧了;而为一个没有任何行需要的数字按 append 广播整份名册被否决。实时偏移随观测流的 `opened` 锚帧到达。
- **record 是尽力而为的实时预览。** 生产者按轮询轮次复制 stdout 与 stderr,同一窗口内的写入先 stdout 落地;pipe 捕获无法还原真实交错(只有 PTY 或 `2>&1` 可以),客户端拼接 chunk 时也不区分 `channel`。`channel` 留在 wire 上,因为它正是让这一限制可见、也是将来区分渲染 stderr 所需要的东西。
- **声明决定生产者面的类型。** `JobStart` 是按 `record` 判别的联合类型:`RecordingJobStart.run` 收到 `RecordingJob`,`PlainJobStart.run` 收到没有 `append` 的 `RunningJob`,注册表给普通 job 构造的面根本没有这个方法——注册表无处存放的输出成为不可表达,而不是警告后丢弃。
- **Web e2e 的保活是 barrier 而非时钟。** 后台命令守在测试拥有的、位于 job cwd 的文件上,由场景显式 kill,因此 CI 卡顿不可能先把任务结算掉;每个场景是一个测试,绝不是共享 job id 的测试链。
## 后果
每个可观测行都是可 kill 的 job;没有生命周期的纯观测面需要新的归属(harness 诊断属于 inspector 平面,不在这里)。未来的远端 job provider 必须同时实现生命周期与 record。record 的保留配置在 `jobs-local`;观测 wire 的节奏配置(`observeFlushMs`、`observeMaxFrameBytes`)在 `api-job-controller`。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-01-parent-owned-subagent-catalog.md
2026-09-01-parent-owned-subagent-catalog.md: 20e2dd035b41612592e7baeb55f89322dff02848
2026-09-01-parent-owned-subagent-catalog.zh.md: 6ec33e8be8d4373ba98a9934178a67e207ffb0bb
2026-09-01-parent-owned-subagent-catalog.md: 536ec33148bd675a974622b98ee23de812691dc0
2026-09-01-parent-owned-subagent-catalog.zh.md: ec78ff9e4b7d54a7309baf21c383ec5ff83a19c8
@@ -44,6 +44,6 @@ Current-writer snapshot expectations include catalog facts even when replay inpu
## Consequences
Session observations and client snapshots expose the direct-child list through `projections.values.subagentCatalog`. The projection change feed publishes a complete list when catalog state changes. Each view costs O(D), so D creations can incur O(D²) cumulative view work; this follows the existing projection mechanism. Direct-child and descendant listing still use the Session corpus and child identity projection.
Session observations and client snapshots expose the direct-child list through `projections.values.subagentCatalog`. The projection change feed publishes a complete list when catalog state changes. Each view costs O(D), so D creations can incur O(D²) cumulative view work; this follows the existing projection mechanism. `listChildren()` reads the parent projection through one live-preferred Session observation; descendant listing uses the Session corpus and child identity projection. [Web projection consumption](../simplification/2026-09-08-web-subagent-catalog-projections.md) owns browser loading and synchronization.
Backends that do not know the required event refuse the log under the existing Session event mechanism. Catalog projection does not reconstruct missing parent facts by scanning old child logs.
[Creation metadata exclusion](../simplification/2026-09-16-subagent-catalog-membership-only.md) records why model configuration is absent from catalog facts.
@@ -44,6 +44,6 @@ snapshot normalizer 会把 `childCreatedAt` 归零,因为它来自 process clo
## 后果
Session 观察和客户端快照通过 `projections.values.subagentCatalog` 暴露直接子级列表。目录状态变化时,projection 变更通知发布完整列表。每次视图计算成本为 O(D),因此 D 次创建的累计视图工作量可能为 O(D²);这沿用既有 projection 机制。直接子级和后代列表仍使用 Session 语料库与子级身份 projection。
Session observation 与 client snapshot 通过 `projections.values.subagentCatalog` 暴露直接子级列表。目录状态变化时,projection change feed 发布完整列表。每次 view 的成本为 O(D),因此 D 次创建可能累计产生 O(D²) 的 view 工作量;这沿用现有 projection 机制。`listChildren()` 通过一次 live-preferred Session observation 读取父 projection;后代枚举使用 Session 语料库与子身份 projection。[Web projection 消费决策](../simplification/2026-09-08-web-subagent-catalog-projections.zh.md) 说明浏览器加载与同步。
不认识该 required event 的 backend 会按既有 Session event 机制拒绝日志。目录投影不通过扫描旧子级日志来重建缺失的父级事实。
[创建元数据排除决策](../simplification/2026-09-16-subagent-catalog-membership-only.zh.md) 说明目录事实为何不包含模型配置。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-02-projcache-cross-version-read-compat.md
2026-09-02-projcache-cross-version-read-compat.md: 82f7f26d5cd0484f5adcd51db17748f41a298294
2026-09-02-projcache-cross-version-read-compat.zh.md: 58705ec8f15fbffff6af31872a69f1f19ef5e3c5
2026-09-02-projcache-cross-version-read-compat.md: 160c5c796bacc6f672b06acc0df4246d7008c52b
2026-09-02-projcache-cross-version-read-compat.zh.md: cff7742f6c1f887b905a2c139e4157782aef677c
@@ -39,7 +39,7 @@ Declared read compatibility — reads tolerate vouched-for older versions, write
5. **Identity matching is stricter than structural admission**: an absent `formatVersion` never matches a current Session, so predecessor rows cannot seed a projection and refold from the authoritative log. Once the format matches, `identityMatches` normalizes absent lineage to unseeded (`?? false` / `?? 0`): exact for an unseeded session, while a seeded expectation fails the match. Poisoned v5 homes therefore boot safely, but their unbound rows are not exposed as current values.
6. **Schema-validation backstop: `invalidRecords: 'backup-and-skip'` (declared by this domain only)**. A stored record that still fails to parse beyond read compatibility no longer refuses the whole domain: the domain layer calls the backend's `KvUnit.backupRecord` (json per-record implementation = rename the document to `<key>.json.bak.<YYYYMMDDHHmm>`, bytes kept, never read again), prints the concrete failure with `logger.error` (domain, table, key, destination, zod cause), and continues the open with the record absent; the next cold read rebuilds and rewrites that session's cache. **The policy is an explicit per-domain declaration and the default stays fail-loud** — other domains still refuse the whole load on invalid stored data, and a backend without `backupRecord` (single layout, row stores) also falls back to fail-loud. Naming history: quarantine → backup-and-skip (user ruling: the word must carry both "back up" and "skip", sharing its root with the `.bak` suffix; skip-backup was rejected because the CLI `--skip-X` convention reads it as "do not back up"). For this domain it supersedes the reset/destroy recovery path of the [2026-07-28 storage recovery proposal](../../proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.md), which stays live for authoritative and whole-medium damage.
7. **A predecessor title is a listing hint, not a fold shortcut**: Session-list startup remains metadata/cache-only and never opens cold log bodies. The log header is authoritative; a lifecycle-matching checkpoint is a durable-prefix witness that may lag but cannot lead the log. `cachedPredecessorTitle` therefore exposes only a predecessor `title` row that still passes the current title unit's `stateVersion` and schema. Both adjacent Session-format edges preserve title text. The hint uses `asOfSeq: -1` rather than the stored row sequence because a cardinality-changing log migration can remap that coordinate. Other predecessor rows remain hidden, and `hydratePrepared`/`coldSnapshot` retain strict format identity, because normalizers can change values such as `blank` or `lastPromptAt` even when storage is physically consistent.
7. **A predecessor title is a listing hint, not a fold shortcut**: Session-list startup remains metadata/cache-only and never opens cold log bodies. The log header is authoritative; a lifecycle-matching checkpoint is a durable-prefix witness that may lag but cannot lead the log. `cachedPredecessorTitle` therefore exposes only a predecessor `title` row that still passes the current title unit's `stateVersion` and schema. Both adjacent Session-format edges preserve title text. The hint carries the stored title row's own watermark as `asOfSeq`; because a cardinality-changing log migration can remap that coordinate, the Session list labels the block `kind: 'cached'` and the client never compares it with the connected Session's seqs ([listing identity and cached rows](2026-09-19-projection-cache-listing-identity-and-cached-rows.md)). Other predecessor rows remain hidden, and `hydratePrepared`/`coldSnapshot` retain strict format identity, because normalizers can change values such as `blank` or `lastPromptAt` even when storage is physically consistent.
### v3-v6 → v7 disposition
@@ -39,7 +39,7 @@ v4→v5 的唯一实质差异是 identity 新增两个 lineage 字段;v6 只
5. **identity 匹配比结构准入更严格**:缺失 `formatVersion` 的记录绝不匹配当前 Session,因此前代行不能播种投影,而会从权威日志重新折叠。格式匹配后,`identityMatches` 才把缺失 lineage 归一化为 unseeded(`?? false` / `?? 0`):对 unseeded 会话精确,对 seeded 期望则匹配失败。v5 投毒 home 因而可以安全启动,但其未绑定行不会作为当前值暴露。
6. **schema 校验兜底:`invalidRecords: 'backup-and-skip'`(仅本域声明)**。读兼容之外仍然解析失败的存量记录不再让整个域拒开:domain 层调用后端的 `KvUnit.backupRecord`(json per-record 实现=把文档改名为 `<key>.json.bak.<YYYYMMDDHHmm>`,字节留档、不再被读取),用 `logger.error` 打印具体失败信息(域名、表、键、移动去向、zod 失败原因),随后当该记录不存在继续启动;下一次冷读会重建并重写该会话的缓存。**该策略是域级显式声明,缺省仍为 fail-loud**——其他业务域的存量数据校验失败照旧整域拒载;后端没有 `backupRecord` 能力(single 布局、行存储)时也回退 fail-loud。命名沿革:quarantine → backup-and-skip(用户裁决:词要同时含"备份"与"跳过"两义,且与 `.bak` 后缀同源;skip-backup 因 CLI `--skip-X` 惯例存在"不备份"反读而弃用)。对本域而言,该策略取代了 [2026-07-28 存储恢复提案](../../proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.zh.md)中 reset/destroy 的恢复途径;该提案对权威介质与整介质损坏仍然有效。
7. **predecessor title 是列表 hint,而不是 fold shortcut**:Session list 启动保持 metadata/cache-only,绝不打开冷 log body。log header 是权威来源;生命周期匹配的 checkpoint 是 durable prefix witness,可以落后但不能领先日志。因此 `cachedPredecessorTitle` 只公开仍通过当前 title unit `stateVersion` 与 schema 的 predecessor `title` row。两条相邻 Session format edge 都保留 title 文本。该 hint 使用 `asOfSeq: -1`,而不使用存储 row 的序号,因为改变日志事件数量的迁移会重新映射该坐标。其他 predecessor row 继续隐藏,`hydratePrepared`/`coldSnapshot` 仍要求严格格式 identity,因为即使物理存储一致,normalizer 仍可能改变 `blank` 或 `lastPromptAt` 等值。
7. **predecessor title 是列表 hint,而不是 fold shortcut**:Session list 启动保持 metadata/cache-only,绝不打开冷 log body。log header 是权威来源;生命周期匹配的 checkpoint 是 durable prefix witness,可以落后但不能领先日志。因此 `cachedPredecessorTitle` 只公开仍通过当前 title unit `stateVersion` 与 schema 的 predecessor `title` row。两条相邻 Session format edge 都保留 title 文本。该 hint 的 `asOfSeq` 是存储 title row 自己的水位;因为改变日志事件数量的迁移会重新映射该坐标,Session list 把该 block 标为 `kind: 'cached'`,客户端不会拿它与建连 Session 的 seq 比较([只读面按 lifecycle 身份匹配与 cached 行](2026-09-19-projection-cache-listing-identity-and-cached-rows.zh.md))。其他 predecessor row 继续隐藏,`hydratePrepared`/`coldSnapshot` 仍要求严格格式 identity,因为即使物理存储一致,normalizer 仍可能改变 `blank` 或 `lastPromptAt` 等值。
### v3-v6 → v7 处置
Loaded 100 of 5142 files, more files were not shown because too many files have changed in this diff. Show more