Commit Graph
22 Commits
Author SHA1 Message Date
Tianyi Cui abe80cec68 fix(loop): record a durable prompt/blocked for every vetoed prompt
Address review on the interception-seams PR: PromptDecision.reason is
documented as the durable record of why a prompt was blocked, but the loop
only surfaced it via the fully-blocked batch's `rejected` turn/end. In a MIXED
batch — one queued prompt blocked, another allowed — the turn does not end
`rejected`, so the blocked prompt and its reason vanished from the session log
entirely.

Add a `prompt/blocked` SessionEventMap variant (content + source + reason),
appended in the open turn at the veto point in place of the user/message the
prompt would have become. It is a non-surface, turn-enclosed event (like
todo/write): it never reaches deriveMessages(). The fully-blocked batch still
also ends `rejected` for boundary balance + ACP settlement. Regression test
drives a mixed batch and asserts the blocked prompt is recorded while the
allowed one runs — proven red without the append.
2026-07-02 17:03:53 +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 9e575a2a2c docs(events): fix stale turn-mirror references caught in review
Codex review of the turn-mirror removal found current-state docs/comments that
still claimed the removed `agent/turn-start`/`agent/turn-end` events exist:

- docs/architecture.md: the loop diagram's turn-start line still said "emit
  agent/turn-start" (the turn-end line was already fixed).
- event-domain-semantics RFC: the `agent/*` domain description listed "the turn
  boundaries" among the transient emits.
