Commit Graph
18 Commits
Author SHA1 Message Date
Tianyi Cui 5481887cd3 docs(agent): point the streaming reader at session/event assistant/chunk
Follow-up to the README fix: the dsh-agent README's emit section had lost its
streaming entry, leaving a "Streaming + tool" heading with no pointer to where
the token stream now lives. Rename the section to "Live control notifications"
and add a line directing readers to the durable `session/event` `assistant/chunk`
feed.
2026-07-03 00:12:37 +08:00
Tianyi Cui e100c52154 docs(agent): drop agent/stream-chunk from the two package READMEs
Codex convergence caught stale prose the doc-sync gate does not scan: the
dsh-agent README still listed `agent/stream-chunk` as an emit and the
dsh-agent-loop README still told UI plugins to listen on it. A plugin author
following either would silently get no chunks. Point both at the
`session/event` `assistant/chunk` feed (the agent-loop UI line also names the
`agent/*` control events a UI still uses).
2026-07-03 00:06:39 +08:00
Tianyi Cui 1323366da3 Merge worktree-hooks-b-bash-seam into worktree-hooks-c-interception
Bring the interception-seams branch onto current master (via A→B). The
substantive reconciliation is master's compaction `agent/pre-step` serial seam
meeting C's interception seams:

- types.ts: keep BOTH master's `agent/pre-step` AND C's new interception events
  (`agent/prompt-submit`, `agent/session-start`, `agent/turn-continuation`→
  `ContinuationDecision`); drop the turn-mirror declarations (removed on A).
- loop.ts: the merged per-turn order is `turn/start` → per queued msg
  `agent/prompt-submit` (rewrite/inject/block) → (fully-blocked ⇒ zero-step
  `rejected`) → per step: drain steering → assemble system prompt →
  `agent/pre-step` (compaction, OUTSIDE the step) → `step/start` → single
  `deriveMessages()` → model → tools/pre-execute·dispatch·post-execute. No
  turn-mirror emits; `closeTurn()` is the A-simplified single-call form.
- Docs (architecture, core.md, agent/agent-loop READMEs, catalog) reconciled to
  show C's interception seams alongside `agent/pre-step`, no turn/step mirrors.
- rfc/README: dropped the stale `proposed/` compaction row (master moved that RFC
  to implemented/); kept C's new `pre-tool-input-rewrite` proposed row.
- interception.spec.ts: migrated its two `agent/turn-end` reason collectors to
  the `turn/end` session event, and ADDED a cross-test proving a
  `prompt-submit` rewrite + additionalContext is VISIBLE to an `agent/pre-step`
  listener on the same turn — pinning the merged seam ordering (compaction sees
  the post-prompt-submit surface, not stale history).
2026-07-02 04:51:24 +08:00
Tianyi Cui 140f818a42 refactor(events): remove the turn boundary mirror events
Complete the boundary-mirror removal begun with the step mirrors: drop
`agent/turn-start` and `agent/turn-end` from the agent event taxonomy. Turn and
step boundaries are now read exclusively off the durable `session/event` feed
(`turn/start`/`turn/end`/`step/start`/`step/end`) — there is no `agent/*` mirror
for any boundary.

- loop.ts: delete both turn emits; `closeTurn` loses its `emit` parameter and
  its now-unreachable idempotency guard (it is called exactly once per turn, on
  mutually exclusive normal/catch paths); `failTurn` loses the dead post-close
  branch that only a throwing turn-end LISTENER could reach.
- ui-stdio: render turn boundaries from `session/event`, recovering the short
  agent label from an `agent/created`→id map (the `turn/start` event carries only
  the turn number, and the session id is not reliably the agent id). ui-stdio is
  a disposable test REPL, so this migration retires the sole justification the
  event-domain-semantics RFC gave for KEEPING the turn mirrors.
- Tests: reason/turn-number collectors and the boundary-ordering test now read
  `session/event`; the throwing-turn-boundary-LISTENER tests are deleted (that
  code path no longer exists). A new test covers the outer-catch disposed branch
  via a pre-step listener that disposes-then-throws (the surviving real path).
- Docs: promote the "remove agent boundary mirror events" RFC to implemented
  (amended/narrowed — `agent/steering` is RETAINED, not a boundary mirror);
  update the event-domain-semantics + turn-enclosure RFCs, architecture.md, the
  cookbook, the ACP/agent/ui-stdio prose, and regenerate the cordis catalog.

