Commit Graph

9791 Commits

Author SHA1 Message Date
creatixchu
3c6188fb99 test(web): cover Goal actions across turns 2026-08-07 11:51:18 +08:00
Yichen Jiang
b22c853de5 Merge branch 'stack/agent-profiles-6-cordis-agent' into stack/agent-profiles-7-docs 2026-08-07 11:48:54 +08:00
Yichen Jiang
1f9cf819a5 Merge branch 'stack/agent-profiles-5-web-ui' into stack/agent-profiles-6-cordis-agent 2026-08-07 11:48:53 +08:00
Yichen Jiang
d099a24cb1 fix(web): resume the preset the log records, and serialize the switch
Six review findings on the select surface, all reachable from the wire:

**Resume read the header, not the log.** The switch was recorded as
`agent-preset/selected` and every projection resolved from it, but `agentFor`
still composed from `inspected.meta.agentPreset` — the value written once at
creation. A blank session that switched and then ran turns came back after a
restart under the ORIGINAL preset, restoring that history under the tool set it
was not produced with, which is the mismatch this feature exists to prevent.
`inspected` already carries the events.

**Cold summaries dropped the preset entirely.** `summarizeCold` hand-copied
three header fields and omitted the fourth, so a restored session reported no
preset and the picker showed the deployment default. It now uses the same
projection the attached path does.

**`select` had no gate.** Two concurrent selects both passed the blank check;
the second `unmountPresetFor` then found no record, because the first had
already removed it, and both mounts installed into one agent layer. Selects on
one session now queue, and the blank check is re-read inside the queue. This is
not turn admission — a `session.prompt` racing a switch is the agent loop's to
reserve — but it closes the select-versus-select tear-down.

**A same-id restore was skipped.** The roster is a live directory, so "the same
inputs that worked a moment ago" does not hold: a changed file is exactly how a
same-id reselect fails, and skipping the restore left the agent with no
composition at all.

**`writable` was dead state**, initialized true and never set, so the row could
never disable. It now carries `settings.describe`'s bit — a browser that may
not write settings sees the current default and no control, rather than one
whose write answers `settings-not-exposed`.

**`list` was documented as id-ordered.** It is root-precedence order with each
root's own presets sorted, first root to supply an id winning.
2026-08-07 11:45:10 +08:00
Yichen Jiang
abd2e2973b Merge branch 'master' into worktree/provider-credential-lifecycle 2026-08-07 11:37:41 +08:00
Chinesezjc
596eb686ec Merge pull request #649 from deepseek-harness/feat/todo-multi-in-progress
feat(todo): allow several in_progress todos at once
2026-08-07 11:34:00 +08:00
Yichen Jiang
6edd98e2b2 Merge branch 'master' into worktree/provider-credential-lifecycle 2026-08-07 11:28:22 +08:00
Chinesezjc
0567727410 Merge remote-tracking branch 'origin/master' into feat/todo-multi-in-progress
# Conflicts:
#	apps/cli/config/base.cordis.yml
#	packages/client/ui-conversation/README.i18n.yaml
2026-08-07 11:27:24 +08:00
Chinesezjc
20651af6a6 Merge branch 'master' into feat/py-types-code-mode 2026-08-07 11:23:41 +08:00
Chinesezjc
e15e3db2b1 docs(code-runtime-worker): correct __debug__ attribution in binding-global comment
The inline comment called every dunder entry "the Python backend's" slot,
but `__debug__` is a CPython compile-time constant, not a seeded slot;
align the comment with the seam declaration, which is the single home for
why each entry is reserved.
2026-08-07 11:20:05 +08:00
Chinesezjc
efdc78cba3 docs(code-runtime): fix remaining dunder/backend wording and document exports in README
- index.ts: RESERVED_ERROR_MEMBERS JSDoc now says `__x__` (non-empty
  middle), matching DUNDER_MEMBER and the derivative docs; and
  PORTABLE_RESERVED_WORDS is described as covering portable *target*
  languages (Python is a later-PR backend, not yet shipped).
