Commit Graph
614 Commits
Author SHA1 Message Date
Tianyi Cui c27b1bba6e Merge branch 'split/agent-factory' into split/session-persistence-sqlite
# Conflicts:
#	yarn.lock
2026-06-16 17:09:39 +08:00
Tianyi Cui 96331432b8 Merge branch 'split/turn-enclosure' into split/session-persistence
# Conflicts:
#	docs/adr/README.md
#	packages/agent-loop/package.json
#	yarn.lock
2026-06-16 17:01:36 +08:00
07akioni dabc2ff411 feat: migrate to pnpm 2026-06-16 14:55:37 +08:00
Tianyi Cui 9126697d87 feat(session-persistence-sqlite): second backend validating the abstraction
Add a SQLite SessionPersistence backend (node:sqlite), a SECOND
implementation built to prove the abstract seam + the shared
runPersistenceContract suite are genuinely backend-agnostic. Each
SessionEvent maps 1:1 onto an events row (session_id, seq, type, time,
data); append is an INSERT inside a transaction asserting the
contiguous-seq contract; the mutable SessionSummary lives in the
sessions metadata row.

It satisfies the SAME contract semantics as the JSONL backend, expressed
over rows instead of file bytes:
- Lazy materialization: create() records intent in memory; no row until
  the first append (a never-appended session is absent from has()/list()
  via a materialized flag set inside the first append transaction).
- Crash-tail-on-load: load() returns events only through the last
  complete turn/end and deletes the uncommitted tail; a seq gap in the
  committed region makes the session unloadable.
- Transactional append: a mid-batch failure (a UNIQUE seq collision from
  a concurrent writer) rolls back entirely, keeping the cursor truthful.

Like the JSONL backend it is also the write-path plugin (session/event →
buffer → session/flush drain, onCreated seed/adopt/collision handling,
HMR seeding, dispose-to-quiescence). The package runs the shared
runPersistenceContract suite plus SQLite-specific tests (transaction
rollback, crash-tail cut, schema version, HMR adoption).

Docs flip every "SQLite is future/deferred" reference (ADR 0016,
architecture.md, the persistence module doc + README) to "implemented;
the contract holds both backends to identical semantics".
2026-06-15 21:45:21 +08:00
Tianyi Cui df4b7d3d9a feat(session-persistence): abstract seam + JSONL backend + wiring
Add the durable session-persistence capability seam (ADR 0016): an
abstract SessionPersistence service (dsh-session-persistence,
ctx.sessionPersistence) defining create/append/load/list/has/delete/
update over the existing SessionEvent — no parallel persisted type — and
a first implementation (dsh-session-persistence-jsonl): an append-only
JSONL log per session with crash-safe atomic writes, truncation-repair
of a never-committed crash tail, and a read/replay path. SessionMeta
(format version, cwd, lineage) travels out-of-log via session.header.

A shared runPersistenceContract suite holds every backend to the same
append-only / contiguous-seq / lazy-materialization / serializability
semantics.

Config-driven create() now uses a per-run ${id}-session-<uuid> session
id so a fixed name no longer collides with an on-disk log once a durable
backend is loaded; each run is a new session (a demo simplification). The
examples drop their hand-rolled session-jsonl.ts and load the JSONL
backend via cordis.yml; CI smoke-loads it too.

