Commit Graph

1157 Commits

Author SHA1 Message Date
Tianyi Cui
a4152705ef fix review finding: abort tests must prove the hook process is gone
Codex review of the draft: the abort-on-dispose tests asserted only that
dispose() resolved and no warning fired — a regression back to untracked
fire-and-forget runs would pass both. The hook script now records its
shell PID before sleeping, and after dispose the test asserts
process.kill(pid, 0) throws: the drain resolves only after the killed
run settled (the child reaped), so ESRCH-by-dispose-return is
deterministic under the correct implementation, while an alive or
unreaped process fails it. Verified the discriminator by gutting drain()
locally: exactly these two tests fail.
2026-07-07 00:02:10 +08:00
Tianyi Cui
f0ef280848 fix: identity-check name resolutions across the pasted closure; exclude the catalog from i18n pairing
Review findings on the config catalog:

The transitive-paste closure deduplicated by bare name, so two DIFFERENT
package-local declarations sharing a name would silently collapse to the
first one pasted — and a reference satisfied by the wrong declaration is a
wrong catalog, not just a thin one. Every resolution is now identity-checked
by source pointer: reaching the same declaration again is benign, while a
name that resolves to two distinct declarations, to conflicting imports, or
to a declaration in one file and an import in another is a violation — a
verbatim fence has a single flat namespace, so the fix is a rename at the
source. A spec case pins the two-declarations collision.

docs/config-catalog.md also joins the translation-pairing exclusions: it is
a generated English-only doc, and without the entry the gate would accept a
hand-added .zh.md twin it must reject.
2026-07-07 00:00:34 +08:00
Tianyi Cui
eaac3b58a4 fix review findings: close wrapped-expression, alias, and binding-pattern gaps
Codex round-2 review found three adjacent fail-open shapes:

- Wrapped function expressions escaped classification: parentheses,
  as/satisfies casts, and non-null assertions are now peeled before the
  arrow/function test (initializers and default exports), and a
  single-call-signature type literal counts as the surface signature.
  A literal mixing call/construct signatures with anything else is
  refused outright — no single signature to hold the tags against.
- The blanket 'export import X = N.member' skip was unsound (the target
  can be a non-exported namespace member no walk visits): an alias now
  documents itself.
- The heritage extra-parameter duty missed binding-pattern extras,
  which no base declaration can name: they now trigger the standard
  binding-pattern violation.

Five new negative-path tests pin the closed shapes; module doc and RFC
updated.
2026-07-06 23:55:16 +08:00
Tianyi Cui
1fd9e2a9ab Pin request-header content in one snapshot scenario
Every session.jsonl fixture embedded the full composed system prompt and
complete tool-schema list in its request/header event (~8 KB on one line,
identical across the suite), so any prompt or tool-schema edit forced a
re-record or hand-edit of every fixture — see the dynamic-workflows PR for
the churn pattern this removes.

Now exactly one scenario (text-turn, flagged pinsHeader) commits and
compares that content verbatim; every other fixture stores and compares it
as {{system}}/{{tools}} tokens via the new pure scrubRequestHeaders
normalizer (applied to both compare sides and to record-mode writes, so a
re-record cannot reintroduce the content). request/header-delta payloads
are scrubbed the same way; config/reason stay verbatim — a model swap
SHOULD churn every fixture, a prompt edit should not. Replay is unaffected:
script derivation reads only assistant/chunk events.

Fixture meta-guards enforce the split: non-pinning fixtures must be fixed
points of the scrub, the pinning fixture must not be, and exactly one
scenario pins. Committed fixtures migrated through the same function.

Docs: pinned-header RFC (implemented/testing), base snapshot RFC + testing
policy + llm-replay module doc/README updated.
2026-07-06 23:41:20 +08:00
Tianyi Cui
82dfc62620 Merge branch 'config-catalog' into catalog-flatten 2026-07-06 23:38:40 +08:00
Tianyi Cui
fe815c9586 Merge remote-tracking branch 'origin/master' into config-catalog
# Conflicts:
#	docs/rfc/INDEX.md
2026-07-06 23:31:58 +08:00
Tianyi Cui
74502fa8c2 Structured output on the subagent seam: schema subset, capture runtime, spawn/fork support
Carved out of #170 per review feedback — the foundation the workflow tool
builds on, now standing alone on master:

- dsh-tools: the structured-output JSON Schema subset (StructuredOutputSchema,
  assertSupportedOutputSchema, validateStructuredValue) — rejects loud outside
  the enforced subset, listing every violation