- types.ts + type-equiv docs: CodeBindingNamespace.global points to
  RESERVED_BINDING_GLOBALS by name with an `e.g.` sample rather than
  enumerating the set (single home), no longer implying `__debug__` is a
  seeded slot.
- Agent Note: separate `__debug__` (CPython compile-time constant) from
  the seeded bootstrap globals.
- README (en/zh): document the four exported exclusion sets and the
  portable identifier/naming contract — no cross-package link needed.
- Regenerate cordis services catalog for the shifted source line.
2026-08-07 11:20:05 +08:00
Chinesezjc
c05db227d5 docs(code-runtime): sync Agent Note and public JSDoc with the shipped seam
- Agent Note: correct the stale worker-adoption paragraph — the worker
  consumes the seam constants directly by name (no RESERVED_WORDS /
  RESERVED_ERROR_PROPERTIES re-alias) — and describe DUNDER_MEMBER as
  `__x__` (non-empty middle).
- types.ts: document RESERVED_BINDING_GLOBALS on CodeBindingNamespace.global
  (names like `__dsh_main__` pass the identifier rule but are still refused)
  and the non-empty-middle dunder rule on memberNameProperty; propagate to
  the type-equiv derivative docs (both languages, re-recorded).
2026-08-07 11:20:05 +08:00
Chinesezjc
4dc2b197d7 test(code-runtime): correct DUNDER_MEMBER edge cases for the __.+__ pattern
`____` has an empty middle between the two `__` pairs and does not match
`/^__.+__$/`; assert that (not a match) and add `__x__` as the shortest
real dunder form that does.
2026-08-07 11:20:05 +08:00
Chinesezjc
0e6fe760c1 docs(code-runtime): regenerate cordis services catalog for shifted source line 2026-08-07 11:20:05 +08:00
Chinesezjc
eb03aa86fe refactor(code-runtime): address seam review — drop worker aliases, tighten dunder
- Worker consumes PORTABLE_RESERVED_WORDS / RESERVED_ERROR_MEMBERS by
  their seam names directly, dropping the local re-alias (symmetry with
  the other two imported constants).
- Split the reserved-vs-duplicate diagnostics: a backend-owned global now
  reports "reserved binding global", not the misleading "duplicate".
- DUNDER_MEMBER uses `__.+__` so a bare `__` (empty middle, not a real
  CPython dunder) is not matched; add coverage.
- Worker misuse tests add `a$b` (second-char `$`) and `lambda` (Python
  keyword) so the identifier narrowing and reserved-word adoption are
  each pinned directly, not only transitively.
- Clarify the seam JSDoc (dunder-vs-explicit-set wording, Python backend
  is a later stack PR) and record in the Agent Note the obligation to
  widen RESERVED_BINDING_GLOBALS when the bootstrap seeds more globals.
2026-08-07 11:20:05 +08:00
Chinesezjc
b5578c026e docs(code-runtime): align derived docs with the portable-identifier JSDoc
Update the type-equiv blocks in docs/core-data-structures/code-runtime
(both languages, re-recorded) to mirror the new CodeBindingNamespace /
CodeBindingErrorClass JSDoc, and regenerate the cordis services catalog
for the shifted source line. Keeps the worker-only intro until the
Python backend package exists.
2026-08-07 11:20:05 +08:00
Chinesezjc
5d4cea9dc1 feat(code-runtime): own portable-identifier exclusions at the seam
Move the reserved-word, reserved-global, reserved-error-member, and
dunder exclusion sets from the worker backend up to the code-runtime
seam package, and narrow the portable identifier subset to drop the
JS-only `$`. Every backend now imports one contract so a binding
namespace list valid on one backend is valid on all.