The agent-facing create/resume factory that consumes load() is a
separate seam, deferred to a follow-up; this change stops at the load
primitive and does not reach into the loop.
2026-06-15 21:05:46 +08:00
Tianyi Cui 7a39616a06 fix(scripts): harden event-taxonomy brace walk against JSDoc braces (PR 4)
Strip comments before the interface-Events brace walk so a future {@link} tag
(or a // { line) inside an Events block can't unbalance the depth counter.
Codex review flagged this as a latent risk; event names live in code, never in
comments, so stripping loses nothing.
2026-06-14 00:54:47 +08:00
Tianyi Cui 6a528be569 build: doc-sync gates — typecheck doc code blocks + verify event taxonomy (RFC 006 pts 1-2)
Two tsx CI gates make doc/code drift fail fast:
- doc-typecheck extracts every fenced ts block from README/docs/package READMEs,
  compiles them with tsc --noEmit against a temp project (vendor->lib, harness->src
  paths from tsconfig.typecheck.json), and fails on errors. Deliberate sketches opt
  out with ```ts ignore-check; the opt-out ratio is reported and capped.
- verify-event-taxonomy asserts the docs/architecture.md taxonomy table names
  exactly the events declared in the interface Events blocks. This surfaced three
  events the table had been missing (tools/change, llm/adapter-change,
  system-prompt/change), now added.

Doc snippets made compilable with stub imports/declares (1 genuine sketch ignored).
Wired into CI after typecheck. API reports (RFC 006 pt 3) deferred. Graduates RFC
006 pts 1-2 -> ADR 0014.
2026-06-14 00:47:38 +08:00
Tianyi Cui 11a29fdefe feat(invariants): dev-mode event-contract assertions + session-log freeze (RFC 005 pt 3, RFC 008)
New @deepseek-ai/dsh-invariants plugin (pure listeners, off in prod) asserts
the event taxonomy at runtime — seq monotonicity, turn/step nesting, a
tool/result needs a prior tool/call (NOT the converse), legal agent/status
transitions — and deep-freezes logged event data so mutating history throws.
Seeded sessions are checked + frozen on session/created.

The real RFC 008 fix is always-on: deriveMessages now structured-clones the
content it emits, so the loop's sanctioned request/adapter mutation can no
longer reach back and rewrite the append-only log. The pervasive
DeepReadonly<T> type flip is rejected (compile-only, high-noise, castable) —
recorded in ADR 0012, which folds in RFC 008. Wired into both demos.
2026-06-13 23:25:12 +08:00
Tianyi Cui ab19fed77c Add two DeepSeek LLM adapters: dsh-llm-deepseek and dsh-llm-pi-ai
The first real LlmAdapter implementations, shipped as a deliberate pair:
same models and wire protocol, completely different internals, so the
StreamChunk protocol is verified across independent implementations.

- dsh-llm-deepseek: hand-rolled fetch + SSE parser + chunk-translation
  state machine against the official chat-completions format (thinking
  mode via top-level thinking/reasoning_effort; the empty-string
  reasoning_content first chunk; usage attached to the finish chunk or
  trailing; reasoning_content passback on tool-call turns; disjoint
  cache-token accounting).
- dsh-llm-pi-ai: the same endpoint through @earendil-works/pi-ai,
  mapping its event vocabulary (parsed tool arguments, in-stream error
  events, folded reasoning tokens) onto the same chunks.

The agent loop now honors the in-band error path: an adapter that ends
its stream with finish {kind:error|aborted} (the only option for
adapters that can't throw mid-stream, like pi-ai) is translated into a
step error, so the turn ends error/aborted with a logged error event
instead of a normal completed assistant message. This makes the
StreamChunk error contract real for both adapters; docs/architecture.md
and the StreamChunk doc are updated accordingly.

New yarn test:e2e (vitest.e2e.config.ts, *.e2e.ts) runs key-gated
real-API matrices for both adapters across V4 Flash/Pro and all
thinking/effort levels; it self-skips without DEEPSEEK_API_KEY. Unit
suites run against local node:http mock SSE servers at 100% per-file
coverage.
2026-06-13 18:30:03 +08:00
Tianyi Cui 8b5a3ef730 Add bash execution: dsh-bash seam, dsh-bash-local impl, dsh-tool-bash tools
Three packages following the new capability-seam pattern (interface /
implementation / consumer, now documented in docs/architecture.md):

- dsh-bash: abstract BashExecutor service (ctx.bash) + vocabulary types.
- dsh-bash-local: local subprocesses — bash -c per call in a detached
  process group, SIGTERM→SIGKILL group kills, tail-keep truncation with
  full-stream spill files, model-friendly env, background task registry.
- dsh-tool-bash: the bash / bash_output / bash_kill tool schemas with
  runtime arg validation and background completion notices via
  agent.inject(). Non-zero exits are reported, not errored.

Design surveyed against the bash tools of Claude Code, OpenCode, Codex,
and pi (notes in the package READMEs). Permissions/sandbox stay TODO on
the tools/execute waterfall seam; stateful-shell alternatives recorded
in run.ts.
2026-06-13 18:28:10 +08:00
Tianyi Cui 630bbddf9a Replace dumble with tsdown for JS bundling
dumble (0.2.x, ~530 dl/wk, single-maintainer) was a bus-factor risk as
the load-bearing bundler. tsdown (rolldown-based, ~2.5M dl/wk, actively
maintained) replaces it while output stays list-identical, verified by
snapshot diff: 17 JS bundles, externals preserved, schemastery dual
.mjs/.cjs and logger-console node+browser entries intact.

Root tsdown.config.ts uses workspace globs ['vendor/*', 'packages/*']
(explicit, so examples/* stays excluded); two per-package overrides in
vendor/ cover the special shapes and are logged in vendor/README.md as
ours (not upstream sync surface). scripts/build.ts (dumble
orchestration) is deleted; yarn build = tsc -b && tsdown. tsc -b keeps
owning declarations (dts: false, clean: false).

Rationale recorded in ADR 0008 (also covers the direct-esbuild and
pkgroll alternatives).

Gates: lint, typecheck, 134 tests, hygiene (knip/publint/constraints),
demo smoke all green.
2026-06-11 22:34:43 +08:00
Tianyi Cui 9d20a36cc4 Add lefthook git hooks with a vendor-manifest guard
pre-commit: ESLint --fix on staged files (vendored source excluded),
incremental typecheck, and the vendor-manifest guard — any staged
change under vendor/*/src must be accompanied by a vendor/README.md
update in the same commit, mechanizing the local-modification log
discipline. pre-push: tests + hygiene (knip/publint/constraints).
Hooks call the same package.json scripts CI runs (single source of
truth); installed automatically via postinstall.
2026-06-11 15:07:55 +08:00
Tianyi Cui 6796a3922d Add repo hygiene gates: knip, publint, yarn constraints
knip (workspace-aware config) fails on unused files, exports, and
dependencies in our code — vendor/ excluded, duplicate default+named
exports allowed (intentional API shape). publint checks every
packages/* package.json for publishing correctness (scripts/
publint-all.ts). Yarn constraints (yarn.config.cjs) mechanize the
AGENTS.md package rules: everything private, dsh-* packages declare
cordis as matching peer+dev dependency, uniform 0.0.1 version, ESM.

yarn hygiene runs all three.
2026-06-11 15:04:50 +08:00
Tianyi Cui ae2e08b4d6 Set up monorepo infra: Yarn 4 workspaces, tsc -b + dumble build, vitest
Workspaces are vendor/* (Cordis framework vendored as source) and
packages/* (@deepseek-ai/dsh-* harness packages). Dev/test/demo run
unbuilt via tsx + root tsconfig paths; build = tsc -b (declarations)
+ dumble (JS bundles); tests = vitest + vite-tsconfig-paths.
2026-06-11 10:52:45 +08:00