Commit Graph

3 Commits

Author SHA1 Message Date
Tianyi Cui
072f97c184 refactor(examples): extract reusable logic into tested packages
Logic that lived under examples/ was outside the per-file 100% coverage
gate (examples/ are not workspaces) and, in the stdio-UI case, duplicated
across two examples. Move it into packages/ so it is gated and de-duped.

- packages/ui-stdio (new): unify the two diverged stdio-chat.ts copies into
  one @deepseek-ai/dsh-ui-stdio plugin (welcome/agent Config). A test-only
  I/O seam (createStdioChat(ctx, config, runtime)) keeps process streams out
  of the serializable config and makes every render/EOF/disposal branch
  unit-testable. Per-file 100%. echo/coding cordis.yml now load the package;
  both src/stdio-chat.ts deleted.
- packages/llm-replay (new): move examples/acp-agent/src/llm-replay.ts (+ its
  spec) here so its derive/parse/replay branches fall under the coverage gate.
  cordis.snapshot.yml + README rewired to the package name; added apply/env
  /assertNever/abort tests to reach per-file 100%.
- examples/{echo,coding}-agent: keyless Loader-path e2e smokes that boot the
  real cordis.yml (no key) — the guard a hand-mounted unit test cannot be for
  the unwrapExports/export-shape class (postmortem 0001). examples/AGENTS.md
  codifies the keyless+with-key smoke convention (keyless-by-nature exception
  for echo-agent).
- AGENTS.md: a scoped, removal-triggered pre-release stance (foundation over
  blast radius). packages/README.md: new rows + a FIXME to later regroup ALL
  packages into a hierarchy. Wiring: tsconfig paths/refs, publint, knip,
  module-graph.

Verified: typecheck, lint, test:coverage (887 tests, 100%), build, hygiene,
doc-sync, test:snapshot (10), test:e2e (6 keyless pass, with-key self-skip).
2026-06-19 12:42:28 +08:00
Tianyi Cui
679aaacfc4 refactor(examples): DRY the acp-agent configs via base-core.yml + acp-tail.yml
The snapshot replay config duplicated most of base.yml + the acp tail just to
swap llm-deepseek → llm-replay. Factor the shared pieces:

- examples/base-core.yml: the providerless provider/tool core (llm, sessions,
  system-prompt, tools, agents, invariants, bash-local, tool-bash). base.yml is
  now base-core + the llm-deepseek adapter; the snapshot replay config is
  base-core + llm-replay. The replay config no longer hand-copies the core.
- examples/acp-agent/acp-tail.yml: agent-loop (no pre-created agents) +
  persistence + the ACP bridge/system-prompt, shared by cordis.yml and the
  replay config so the three acp-agent configs can't drift. Its persistence root
  is `$DSH_SNAPSHOT_SESSIONS_ROOT ?? ./.sessions`.
- Deleted cordis.snapshot-record.yml: recording now reuses the normal cordis.yml
  (real adapter), with the harness redirecting the persistence root via env.
  start.ts maps DSH_SNAPSHOT=record → cordis.yml.

Verified: snapshot replay 8/8 keyless; record path works through cordis.yml;
ACP e2e no-key boot green through the doubly-nested include (cordis.yml →
base.yml → base-core.yml); coding-agent boots clean; all gates pass.
2026-06-19 09:41:10 +08:00
Tianyi Cui
81d434896d feat(acp-example): snapshot harness, normalizers, wiring, and handshake scenario
Adds the snapshot-test harness and the keyless replay pipeline end-to-end.

- snapshot-harness.ts: boots the real acp-agent subprocess via the cordis
  Loader (preserving TSX_TSCONFIG_PATH so unbuilt dsh-* imports resolve from a
  temp cwd), tees raw stdout into an SDK ClientSideConnection, interprets a
  per-scenario input.json DSL (initialize / newSession capturing the random
  sessionId / prompt / cancel), closes stdin to trigger graceful shutdown, and
  harvests the persisted session.jsonl. Failure-safe: a finally block SIGKILLs
  a live child, awaits its exit, and removes both temp dirs even on a thrown
  step or harvest. Raw bytes are buffered and decoded once (no multibyte split).
- snapshot-normalize.ts (+ spec): two pure normalizers (stdout frames + session
  JSONL) scrub cwd, session ids / UUIDs, and JSON-RPC ids, and zero time /
  createdAt — but keep `seq` (deterministic by contract). normalizeStdout throws
  on a non-JSON line (the stdout-purity check).
- start.ts: selects cordis.snapshot.yml (replay, providerless) or
  cordis.snapshot-record.yml (record, real adapter) from DSH_SNAPSHOT, skips
  .env in replay, and disposes the ctx on stdin end so persistence flushes
  before exit (harvest-after-flush, not on the prompt response).
- acp.snapshot.ts: asserts the normalized stdout golden (and, for model
  scenarios, the re-persisted JSONL golden) via toMatchFileSnapshot; record mode
  writes the harvested log back to the scenario fixture; an orphan-fixture guard
  fails on an unregistered scenario dir.
- handshake scenario: initialize + session/new (no model call; a header-only
  session.jsonl, since session/new persists no events).
- vitest.snapshot.config.ts, test:snapshot / test:snapshot:record scripts, a
  pre-push snapshot job, and the knip entry.

Incorporates Codex review: record-fixture writeback, failure-safe teardown,
seq-not-scrubbed, harvest-after-flush. Per docs/rfc/implemented/2026-06-19.
2026-06-19 03:36:12 +08:00