Delivers only the seam extension and the worker's adoption; the Python
backend, py-types renderer, and Code Mode language dispatch are later
PRs in the stack that depend on these exports.
2026-08-07 11:20:05 +08:00
Yichen Jiang
8d6b6098cb fix(web): start a new preset blank instead of copying the default
Copying is already its own action, offered on the row being copied, so the two
routes now arrive at the same editor by whichever one the author chose. **New
preset** starting from some preset nobody named put a composition in the editor
that the author had to recognise as unwanted before deleting it — and the
preset it copied was the deployment default, which is the one least likely to
be what someone reaching for "new" wanted.

A blank draft has no source, so it names none and makes no read: the editor's
"copied from" line and header clause appear only when there is a source.
2026-08-07 11:18:22 +08:00
CreatixChu
70937db8a0 Merge pull request #1394 from deepseek-harness/fix/conversation-column-one-axis-scroll
fix(web): stop the conversation column from scrolling sideways
2026-08-07 11:11:32 +08:00
creatixchu
fb8f5f5eb4 Merge master into fix/conversation-column-one-axis-scroll 2026-08-07 10:51:29 +08:00
creatixchu
600ee462d5 Merge remote-tracking branch 'origin/master' into worktree/turn-tail-actions 2026-08-07 10:50:49 +08:00
imccyu
5c3d2a1706 Merge pull request #1802 from deepseek-harness/worktree-subagent-pref
feat: subagent list use preparation + projection
2026-08-07 10:45:25 +08:00
imccyu
3216150ae0 fix: address review round next
- subagents.history computes its projections best-effort on both arms
  (a hostile unit's fold rejection serves the page without the block,
  matching the session-list precedent) with dual-arm coverage
- the lifecycle-witness spec probes every field of the seven-key
  witness, protecting the key list itself
- list-children's own module docs catch up with the seq-gate contract,
  and the design note records the rung-two later-event window as an
  accepted, self-healing deviation of the corruption class
2026-08-07 10:17:35 +08:00
Yichen Jiang
9fc1430b79 fix(web): offer a just-authored preset on the new-session chip
Authoring writes a FILE, not a settings field, so nothing on the wire announces
it — `settings/changed` covers the default moving, never the directory. The
chip therefore kept the roster it read when it first mounted, and a preset
authored to be used was missing from the one screen that starts sessions.

The page that changes the directory now says so, and every surface reading the
same roster re-reads it. The chip subscribes rather than being reached from the
outer scope, because it is registered later, under `conversation`/`sessions`.
2026-08-07 03:31:23 +08:00
Yichen Jiang
a7ab8b27c5 fix(web): move the new-session chip when the default preset changes
The General row and the preset page already re-read on `settings/changed`, but
the hero chip did not — so changing the default and starting a session without
reloading composed the previous default. That is the one session the setting
claims to govern ("对此后新建的会话生效"), and the chip is what decides it.

A staged pick still wins: `load()` prefers the stage over the refreshed
fallback, so a refresh never overwrites a choice the user just made.
2026-08-07 03:20:49 +08:00
Yichen Jiang
0ab035c748 fix(web): name the preset in General settings, and say 预设 in Chinese
The preference row rendered `option.id` while every other surface — the
new-session chip, the session header label, the preset cards — renders the
metadata name. So the same roster read `标准模式` on one screen and `standard`
on the next, and the id is addressing, not a label. A preset that names itself
nothing still falls back to its id, which is then all there is to say about it.