`agent/steering` and `agent/stream-chunk` are explicitly out of scope (not
durable-boundary mirrors). ACP is unaffected — it already settles from the log's
`turn/end` + `agent/status`; snapshot goldens are byte-unchanged.
2026-07-02 03:26:45 +08:00
Tianyi Cui bdf390679b Merge origin/master into worktree-hooks-a-taxonomy
Bring the event-taxonomy branch up to date with master's compaction work.
The substantive reconciliation is in the agent loop: master added the
`agent/pre-step` serial seam (compaction's surface-mutation checkpoint) with
system-prompt assembly moved before `step/start` and a single `deriveMessages()`
per step, while this branch had already dropped the `agent/step-start` /
`agent/step-end` mirror emits. Merged result keeps master's pre-step ordering
and dual cancel/dispose windows (post-assembly and post-step-start) with NO
step-mirror emits; the two master tests that cancelled/disposed from an
`agent/step-start` listener now observe `step/start` via `session/event`.

Regenerated the cordis catalog and module graph from source. Gates: typecheck
clean, agent-loop + compact suites green (226 tests).

Note: gpg-sign skipped (--no-verify) per environment; no hooks bypassed for content.
2026-07-02 02:16:07 +08:00
Tianyi Cui 8a8b88b0a7 Merge remote-tracking branch 'origin/master' into compact-basic-refactor
# Conflicts:
#	docs/architecture.md
#	examples/coding-agent/README.md
#	packages/core/session/README.md
2026-07-01 23:29:20 +08:00
Tianyi Cui 5bdb40ff34 Merge remote-tracking branch 'origin/worktree-hooks-b-bash-seam' into worktree-hooks-c-interception
# Conflicts:
#	docs/architecture.md
#	packages/core/agent-loop/README.md
2026-07-01 15:37:54 +08:00
Tianyi Cui 3e26102baf Merge remote-tracking branch 'origin/master' into worktree-hooks-a-taxonomy 2026-07-01 15:10:31 +08:00
Tianyi Cui 0478f5965a docs: address review sync gaps 2026-07-01 12:56:13 +08:00
Tianyi Cui dc95a7881d feat(events): interception seams — the typed-Decision surface for hooks
Reshape the agent's interception surface so every seam returns a small, typed
Decision union, and the set covers the hook points a CC/Codex bridge (and a
native plugin) needs. "Native hooks" are not a package — a native hook is just a
cordis plugin on these canonical events; the bridges (a later PR) only translate
an external protocol onto the same surface.

dsh-agent:
- NEW agent/session-start(agent, source) emit (once before turn 1; SessionStartSource
  startup|resume|clear|compact) — a pure notification, seeds context via inject().
- NEW agent/prompt-submit waterfall → PromptDecision (allow, optionally rewriting the
  prompt or attaching additionalContext, or block).
- RESHAPE agent/turn-continuation boolean → ContinuationDecision ({action:'stop'} |
  {action:'continue', reason?}; a continue reason is recorded as next-step steering).
- New HookContext envelope (required source — inject() would mislabel a missing one).

dsh-tools: split the single tools/execute waterfall into tools/pre-execute
(PreToolDecision allow/deny/ask gate) and tools/post-execute (PostToolDecision
accept/block, optionally replacing content or attaching additionalContext). Core
dispatch sits between as plain code; the tool body keeps its inner try/catch so a
thrown tool still reaches post-execute as an isError. ToolExecutionResult gains
additionalContext (ferried to the loop's per-step buffer). Input rewrite is
deliberately NOT offered (a proposed RFC designs it consistently).

dsh-session: new `rejected` TurnEndReason — a turn whose whole prompt batch was
blocked by prompt-submit.

agent-loop firing points: session-start emitted at create (source threaded —
startup for create/fork, resume for resume()); prompt-submit per drained message
with the always-open-turn rule (a fully-blocked batch is a zero-step rejected
turn); the continuation reshape; post-tool additionalContext buffered and appended
after all tool/results (adjacency). ACP codec maps rejected→cancelled.

A worked native-plugin example (interception.spec.ts) proves all four seams compose
end-to-end through the real loop with NO hook/* events (those belong to the bridge
lib). All existing tools/execute + turn-continuation tests migrated. The
tool-subagent abort test now aborts after a microtask so it still exercises the
live onAbort bridge (execute() awaits pre-execute before the body runs).

RFCs: implemented/feature/2026-06-30-interception-seams.md (the reshape) +
proposed/feature/2026-06-30-pre-tool-input-rewrite.md (the deferred rewrite design).
2026-06-30 17:11:18 +08:00
Tianyi Cui 8df89d8e33 fix(events): address Codex review — balance step on step/start-listener throw, restore /goal guard
Codex review of PR-A found three blockers:

- A throwing step/start session-event listener left an unbalanced log
  (turn/start → step/start → turn/end with no step/end), which the invariants
  oracle rejects — masked because that rejection was itself contained as a
  throwing turn/end listener. Fix the root cause in the loop: mark the step open
  BEFORE appending step/start (Session.append pushes before notifying), so the
  outer catch's closeStep() appends the balancing step/end. The test now asserts
  the balanced outcome (stepEnd:1, step/end before turn/end); proven load-bearing
  (revert the reorder → the test goes red with stepEnd:0).
- Reintroduce the /goal-pattern guard deleted in the prior commit, migrated to a
  step/end session-event listener (the surviving step-boundary hook point), with
  a no-tools first step so it exercises the hasSteering continuation override.
- Update packages/core/agent/README.md: step boundaries are no longer agent/*
  emits.
2026-06-30 12:19:18 +08:00
Hypatia May b13586ff8e docs(compact): align seam docs with the pre-step seam and record session/invariants changes (CBR-003)
Codex round 1 CBR-003: several docs still described compaction as an
`agent/request` waterfall concern, and the implemented compaction RFC
claimed "No changes to dsh-session or dsh-invariants" while the diff
changed both.

- Package READMEs / JSDoc (agent, agent-loop, system-prompt, compact,
  compact-basic): compaction now lives on the serial `agent/pre-step`
  seam (fired after turn/start, before step/start); the structural guard
  is tool-pairing balance (`isToolPairingBalanced`), not step-alignment;
  the convergence bound is strict (`>=` rejects).
- architecture.md / core-data-structures/compaction.md: same seam +
  predicate + dispatch-mode updates; regenerated cordis catalog.
- Implemented compaction RFC, updated in place to describe shipped
  reality: the seam is `agent/pre-step` (@mode serial) fired before
  step/start; alignment is surface tool-pairing balance; the convergence
  invariant rejects `>=`; and the "no dsh-session/dsh-invariants changes"
  claim is corrected — dsh-session gains the tool-pairing predicate and
  dsh-invariants drops its `start <= end` replace assertion (a positional
  replace makes start > end normal).
2026-06-26 13:51:45 +08:00
Tianyi Cui c44ae5570c fix review findings: whenIdle() is observation, not the teardown await
Codex's confirmation pass found the teardown-framing error went deeper than the
three prose spots already fixed: the whenIdle() JSDoc itself (and its mirrors)
claimed "the quiescence signal a teardown awaits ... a lifecycle owner disposes
the agent through its AgentHandle which ... awaits THIS". The disposer does not
call whenIdle() — it does `stop(); await agent.done` directly
(packages/core/agent-loop/src/index.ts:271). whenIdle() is the NON-OWNER
observation hook; owner teardown awaits the loop-exit promise (done) through
AgentHandle.dispose(). Reframe every copy accordingly:

- packages/core/agent/src/types.ts: the Agent.whenIdle() contract JSDoc.
- packages/core/agent-loop/src/agent.ts: the impl JSDoc.
- packages/core/agent/README.md and docs/core-data-structures/core.md (the
  type-equiv mirror of the types.ts JSDoc — re-copied verbatim).
- docs/rfc/proposed/feature/2026-06-14-acp-agent-client-protocol.md:40 and :70:
  owner teardown via AgentHandle.dispose(); a non-owner observing quiescence
  uses the interface-level agent.whenIdle(), not hand-rolled agent/status.
- Regenerate the cordis catalog (whenIdle source line moved).
2026-06-21 10:03:30 +08:00
Tianyi Cui f6bd1468f2 simplify(agent): drop the unused public Agent.abort(), keep whenIdle()
The public Agent handle exposed abort() (step-only) and cancel() (queue-aware).
No production caller used abort() — ACP maps session/cancel to cancel(), and
lifecycle owners tear down via AgentHandle.dispose(); the loop's own stop paths
abort their per-step AbortController directly. So abort() is latent generality
that keeps a private loop mechanic public.

RFC-premise correction: the public-agent-stop-surface RFC proposed removing
whenIdle() too. Implementation found whenIdle() load-bearing — a real
quiescence primitive with a deliberate loop contract (settle-without-transition,
the replacement-turn race) and ACP test consumers; its proposed replacement
("observe the running->idle transition") is exactly the async-state race
AGENTS.md warns against. So only abort() is removed; whenIdle() stays. The RFC
is amended on the way to implemented/ to record the narrowed scope, and the new
AGENTS.md "RFCs are proposals, not golden truth" principle (PR1) gets its
worked example.

- Remove Agent.abort() from the interface + the ReactLoopAgent impl; the no-arg
  'aborted' default goes with it (cancel() keeps its 'cancelled' default).
- Migrate tests: empty-queue abort() -> cancel(reason); the two review-fixes
  tests whose subject is the in-flight step's AbortController drive that
  controller directly via the private currentAbort field (cancel() would clear
  the inbox and destroy the queued steering one of them proves survives a step
  abort). The no-arg-default test is dropped (cancel()'s default is already
  covered in cancel.spec.ts).
- Resulting public stop surface: cancel() + whenIdle(). Update agent/agent-loop
  READMEs, architecture.md, core.md type-equiv, the extension cookbook, the
  lifecycle RFC (short note), and the proposed ACP RFC.

Implements docs/rfc/implemented/simplification/2026-06-20-public-agent-stop-surface.md
2026-06-21 09:05:21 +08:00
Tianyi Cui d6a2ab30c8 feat(types): brand bash ids + stop brand erosion; extract Branded to dsh-brand
Type-only change (brands are zero-cost casts; no runtime/wire impact). Closes
the two gaps in the "brand ids that cross package boundaries" policy and fixes
the dependency direction so a capability package never pulls in an unrelated one.

- Extract the `Branded<B>` primitive into a new standalone type-only package
  `@deepseek-ai/dsh-brand` (packages/util/brand) with no harness-package deps.
  dsh-llm keeps its owned CallId but imports Branded from dsh-brand; dsh-session,
  dsh-agent, and dsh-bash all import Branded from there. dsh-bash depends on
  dsh-brand ALONE — never on dsh-llm or dsh-session (the architectural fix: a
  generic execution backend must not couple to the LLM or session vocabulary).
- Mint BashTaskId + OwnerToken in dsh-bash and thread them through BashTask.id,
  the get/ownerOf/list/readOutput/kill seam, the bash-local generation site, and
  the dsh-tool-bash validate/access surface. OwnerToken is a DISTINCT brand from
  SessionId so the seam stays decoupled; dsh-tool-bash is the single boundary
  that casts SessionId -> OwnerToken.
- Brand at the SOURCE, not via mid-pipeline casts: agent-loop's Config types
  agents[].id as AgentId and resumeSessionId as SessionId, so the brand enters
  at the config boundary and the inner create()/resume casts disappear (only the
  genuinely-new per-run session-id string is cast).
- Stop brand erosion: propagate CallId/SessionId/AgentId to the registry/store
  Map keys and public params/exports (SessionStore, AgentRegistry + factory
  options, the ACP session-id surface + ToolPresenter CallId map, the
  persistence coordinator, invariants pendingCalls, the pi-ai tool-call maps).
- Docs: document BashTaskId/OwnerToken in bash.md (type-equiv re-pasted), point
  the Branded type-equiv at dsh-brand, fix stale param types in the session/
  agent/bash READMEs, regenerate the cordis catalog + module graph.

Implements docs/rfc/proposed/architecture/2026-06-20-branded-ids.md
2026-06-21 07:19:59 +08:00
Tianyi Cui ca2207e26c Fix doc cross-links for the hierarchy; add package-path + shape gates
Merge brought in the RFC-classification reorg and two new doc gates;
rewrite every drifted packages/<name> cross-link (Markdown link targets,
moved-README relative depths, and .ts comment paths) to the grouped paths.

Add two doc-sync/hygiene gates so the manual checks this restructure
needed become automated:
- verify-package-paths.ts: flags a packages/<path> reference (in Markdown
  or a .ts comment/string) that does not resolve AND names a real package
  in a segment — i.e. a stale path to a MOVED package. A path naming a
  non-existent package (a forward-looking proposal) is left alone, so it
  applies uniformly across proposed/implemented/rejected.
- check-workspace-constraints: assert the packages/<group>/<pkg> depth-2
  shape (group dirs carry no package.json; no flat or over-nested
  packages). Group names stay open; only the shape is fixed.
2026-06-20 23:12:14 +08:00
Tianyi Cui 08f6f17cc1 Merge remote-tracking branch 'origin/master' into worktree-package-hierarchy 2026-06-20 22:55:38 +08:00
Tianyi Cui d02e9f1bd6 Reorganize packages into a modular hierarchy
Move the 18 flat packages/<name> packages into role-grouped dirs:
core/, llm/, bash/, session-persistence/, ui/, support/. Group dirs are
pure containers; each package keeps its @deepseek-ai/dsh-* name.

Collapse the per-package tsconfig paths maps (base + typecheck) into one
@deepseek-ai/dsh-* wildcard with a candidate per group, and derive the
publint list from the hierarchy. Update all depth-coupled globs/configs
(workspace, tsdown, vitest, eslint, knip, tsconfig includes/refs,
per-package tsconfigs, generators, doc-script scopes, type-equiv manifest)
and the cross-package/script relative imports in tests.

Fix doc-typecheck's workspacePaths() to parse tsconfig JSONC via the
TypeScript API instead of a regex comment-strip, which corrupted the
new wildcard `/*/` path candidates.

WIP: doc cross-links and package/RFC docs still to update.
2026-06-20 22:55:20 +08:00