- dsh-subagent: SubagentStartRequest.outputSchema / SubagentResult.structured
  become a real capability; the service rejects a schema'd request whose
  provider lacks it
- dsh-subagent-inprocess: the shared structured runtime — one global
  structured_output capture tool, a prepend final-assembly listener that
  strips the placeholder for plain agents and swaps in the run's own schema
  (plus the calling instruction as a trailing section) for structured
  children, an agent/turn-continuation veto once captured, and the
  capture/nudge loop in the run driver (structuredNudgeRetries, cancellation
  honored mid-nudge); lifetime refcounted by backends and live runs
- subagent-spawn / subagent-fork flip outputSchema: true

One deliberate divergence from the #170 revision: the backends do NOT add
'tools' to their plugin inject. Doing so deferred their apply past the todo
plugin, and the delegation tool mirrors provider lifecycle — so the
model-visible tool order of every existing prompt changed, invalidating every
recorded snapshot fixture. The runtime now gates its capture-tool registration
on tools availability itself (sync when live, a scoped inject fiber when the
Loader starts the backend first), keeping this PR byte-invisible to existing
transcripts: all 35 snapshot scenarios pass against master's fixtures
unchanged.
2026-07-06 23:29:08 +08:00
Tianyi Cui
9ca31ab193 fix(hooks): drain detached hook runs on bridge dispose
The emit-shaped hook points (SessionStart, SubagentStart, SubagentStop)
run fire-and-forget: no seam awaits the run chain, so disposing a bridge
could strand a live hook process and let a late continuation inject into
a disposed context. The floating continuation also made the coverage
gate racy: the only coverage of the SubagentStart continuation's
no-context branch arm rode on an un-awaited .then, and on a loaded CI
runner the fork's per-file coverage snapshot beat it — master run
28798191671 failed the 100% branch gate on hooks-claude/src/index.ts at
99.03% (uncovered line 336) with the identical tree passing the PR run
three minutes earlier.

New shared primitive createDetachedRuns() in dsh-hook-protocol: a bridge
tracks each detached run chain, passes the tracker's abort signal to
runHook, and registers drain() as its effect disposer — drain aborts
still-running hook processes (a kill via the bash seam, not a wait out
to the 10-minute default hook timeout), then resolves once every chain
has settled. fiber.dispose() resolving now means the bridge's detached
work is quiescent (docs/defensive-patterns.md).

The subagent marker test disposes the bridge as its sync point, so the
formerly racy branch arm is executed deterministically before the file's
coverage snapshot; new tests pin abort-on-dispose promptness for both
bridges and the tracker's settle/drain contract in hook-protocol.
2026-07-06 23:27:54 +08:00
Tianyi Cui
9ff010cbef fix review findings: close export-form and heritage-exemption gaps
Codex round-1 review found three fail-open paths in the new gate:

- Unhandled export forms passed silently. checkDecl now fails CLOSED on
  unrecognized exported statement kinds, 'export =' is refused outright,
  'export import X = N.member' is an explicit documented skip (alias;
  definition site owns the doc), and ambient 'declare namespace' bodies
  recurse with implicit export semantics.
- Function-like exports escaped the function contract: non-identifier
  default exports and consts with INLINE function-type annotations now
  get full @param/@returns checks (the named-type waiver stays for
  reference annotations only).
- The heritage exemption was name-only: it no longer exempts a public
  override of a protected-only base member, and parameters the base
  never names keep their @param duty (underscore-prefixed renames of a
  base parameter count as the same parameter).

Eight new negative-path tests pin the closed gaps; RFC and module doc
updated to the refined contract.
2026-07-06 23:25:33 +08:00
Tianyi Cui
6d9a4f1258 Merge branch 'config-catalog' into catalog-flatten
# Conflicts:
#	docs/config-catalog.md
#	scripts/gen-config-catalog.ts
2026-07-06 23:16:29 +08:00
Tianyi Cui
fb3f3a69ec fix: extend the schema cross-check to nested key paths; scope the catalog framing
Review findings on the config catalog:

The schema-subset check compared only top-level z.object keys against
top-level type members, so a nested loader-accepted key (agents[].id,
agentOptions.model, capabilities.*) missing from the declared type would
pass the gate unseen. The walk now collects nested object/array
compositions as key paths and resolves each against the declared config
type — through interfaces (heritage included), aliases, literals,
intersections, unions, arrays, indexed access, Partial-style wrappers, and
type references across package-local and workspace imports (re-export
chains included). The check stays presence-only and one-directional, and
only a definite miss is a violation: a path crossing a type the walk
cannot enumerate (an external package's) is skipped, never mis-reported.
The recursion guard applies at named declarations only — a structural
first child shares its span start with its parent, so a span-keyed guard
on every node mistakes ordinary descent for a cycle and silently turns
definite misses into unknowns.

The page and RFC framing also overstated the catalog as the exact
cordis.yml-settable surface: the paste is the plugin's full declared
config type, and a field the runtime schema deliberately excludes (the ACP
bridge's test-injected stream) is a runtime-only seam its own JSDoc marks.
Both now say so.

Five spec cases pin the new behavior: nested hidden key, workspace
intersection via star re-export, Partial wrapper, indexed-access
composition, and the external-type unknown path.
2026-07-06 23:06:03 +08:00
Tianyi Cui
116e2c45a8 docs: flatten single-file catalog dirs to docs/tool-catalog.md and docs/persistence-catalog.md
A directory holding exactly one file adds a path level for nothing;
cordis-catalog/ keeps its folder because it holds two sibling pages
(events.md + services.md), matching config-catalog.md which was born flat.

docs/tool-catalog/tools.md -> docs/tool-catalog.md and
docs/persistence-catalog/log-events.md -> docs/persistence-catalog.md; their
generators' OUT paths and in-page relative links drop one directory level,
and every citation repo-wide (docs, RFCs, package READMEs, generator
headers) is updated. graph-atlas.md, config-catalog.md, and the doc graphs
regenerate with the new paths.
2026-07-06 22:26:06 +08:00
Tianyi Cui
54bb9e5ec7 Merge remote-tracking branch 'origin/master' into worktree-export-jsdoc-gate
# Conflicts:
#	docs/development.i18n.yaml
#	docs/rfc/INDEX.md
2026-07-06 22:25:39 +08:00
Tianyi Cui
0606cd559c Merge pull request #175 from deepseek-harness/codex/parallelize-ci
ci: parallelize GitHub workflows
2026-07-06 22:15:13 +08:00
Tianyi Cui
8458530298 Merge branch 'master' into codex/parallelize-ci 2026-07-06 22:12:44 +08:00
Wenlu Wang
654d192c47 Merge pull request #182 from deepseek-harness/codex/gate-cadence-docs
docs: clarify local gate cadence
2026-07-06 22:10:19 +08:00
Tianyi Cui
cd9737d569 Gate JSDoc completeness on every package export
New doc-sync gate verify-export-jsdoc walks every module-level exported
name under packages/*/*/src and requires description prose everywhere,
plus @param per parameter and @returns on non-void annotated returns for
function-like exports, public class methods, properties, and accessors.
The parsing + check helpers move out of gen-cordis-catalog.ts into a
shared scripts/jsdoc.ts so 'documented' means one thing on both gated
surfaces.

Deliberate exemptions (documented in the RFC): heritage-declared class
members (the seam declaration is the doc's one home — the one checker
query in an otherwise pure-AST walk), cordis plugin-protocol slots
(name/inject/reusable/Config/apply, top-level and static), constructors,
overload implementations, declare-module augmentation bodies, and
re-export statements (checked at the defining module).

The 203 under-documented exports the gate found at adoption are filled
in this change, so the gate lands green; generated catalogs/graphs are
regenerated for the shifted line pointers.

RFC: docs/rfc/implemented/process/2026-07-06-export-surface-jsdoc-gate.md
2026-07-06 22:09:30 +08:00
Tianyi Cui
b0c7eadd23 feat: generated plugin config catalog (docs/config-catalog.md)
scripts/gen-config-catalog.ts walks every packages/<group>/<pkg> entry with
the TypeScript compiler API and emits docs/config-catalog.md: per loadable
plugin, the verbatim config declaration (JSDoc included) its apply/constructor
receives in a ts config-catalog fence, the inject requirements, resolved links
for every referenced type (package-local types pasted transitively, other
plugins' config types as intra-page anchors, LINK_MAP names to
core-data-structures, workspace types to source), and terse classification
lists for config-free plugins, abstract seams, and libraries — classification
is total, so a new package cannot go undocumented.

The walk enforces per-field JSDoc prose on every pasted declaration and
statically cross-checks the schemastery schema (z.object keys, z.intersect
composition across packages): every schema-validated key must be a declared
member of the config type. One violation existed repo-wide — the agents[].id
field in dsh-agent-loop — fixed by adding its JSDoc (which shifts the
cordis-catalog services page's source pointers; regenerated).

verify-config-catalog (--check) joins doc-sync; doc-typecheck learns the
ts config-catalog fence; gen-cordis-catalog exports its JSDoc/pointer helpers
and LINK_MAP for reuse. Negative-path spec in
packages/core/agent-core/tests/gen-config-catalog.spec.ts mirrors the
gen-cordis-catalog spec. Decision record:
docs/rfc/implemented/process/2026-07-06-generated-config-catalog.md (includes
the deliberate acceptance of README ## Config overlap).
2026-07-06 21:57:17 +08:00
Tianyi Cui
9df79534a6 Merge branch 'master' into codex/gate-cadence-docs 2026-07-06 21:55:48 +08:00
Tianyi Cui
c57c7527a5 Merge branch 'master' into codex/parallelize-ci 2026-07-06 21:54:07 +08:00
Tianyi Cui
1c999804d8 Merge pull request #184 from deepseek-harness/codex/clarify-doc-budget-guidance
Clarify doc budget guidance
2026-07-06 20:46:31 +08:00
Dudu-0223
6beed9a883 test: make the timeout-wins race deterministic under fake timers
CI (node 24/26) failed on the exact-boundary construction: advanceTimersByTime(100)
then an immediate upstream.abort() let the manual abort win the race on some
runtimes, so timeoutOf returned undefined. Advance unambiguously past the deadline
and assert the timeout classification before firing the late abort — that late
abort is now asserted as a no-op, which is the real first-cause-wins invariant.
2026-07-06 19:55:46 +08:00
Yichen Jiang
e30f5e633d add demo smoke to pre-push skill 2026-07-06 19:09:36 +08:00
Yichen Jiang
671cbe173e add dsh pre-push checks skill 2026-07-06 18:40:57 +08:00
Dudu-0223
208123f252 Merge remote-tracking branch 'origin/master' into timeout-design
# Conflicts:
#	knip.json
2026-07-06 17:57:38 +08:00
kingwl
e48a344f6a docs: keep gate cadence guidance agent-only 2026-07-06 17:08:50 +08:00
Dudu-0223
760bc9aa6a fix: scope timeoutOf by deadline code so nesting composes (codex round 2)
Round 2 P2: timeoutOf() accepted ANY TimeoutReason, so under nesting — when
the upstream handed to deadline() is itself a deadline (the RFC's named
tools/execute middleware follow-up) and its outer timer fires first —
AbortSignal.any preserves the outer reason and the inner bash/web would report
the outer timeout as their own (timedOut / WEB_FETCH_TIMEOUT) though their local
timer never expired. Add an optional code to timeoutOf; bash and web pass their
own code, so a foreign timeout falls through to the upstream-cancel path.
2026-07-06 17:07:11 +08:00
Dudu-0223
b4ba84a1a9 fix: drop trailing blank line in fs README (codex review round 1)
Round 1 P3: the "No timeouts on file IO" section left the file ending in a
blank line, which the trailing-newline whitespace gate rejects. Declined P2
(late abort after a timeout is lost): that is the RFC's decided trade-off —
mutually-exclusive first-abort classification — and re-latching aborted would
violate the acceptance criterion.
2026-07-06 16:46:14 +08:00
Hypatia May
41e1955ace Clarify condensing in doc budget guidance 2026-07-06 16:31:18 +08:00
Dudu-0223
8615e019d3 feat(timeout): add dsh-timeout and converge bash + web_fetch onto it
Timeout timing/classification was re-implemented three ways across the
tool-bearing capabilities, with the fusion of timeout+cancel and the
timeout-vs-cancel reason recovery being the error-prone parts. Extract that
shared half into a zero-dependency @deepseek-ai/dsh-timeout library
(clampTimeout/deadline/timeoutOf/TimeoutReason) and leave the non-shareable
hard-kill in each capability, per the timeout-library RFC.

bash: run() owns the deadline; runBash drops its killTimer and no longer
classifies (SpawnSpec/SpawnOutcome lose timeoutMs/timedOut/aborted), so the
public timedOut/aborted booleans become mutually-exclusive first-abort
classifications. web_fetch: the hand-rolled controller/timer/listener/
signal.reason dance is replaced by provider-owned deadline/timeoutOf, keeping
the WEB_FETCH_TIMEOUT / WEB_ABORTED contract. fs stays timeout-free (README
states why).
2026-07-06 16:25:04 +08:00
Hypatia May
9c5da8d81f test: cover corrupt fork boundary guard 2026-07-06 16:16:47 +08:00
Hypatia May
72166dad03 Clarify doc budget guidance 2026-07-06 16:11:56 +08:00
Hypatia May
50ffa0c9a9 test: use portable command in fs policy snapshot 2026-07-06 16:00:52 +08:00
Yichen Jiang
510e80e447 test: update ACP request-header snapshots 2026-07-06 14:35:24 +08:00
Hypatia May
6a7bb4045b test: normalize platform sed snapshot output 2026-07-06 14:35:22 +08:00
Hypatia May
aed594813b Merge remote-tracking branch 'origin/master' into session-fork
# Conflicts:
#	docs/architecture.md
#	docs/cordis-catalog/services.md
2026-07-06 14:30:36 +08:00
Yichen Jiang
929e6f21e9 Merge branch 'master' into codex/ask-user-question 2026-07-06 14:22:53 +08:00
Hypatia May
71f71816b9 fix: keep fork boundary check simple 2026-07-06 14:17:31 +08:00
Hypatia May
37aac7f313 fix: collapse session fork to one api 2026-07-06 13:57:59 +08:00
kingwl
e1b85da182 docs: trim daily command wording 2026-07-06 13:45:17 +08:00
kingwl
7af8f98080 docs: clarify local gate cadence 2026-07-06 13:45:17 +08:00
Tianyi Cui
cbc9c26366 Merge pull request #179 from deepseek-harness/worktree-reconstructable-requests
Make every LLM request a pure function of the session log
2026-07-06 13:37:20 +08:00
Hypatia May
37f3aedc0b docs: keep architecture summary within budget 2026-07-06 12:37:54 +08:00
Hypatia May
0bf8749128 fix: fold session fork into session store 2026-07-06 12:35:34 +08:00
Hypatia May
cf036299f3 Merge remote-tracking branch 'origin/master' into session-fork
# Conflicts:
#	docs/architecture.md
#	docs/cordis-catalog/events-and-services.md
#	docs/core-data-structures/core.md
#	docs/module-graph.md
#	docs/rfc/README.md
#	packages/README.md
#	tsconfig.base.json
2026-07-06 12:20:00 +08:00
Yichen Jiang
7d7c4a3c16 Merge remote-tracking branch 'origin/master' into codex/ask-user-question
# Conflicts:
#	docs/rfc/README.md
#	packages/ui/acp-agent/src/index.ts
2026-07-06 10:08:58 +08:00
Tianyi Cui
39f2898502 fix review findings: last stale compact README line; stale-mark the Code Mode proposal
The compact-basic README's opening line still described summarization
as routed through the agent request pipeline — corrected to the direct
one-shot llm/stream contract the rest of the package documents. The
proposed Code Mode RFC gets a premise-stale note: it names the old
full-request agent/request seam, which now shapes call config only —
re-map onto the log channels and system-prompt/assemble before
implementing from it.
2026-07-06 04:22:37 +08:00
Tianyi Cui
8bd80e5e9b fix review findings: one frozen seed through the waterfall; stale agent/request docs
Codex diff review, round 1, two (A) findings:

- The agent/request fallback resolved the RAW seed object — on later
  steps the session's cached header fold — so a delegating listener
  (await next(), mutate, return) could rewrite the fold in place and
  the change would compare as already-baseline: no delta logged, the
  persisted log unable to reconstruct the request (the dev invariant
  would fire on the divergence, but the log would still lie). One
  structuredClone'd, deep-frozen seed now serves both the listener
  chain and the fallback — in-place shaping after delegation throws —
  and Session.requestHeader() freezes its fold on update, so the leak
  class is unrepresentable from either side. Pinned by a loop-level
  delegating-mutator test.
- Doc sweep for the old contract: agent README's event row (mutate
  GenerateOptions / tool filtering → frozen config seed, replacement
  out, logged header), compact-basic's module JSDoc (summarize routed
  through agent/request → direct one-shot at llm/stream), and
  architecture.md's event-domain line (request mutation → call-config
  shaping).
2026-07-06 04:17:29 +08:00
Tianyi Cui
23ed47ba75 chore: knip learns agent-loop's e2e entry (ported from #162 with request-cache.e2e.ts) 2026-07-06 04:01:56 +08:00
Tianyi Cui
c58e5adee7 test: close the coverage gaps the gate found
surface.replaceGeneration gets its direct-access test (the getter folds
a pending delta itself, not only via a nodes read); the reconstruction
theorem drives maxTokens and stop through the config waterfall; the
compact-basic envelope drops a dead conditional — config.maxTokens is
required and validated positive, so this backend's envelope always
carries the cap (the return type's optionality exists for overriding
subclasses).
2026-07-06 03:55:30 +08:00