The row's Chinese copy also still ended on the English word: the section is
`Agent 预设`, so the sentence about a running session keeping its composition
says 预设 too.
2026-08-07 03:01:30 +08:00
Yichen Jiang
d89f1ced94 Merge branch 'stack/agent-profiles-7-docs' into stack/agent-profiles-8-authoring
# Conflicts:
#	apps/cli/config/agent-presets/minimal/agent.cordis.yml
#	apps/cli/config/agent-presets/standard/agent.cordis.yml
#	packages/bundle/web-app/cordis.patch.yml
2026-08-07 02:59:26 +08:00
Yichen Jiang
1b70c30bee Merge branch 'stack/agent-profiles-6-cordis-agent' into stack/agent-profiles-7-docs 2026-08-07 02:58:53 +08:00
Yichen Jiang
1ebe0aef94 Merge branch 'stack/agent-profiles-5-web-ui' into stack/agent-profiles-6-cordis-agent 2026-08-07 02:58:53 +08:00
Yichen Jiang
c4e76384f4 Merge branch 'stack/agent-profiles-4-settings' into stack/agent-profiles-5-web-ui
# Conflicts:
#	apps/cli/package.json
#	pnpm-lock.yaml
2026-08-07 02:58:53 +08:00
Yichen Jiang
31b7dc439b Merge branch 'stack/agent-profiles-3-wire' into stack/agent-profiles-4-settings 2026-08-07 02:58:16 +08:00
Yichen Jiang
89ae94d89f fix(web): stop leaking ask_user_question into every preset
`ui-question`'s node half called `ctx.tools.register` on the host context.
`ScopedLayers.merge()` combines the global layer with the agent's exact-scope
layer, and an unscoped registration lands in the global one — so the tool
reached every agent no matter which preset composed it. `core-web`, sold as a
two-tool benchmark surface, really presented three.

Rendering a question is a host UI capability; having the tool is an agent
capability, and only a preset decides that. The node half is now empty and the
`tool-ask-user` row moved into the preset that wants it. The TUI keeps its own
row, having no presets.

The composition tests now assert the global tool layer is EMPTY, which is the
invariant that would have caught this: any tool outside a preset reaches every
agent. The browser lane's composition, seeded-history, and hermetic-skill
assertions address their registries through a composed agent for the same
reason — those services are per session now, and the host cannot resolve an
`isolate` realm by name.
2026-08-07 02:57:30 +08:00
Yichen Jiang
2d1df8a9e2 fix(cli): declare the packages the shipped presets name
The bundle split emptied `apps/cli`'s plugin dependencies, and the flat module
fallback links only that manifest's closure — so a preset row naming
`@deepseek-ai/dsh-persona` resolved to nothing and every preset mount failed,
leaving each session with an agent that had no tools, persona, or token meter.
Which packages the shipped presets compose is not implied by any bundle: the
presets live beside this manifest, so this is where their closure is declared.

The hermetic skill test now addresses the registry through the composed agent,
the only shape that can see a preset's `isolate` realm.
2026-08-07 02:51:09 +08:00
Yichen Jiang
f1d09ad4d1 fix(web): keep host-plane services out of the preset, and pin the lane's skill roots
Moving the agent plane behind per-session presets took five rows with it that
the host still owns, and the Web surface stopped booting: `host-apiproxy`
injects `subagents`, so with the registry disabled here the entry never
activated and `dsh web` died at plugin-tree load.

The criterion is injection, not subject matter. A host row that injects a
service resolves it before any session exists, so there is no agent to key by:
`bash-env` (which `apps/cli/src/web.ts` injects to publish `DSH_WEB_URL`), the
`subagents` registry and its spawn/fork backends (a process singleton whose
cross-session queries the api-proxy serves, and whose provider names are
globally unique), and `tool-subagent-report` (a continuable setup on that
singleton, registered once per live session by a list that is not scope-aware)
all stay host-plane. What a preset chooses is which delegation TOOLS it sees.