- docs/core-data-structures/core.md: the agent/* taxonomy blurb listed
  "turn/step boundaries" as agent events.
- the proposed ACP RFC: the settle-signal rows named agent/turn-start /
  agent/turn-end; retargeted to the durable `turn/end` session event + the
  session/event owning-turn correlation.
- loop.ts outer-catch comment: said "closeTurn/failTurn are idempotent" — after
  the emit-param removal closeTurn is called exactly once (mutually exclusive
  normal/catch paths), so corrected to state that and to scope idempotency to
  closeStep (which is still guarded by stepOpen).

Regenerated the cordis catalog. No behavior change.
2026-07-02 03:47:37 +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 483e0e5edf fix(events): address Codex review — protect post-execute result, purge stale tools/execute refs
Codex's PR-C review found two (A) blockers:

- tools/post-execute could corrupt the protected outcome. postExecute passed the
  mutable `result` to listeners and then read result.callId / spread result on the
  return paths, so a listener mutating the reference (flipping isError, rewriting
  callId, injecting an error) escaped the decision channel. Now the authoritative
  callId/isError/error are SNAPSHOT before the waterfall and the return value is
  rebuilt from the snapshot + the typed PostToolDecision — the decision is the only
  sanctioned way to change the outcome, and callId is always exec.callId. Added a
  regression test that mutates the result reference and asserts it has no effect;
  proven to fail red on the unfixed code.

- Public docs/JSDoc still advertised the removed `tools/execute` waterfall after the
  split. Swept every current-state reference to tools/pre-execute + tools/post-execute:
  the ToolRegistry class JSDoc (and the regenerated catalog), loop.ts's ASCII flow
  (also added the prompt-submit/session-start steps it was missing), the package-map
  READMEs (packages, core, agent-core), core-data-structures core.md/tools.md, the
  bash + acp + invariants src/READMEs (the deferred permission gate is the
  tools/pre-execute deny/ask seam now), the cookbook, and the implemented RFCs whose
  factual seam catalog drifted. codec.ts's totality prose now lists `rejected`.
  Proposed-RFC references are left as-is (frozen proposals, validated when built).
2026-06-30 20:25:30 +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 b8d0da9f8c docs(loop): clarify the /goal steering comment names the step/end session event
The continuation-override comment said "step-end/continuation listeners". With no
agent/step-end emit, the surviving step-boundary listener is the durable step/end
SESSION event, so spell it "step/end session-event/continuation listeners" to avoid
implying a removed agent/* mirror. Comment-only; no behavior change.
2026-06-30 13:02:56 +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 b0eae94fc8 fix pre-step cancellation and compaction convergence 2026-06-30 10:56:34 +08:00
Tianyi Cui 05b75abbca refactor(events): document event-domain semantics, drop step-boundary mirror emits
Pin the three-domain rule (session = durable fact log, agent = live runtime
surface, tools = registry/exec): a durable replayable fact is a SessionEvent; a
live interception or transient/live-object signal is an agent/tools Cordis
event. A boundary that is both is mirrored as an agent/* emit ONLY where a live
consumer needs the Agent handle.

Apply it to the boundary twins: drop agent/step-start and agent/step-end (no
production consumer needs the live Agent at a step boundary — consumers read the
durable step/start/step/end session events). Keep agent/turn-start/turn-end (the
stdio UI labels output by agent.id). Tests that observed step boundaries via the
removed emits now observe the durable session events; the pinned behavior is
unchanged.

Conservative subset of the proposed "remove boundary mirror events"
simplification; foundation for the Hooks subsystem's canonical event surface.
2026-06-30 10:32:55 +08:00
Hypatia May 1f35a4446d fix(compact): address PR 110 review findings
Honor cancellation and disposal around async pre-step setup before the loop can open a step or call the model.

Route compaction summarization through agent/request so router agents can select the model, and remove the stale model argument from agent/pre-step.

Document serial events and the approximate convergence bound, regenerate the Cordis catalog, and add regression coverage for router compaction, HMR cleanup, and assembly/pre-step interruption.
2026-06-29 16:02:27 +08:00
Hypatia May d6da8ca29a fix(compact): decide step-alignment from surface tool-pairing, fire compaction pre-step (CBR-001)
Codex round 1 CBR-001: a head-anchored compaction checkpoint was
mis-classified by the log-position step-alignment scan, so a second
auto-compaction over a checkpoint-headed surface silently failed.

Root cause: `isStepAlignedStart/End` scanned the LOG by seq, but a
`replace` op lands a checkpoint at a high log seq whose SURFACE position
is the head — its log neighbours (the open step's assistant/message) are
not its surface neighbours, so the forward scan wrongly reported mid-step.

Fix, per the agreed direction:
- Replace the two log-position predicates with one surface-anchored
  helper `isToolPairingBalanced(nodes, events, beforeSeq)` in
  `dsh-session` (renamed step-boundary.ts → tool-pairing.ts). A cut is
  balanced when no unanswered tool-call precedes it on the surface; a
  region is collapsible iff both edges are balanced cuts. The open-tail
  and free-node cases fall out of the same counter. It also throws on a
  corrupt surface (a tool/result with no matching call).
- Move compaction off the in-step seam to a new "pre-step" seam fired
  after turn/start and before step/start, so a compaction's log-only
  compact/* records and its replacement node land cleanly OUTSIDE any
  step (the honest structure crash-safety relies on). Renamed the event
  agent/pre-request → agent/pre-step and switched its dispatch from
  parallel → serial (listeners mutate the surface as a side effect;
  serial isolates them so concurrent appends can't interleave). Extended
  the catalog generator to accept @mode serial.

Regression coverage: a real-loop test driving an auto-compaction asserts
the landed checkpoint is a balanced cut on both sides; unit tests pin the
checkpoint case, the mid-step injection case, multi-call steps, and the
corrupt-surface guard. Proven red on the old log-position logic.
2026-06-26 13:51:01 +08:00
Hypatia May cec32faa4e refactor(compact): turn-agnostic retention + dedicated agent/pre-request seam
Reform the compaction blueprint so a runaway turn survives and the design
stops drifting across review rounds:

- Drop in-flight-turn protection ("layer 2"). Retention is a uniform tail→head
  whole-unit walk; the only structural guard is step-alignment. A single turn
  that alone exceeds the window now compacts its own early closed steps instead
  of being retained verbatim (the failure mode that motivated this).
- Move auto-compaction off the agent/request waterfall onto a new awaited
  agent/pre-request loop seam, fired before history derivation. Compaction
  mutates the surface; the loop derives once from the result — no double-derive,
  and a listener structurally cannot act on not-yet-derived messages.
- Tighten compactIfNeeded to required (session, system, model, signal).
- Enforce a single-pass convergence invariant in resolveConfig: reject configs
  where summarizationMaxTokens + retainTokens exceeds the threshold, so a
  compaction can never immediately re-trigger.
- Document the crash vs recoverable failure taxonomy; core session repair stays
  compaction-agnostic (a log-only orphaned compact/start is inert).
- Wire dsh-compact-basic into examples/coding-agent and add a with-key
  compaction e2e (compaction's first real-world exercise + runaway net).
- Rewrite the RFC to encode the blueprint and move it to implemented/.

The runaway-turn snapshot is a named deferred follow-up: dsh-llm-replay cannot
yet serve the interleaved summarization model call.
2026-06-26 08:59:33 +08:00
Hypatia May 9cc8dc371e Merge remote-tracking branch 'origin/master' into session-surface
Reconciles the session-surface work (surfaceOp/sourceEventSeqs provenance as
the sole derivation path) with master's worktree-subagent series (fork-seed
boundary + out-of-process subagent backends).

Semantic reconciliations beyond the textual auto-merge:
- SQLite SCHEMA_VERSION: both sides bumped 2->3. Merged to a single v3 carrying
  BOTH column families — master's seed_length on `sessions` and surface's
  source_event_seqs/surface_op on `events`. writeRow + both INSERT sites bind
  the full set; the schema doc lists all three added columns as the v2->v3 gap.
- agent-loop runStep request: master's `sessionId: session.id` and surface's
  per-append surfaceOp/sourceEventSeqs coexist (different regions).
- Fork seed + surface: a fork seeds the child from the parent's LIVE events,
  which now carry surfaceOp, so the child's surface rebuilds correctly. Verified
  end-to-end — the subagent-fork replay recalls the inherited "SAFFRON" codeword
  through the seeded prefix.
- Subagent snapshot fixtures (recorded pre-surface) re-enriched via KEYLESS
  deterministic replay: only surfaceOp/sourceEventSeqs added onto existing
  recorded lines (matched by seq), no recorded value changed. Not re-recorded
  against the live API.

Gates: typecheck, test (1112), test:snapshot (14), doc-sync, lint, build,
hygiene all green.
2026-06-24 10:48:01 +08:00
Hypatia May 0298f5c6f0 Merge remote-tracking branch 'origin/master' into session-surface
Reconcile the session-surface feature with master's package reorg and
simplifications:

- Adopt master's folded usage (assistant/message.usage; standalone `usage`
  event dropped) and re-attach surface metadata (surfaceOp/sourceEventSeqs).
- Add surface opts to master's new max-tokens assistant/message append.
- Port surface columns onto the coordinator-refactored SQLite backend at its
  new path; drop the dead v1->v2 migration (bump-and-reject, no migration per
  pre-release policy).
- Move the session-surface RFC into implemented/architecture/ and refresh its
  stale body (no migration, SESSION_FORMAT_VERSION=0, renamed package paths).
- Update the core-data-structures catalog SessionEvent blocks for the two new
  surface fields; regenerate the cordis catalog.
- Re-harvest ACP snapshot fixtures (keyless replay) to carry surface metadata.
2026-06-22 10:35:59 +08:00
Tianyi Cui e68496fd79 Add per-session snapshot replay for nested agents (PR2.5)
The snapshot tier was built single-session: dsh-llm-replay served calls from
one global positional cursor, and the harness harvested one session log. A
subagent runs as a second agent with its own session, so a parent→child
scenario could neither replay deterministically nor harvest the child's log.
This resolves the TODO(subagent-snapshots) deferral from the subagent RFC.

- Stamp the calling session id onto the model request: GenerateOptions.sessionId
  (typed Branded<'SessionId'> to avoid the dsh-llm↔dsh-session cycle), set by the
  agent loop from agent.session.id. Adapters ignore it; an llm/stream listener
  routes by it.
- Key replay per session: dsh-llm-replay loads the parent log plus one per child
  (childFiles / $DSH_SNAPSHOT_CHILD_FILES), derives a script per recorded session,
  and binds each live (freshly-random) session to a recorded script by first-call
  order — parent first (earliest createdAt, first to stream). Keys by WHO calls,
  so it survives a future concurrent/backgrounded subagent; a global cursor would
  not. An unrecorded extra session fails loud.
- Harvest every log: the harness collects all .jsonl across cwd buckets, ordered
  primary-first (top-level, then children by createdAt), and RunResult exposes the
  plural sessionLogs. The spec writes each back on record (session.jsonl +
  session.<n>.jsonl) and diffs each against its fixture on replay.
- Wire the subagent seam + spawn + fork + tool into the acp-agent example (both
  cordis configs) and add two nested scenarios recorded against the real API:
  subagent-spawn (parent + 1 child) and subagent-multi (parent + 2 children, 3
  sessions). Both replay keyless in the default gate.

A new RFC documents the design (docs/rfc/implemented/testing/). Single-session
replay is unchanged (a call with no sessionId is one anonymous primary session).

TODO follow-up: a dedicated branded-ids package could own the SessionId brand and
dissolve the cross-package cycle note; out of scope for this testing PR.
2026-06-22 08:39:36 +08:00
Tianyi Cui 2eb6ad3260 fix review findings: sync stale v1/removed-event doc references
Codex's re-confirmation pass verified both blocker fixes correct but found
doc/comment drift the fix commit missed:

- session/index.ts + session/README.md: "minimal v1 header" → "minimal header
  (stamped with the current SESSION_FORMAT_VERSION)" — the version is 0, not 1.
- session/index.ts deriveMessages comment listed "usage, and errors" as trace
  data — those standalone events no longer exist; only boundaries + chunks are.
- session-persistence RFC: "no v1 migration" → the pinned-v0 pre-release stance.
- collapse-trace-only RFC format-version note: reframed off the "bump the
  version and reject" wording (which now reads as the OTHER AGENTS.md stance)
  onto the pinned-0 unstable stance the session log actually uses.
- agent-loop/loop.ts finishError JSDoc: "with a logged `error` event" → the
  failure is recorded on turn/end.reason (no standalone error event).
- acp/acp-feature-support.md (two spots): usage is recorded on assistant/message
  now, not as standalone internal usage events.
- Regenerate the cordis catalog (finishError JSDoc line shift).
2026-06-21 11:33:41 +08:00
Tianyi Cui b0422f2a50 fix review findings: bump session format version + restore late turn-end warn
Codex review of the trace-event fold found two merge-blockers.

Blocker #1 — format version. Folding usage onto assistant/message and removing
the standalone usage/error events changed the persisted SessionEventMap shape,
which per the AGENTS.md "bump the version and reject — don't migrate" policy
requires a backend to reject any non-current log. Centralize the version in an
exported SESSION_FORMAT_VERSION constant (dsh-session), read by both write sites
(Session constructor default, SessionStore.prepare header) and the coordinator's
load-time assertVersion check. The constant is pinned at 0: while unreleased the
on-disk format is unstable/pre-release, so breaking shape churn is absorbed at v0
(no monotonic bump until the first tagged release) and any non-0 log is rejected
on load — no migration. Update every test/fixture/doc that stamps a
currently-written header to the constant, bump the ACP snapshot fixture + golden
headers to v0, and keep the version-rejection test meaningful by switching its
bad value to a clearly non-current 99. AGENTS.md documents both the monotonic
(SQLite SCHEMA_VERSION) and pinned-0 (session log) pre-release stances.

Blocker #2 — restore the late turn-end warn. failTurn now sets the error reason
only while the turn is still open; once turn/end is appended (a throwing
agent/turn-end listener after closeTurn) the reason can no longer reach the
durable log, so the late throw is logged via ctx.logger.warn instead of
vanishing into a futile post-close assignment. A regression test asserts the
warn fires.

Also guard the normal-step assistant/message append with the same
content-or-usage condition as the max-tokens branch (a content-less, usage-less
step records no trace-only row), with a covering test.
2026-06-21 11:08:10 +08:00
Tianyi Cui 2be60b9a22 simplify(session): fold trace-only usage/error events into load-bearing events
The session event vocabulary carried two standalone trace-only events that
were not load-bearing as separate records. Fold their facts into nearby
load-bearing events and delete the standalone variants.

- Token usage now rides on `assistant/message` as an optional `usage` field —
  the assembled model output and its accounting travel together. The loop folds
  `assembler.usage` onto the append instead of emitting a separate `usage`
  event.
- The max-tokens path is the no-data-loss host: a step cut off with usage but
  EMPTY content (e.g. only a dropped tool call) previously emitted a standalone
  `usage`; it now records an empty-content `assistant/message { content: [],
  usage }`. `deriveMessages()` skips empty-content assistant messages, so the
  usage host never injects a spurious content-less assistant turn into the
  provider transcript. A step with neither content nor usage appends nothing.
- An operational error's step number now rides on `turn/end.reason` for
  `kind: 'error'` (`{ kind: 'error', step, message, code? }`) — the durable
  turn outcome ACP and resume already consume. `failTurn` sets the reason
  directly (no separate session `error` event). `agent/error` + logging are
  unchanged for live diagnostics.
- No format-version bump: pre-release, no persisted data, so per the format
  policy there is nothing to migrate or reject (the RFC's "refresh the format
  version" criterion over-reached). `version` stays 1.
- ACP fixtures + goldens re-recorded (keyless replay): dropped standalone
  usage/error lines, usage folded onto assistant/message, error step on
  turn/end.reason.

RFC moved proposed -> implemented with an implementation note recording the two
scope refinements.
2026-06-21 10:00:06 +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 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