The browser lane needs the second half: skill roots now resolve inside a preset,
a subtree the lane's include patches cannot reach, so the row's documented
environment fallback is pinned for the whole scaffold lifetime — presets mount
when a session is created, not at boot. Without it a developer's real
~/.dsh/skills enters replay requests and goldens while CI sees none.
2026-08-07 02:33:53 +08:00
Yichen Jiang
45d88fd6f1 Merge branch 'stack/agent-profiles-7-docs' into stack/agent-profiles-8-authoring
# Conflicts:
#	docs/architecture.i18n.yaml
2026-08-07 01:10:34 +08:00
Yichen Jiang
97ef0fa73a Merge branch 'stack/agent-profiles-6-cordis-agent' into stack/agent-profiles-7-docs
# Conflicts:
#	docs/architecture.i18n.yaml
2026-08-07 01:09:50 +08:00
Yichen Jiang
f56c7d6638 Merge branch 'stack/agent-profiles-7-docs' into stack/agent-profiles-8-authoring 2026-08-07 01:01:51 +08:00
Yichen Jiang
9d836c24a1 Merge branch 'stack/agent-profiles-5-web-ui' into stack/agent-profiles-6-cordis-agent 2026-08-07 01:01:50 +08:00
Yichen Jiang
da8e9c83c8 Merge branch 'stack/agent-profiles-4-settings' into stack/agent-profiles-5-web-ui 2026-08-07 01:01:50 +08:00
Yichen Jiang
5f20de5e83 Merge branch 'stack/agent-profiles-3-wire' into stack/agent-profiles-4-settings 2026-08-07 01:01:50 +08:00
Yichen Jiang
4a61c7da13 Merge branch 'stack/agent-profiles-6-cordis-agent' into stack/agent-profiles-7-docs
# Conflicts:
#	docs/architecture.i18n.yaml
2026-08-07 01:01:50 +08:00
Yichen Jiang
2b7d50642c Merge branch 'stack/agent-profiles-2-configs' into stack/agent-profiles-3-wire 2026-08-07 01:01:49 +08:00
Yichen Jiang
380eaec39b Merge branch 'stack/agent-profiles-1-seam' into stack/agent-profiles-2-configs 2026-08-07 01:01:38 +08:00
Yichen Jiang
7c7cd1aa35 Merge remote-tracking branch 'origin/master' into stack/agent-profiles-1-seam 2026-08-07 00:59:28 +08:00
Yichen Jiang
d247c50c6f fix(web): return every service a host row injects, and run the browser lane
The browser e2e lane had been failing wholesale since this stack moved the
agent plane into presets, and nothing caught it: 34 of 48 files. Two of the
causes are product defects, not test breakage.

`bashEnv` goes back to the host plane. `apps/cli/src/web.ts` injects it to
publish `DSH_WEB_URL`/`DSH_WEB_MODE`, so the earlier note that "nothing outside
the agent plane injects bashEnv" was simply wrong — behind a preset's `shell`
realm those variables reached no shell at all, and a `dsh web` agent could not
find the address of its own interface. This is the same criterion that returned
`subagents`: a host row that injects a service resolves before any session
exists and has no agent to key by, so the service is host-plane. `tool-bash`
consumes the host registry from inside the preset, which works because an
agent context chains to the host; only the reverse is invisible.

`tool-subagent-report` goes back with it. It is not a tool this agent calls: it
registers a continuable SETUP on the host `subagents` singleton, and that list
is not scope-aware. One copy per mounted preset meant every child was handed
`report` once per live session, so the second registration threw and a cold
subagent resume failed with `subagent-not-resumable` — a diagnostic three
layers removed from the cause.

The lane's own composition facts follow. Skill roots resolve inside a preset
now, a subtree include patches cannot reach, so the scaffold pins the roots'
documented environment fallback for its whole lifetime rather than for the boot
— presets mount per session. Without it the developer's real `~/.dsh/skills`
enters replay requests and goldens while CI sees none. The `apps/cli`
composition test pins `storage-json` for the same reason: unpinned it wrote,
and then read back, the developer's own `~/.dsh/storages/`.

Three tests now address through an agent what they used to read off the root
context, because that is where the thing lives: the tool catalog, the skill
registry, and the token meter. The seeded-history projection baseline asserts
the opposite of what it did — a detached session yields a preset-plane
projection only from a durable checkpoint written while it was live, and this
seed was written straight to persistence and never ran.

Goldens re-recorded for the hero's preset chip and the settings nav entry.
2026-08-07 00:42:25 +08:00
Yichen Jiang
b8bc89c2f0 docs(agent-presets): record what a live agent costs, and that nothing evicts one
Review asked whether composing a preset per session compounds memory.
Measured against the shipped compositions: it does not compound — growth
is strictly linear at ~0.17 MB per agent on `minimal` and ~1.31 MB on
`standard`, the first agent of a process pays ~7 MB more for module
imports every later mount shares, and disposal returns essentially all
of it.

What the measurement did find is that nothing disposes. The api-proxy
discards the handle it creates, archiving only edits the workspace
registry, the registry has no eviction, and the sole disposal site in
the host is the JSON-RPC server's shutdown. A preset did not introduce
that; it raised the per-session price from ~0.2 MB to ~1.3 MB and made
it visible.

Recorded as a remaining TODO rather than fixed here: eviction belongs to
the host that owns the handle, not to this seam.
2026-08-07 00:41:50 +08:00
Yichen Jiang
1d917e7c31 fix(web): give the settings panel one height, taken from the viewport
Sections differ by hundreds of pixels — a short preferences list against
the composition editor — and the 600px panel served both badly: the
list left a wide empty band, the editor cramped a YAML file into a
16-row box with dead space under it.

The panel is now `min(800px, 100vh - 48px)`: one height for every
section, whatever does not fit scrolls where it already did. The
composition editor takes the column's full height rather than a fixed
row count, so the extra space reaches the text instead of the gap.

Refresh the settings goldens the preset section's nav entry had made
stale.
2026-08-07 00:41:50 +08:00
Yichen Jiang
2b8a0a8cff feat(web): choose the agent preset on the new-session screen
The composer seat spent nearly all its life disabled: a session's
composition is fixed once a turn has run. Move the choice to the
new-session screen beside the workspace picker, where it still works,
and let the session header report what a running session runs.

The hero pick is staged rather than applied — that screen precedes the
session it belongs to. It lands when a session becomes current and is
still blank, which covers both the session a workspace connect creates
and the blank one it reuses; riding `sessions.create` would miss the
second. It is spent on first use, matching the workspace picker.

Fix the durability the header field claimed but never had: `agentPreset`
was declared on `SessionHeader` and dropped by the JSONL header line, the
SQLite sessions row, the derived query index, and the cold list
projection, so every resumed session came back composed from nothing.

Add the web e2e lane that would have caught it — the one lane that mounts
the shipped roster, which needed `cordis:group` in the scaffold's Loader
builtins, as `mountRootInclude` already registers.
2026-08-07 00:41:50 +08:00
Yichen Jiang
4bb9836abe fix(web): drop the Close button from the read-only preset view
A shipped preset has nothing to commit or abandon, and the back link above
already leaves the screen; a lone Close button below the composition was a
second way out of the same place. The action row now renders only when the
draft is writable, where Cancel and Save are both real choices.

The composition label named `cordis.yml`; the file a preset actually holds
is `agent.cordis.yml`.
2026-08-07 00:41:50 +08:00
Yichen Jiang
64fc536982 feat(web): order the shipped presets, and edit one on its own screen
The picker listed presets alphabetically by id, so the shipped set read
cordis, minimal, standard — reverse order of capability. A preset may now
declare `order` in its metadata; the shipped three declare 1/2/3 and read
standard, minimal, cordis. A preset that declares none sorts behind those
that do, then by id, so authored presets stay stable.

Editing had nowhere good to live. Inside a card it was squeezed into a
~268px column; hanging off the end of the grid it was orphaned from the card
it edits. It now replaces the list: a back link, what is being edited, and
the form at full width. One thing on screen at a time, which is what the
form's height wanted all along.

Cards in different grid rows sized independently, so a short description made
a short card. `grid-auto-rows: 1fr` makes every row the same height.

The trust badge lost its pill when the card CSS was rewritten, and `In use`
never had one; both are tags now. Icon labels moved from `title` to a drawn
tooltip — the native one waits about a second, which reads as nothing
happening.
2026-08-07 00:41:50 +08:00