Explorar o código

Merge branch 'worktree-subagent-seam-pr2.5' into worktree-subagent-seam-pr3

# Conflicts:
#	docs/module-graph.md
#	packages/subagent/README.md
Tianyi Cui hai 2 meses
pai
achega
2086804b7e
Modificáronse 100 ficheiros con 933 adicións e 351 borrados
  1. 9 10
      .github/workflows/ci.yml
  2. 7 6
      AGENTS.md
  3. 15 11
      docs/cookbook/adding-a-package.md
  4. 9 7
      docs/cookbook/adding-a-vendored-package.md
  5. 8 7
      docs/development.md
  6. 10 7
      docs/module-graph.md
  7. 1 0
      docs/rfc/README.md
  8. 1 1
      docs/rfc/implemented/architecture/2026-06-20-package-hierarchy.md
  9. 2 2
      docs/rfc/implemented/process/2026-06-11-doc-sync-enforcement.md
  10. 2 2
      docs/rfc/implemented/process/2026-06-11-quality-gates.md
  11. 3 3
      docs/rfc/implemented/process/2026-06-11-tsdown-over-dumble.md
  12. 73 0
      docs/rfc/implemented/process/2026-06-17-ts-build-config.md
  13. 3 3
      eslint.config.mjs
  14. 22 6
      examples/coding-agent/cordis.yml
  15. 4 2
      package.json
  16. 1 1
      packages/README.md
  17. 5 3
      packages/bash/bash-local/package.json
  18. 1 1
      packages/bash/bash-local/tsconfig.json
  19. 5 3
      packages/bash/bash/package.json
  20. 1 1
      packages/bash/bash/tsconfig.json
  21. 5 3
      packages/bash/tool-bash/package.json
  22. 1 1
      packages/bash/tool-bash/tsconfig.json
  23. 5 3
      packages/core/agent-core/package.json
  24. 1 1
      packages/core/agent-core/tsconfig.json
  25. 5 3
      packages/core/agent-loop/package.json
  26. 1 1
      packages/core/agent-loop/tsconfig.json
  27. 5 3
      packages/core/agent/package.json
  28. 1 1
      packages/core/agent/tsconfig.json
  29. 5 3
      packages/core/session/package.json
  30. 1 1
      packages/core/session/tsconfig.json
  31. 5 3
      packages/core/system-prompt/package.json
  32. 1 1
      packages/core/system-prompt/tsconfig.json
  33. 5 3
      packages/core/tools/package.json
  34. 1 1
      packages/core/tools/tsconfig.json
  35. 5 3
      packages/llm/llm-deepseek/package.json
  36. 1 1
      packages/llm/llm-deepseek/tsconfig.json
  37. 5 3
      packages/llm/llm-pi-ai/package.json
  38. 1 1
      packages/llm/llm-pi-ai/tsconfig.json
  39. 5 3
      packages/llm/llm/package.json
  40. 1 1
      packages/llm/llm/tsconfig.json
  41. 5 3
      packages/session-persistence/session-persistence-jsonl/package.json
  42. 1 1
      packages/session-persistence/session-persistence-jsonl/tsconfig.json
  43. 5 3
      packages/session-persistence/session-persistence-sqlite/package.json
  44. 1 1
      packages/session-persistence/session-persistence-sqlite/tsconfig.json
  45. 5 3
      packages/session-persistence/session-persistence/package.json
  46. 1 1
      packages/session-persistence/session-persistence/tsconfig.json
  47. 3 2
      packages/subagent/README.md
  48. 5 3
      packages/subagent/subagent-acp/package.json
  49. 1 1
      packages/subagent/subagent-acp/tsconfig.json
  50. 7 4
      packages/subagent/subagent-fork/package.json
  51. 5 3
      packages/subagent/subagent-fork/src/index.ts
  52. 2 2
      packages/subagent/subagent-fork/tsconfig.json
  53. 28 0
      packages/subagent/subagent-inprocess/README.md
  54. 42 0
      packages/subagent/subagent-inprocess/package.json
  55. 11 10
      packages/subagent/subagent-inprocess/src/index.ts
  56. 85 0
      packages/subagent/subagent-inprocess/tests/subagent-inprocess.spec.ts
  57. 30 0
      packages/subagent/subagent-inprocess/tsconfig.json
  58. 2 12
      packages/subagent/subagent-spawn/README.md
  59. 7 6
      packages/subagent/subagent-spawn/package.json
  60. 4 7
      packages/subagent/subagent-spawn/src/index.ts
  61. 1 1
      packages/subagent/subagent-spawn/tests/subagent-spawn.spec.ts
  62. 3 9
      packages/subagent/subagent-spawn/tsconfig.json
  63. 5 3
      packages/subagent/subagent/package.json
  64. 1 1
      packages/subagent/subagent/tsconfig.json
  65. 5 3
      packages/subagent/tool-subagent/package.json
  66. 1 1
      packages/subagent/tool-subagent/tsconfig.json
  67. 5 3
      packages/support/invariants/package.json
  68. 1 1
      packages/support/invariants/tsconfig.json
  69. 5 3
      packages/support/llm-replay/package.json
  70. 1 1
      packages/support/llm-replay/tsconfig.json
  71. 5 3
      packages/support/subagent-mock/package.json
  72. 1 1
      packages/support/subagent-mock/tsconfig.json
  73. 5 3
      packages/support/ui-stdio/package.json
  74. 1 1
      packages/support/ui-stdio/tsconfig.json
  75. 7 4
      packages/ui/acp-agent/package.json
  76. 1 1
      packages/ui/acp-agent/tsconfig.json
  77. 4 3
      packages/ui/acp-agent/tsdown.config.ts
  78. 5 3
      packages/ui/acp/package.json
  79. 1 1
      packages/ui/acp/tsconfig.json
  80. 7 4
      packages/ui/stdio-agent/package.json
  81. 1 1
      packages/ui/stdio-agent/tsconfig.json
  82. 4 3
      packages/ui/stdio-agent/tsdown.config.ts
  83. 5 3
      packages/util/brand/package.json
  84. 1 1
      packages/util/brand/tsconfig.json
  85. 36 0
      pnpm-lock.yaml
  86. 51 0
      scripts/check-workspace-constraints.ts
  87. 25 34
      scripts/doc-typecheck.ts
  88. 160 0
      scripts/verify-node-next-types.ts
  89. 6 6
      tsconfig.base.json
  90. 1 0
      tsconfig.build.json
  91. 51 1
      tsconfig.json
  92. 0 10
      tsconfig.test.json
  93. 0 32
      tsconfig.typecheck.json
  94. 4 4
      tsdown.config.ts
  95. 4 3
      vendor/README.md
  96. 5 3
      vendor/cordis/package.json
  97. 6 6
      vendor/cordis/src/context.ts
  98. 4 4
      vendor/cordis/src/events.ts
  99. 5 5
      vendor/cordis/src/fiber.ts
  100. 7 7
      vendor/cordis/src/index.ts

+ 9 - 10
.github/workflows/ci.yml

@@ -33,19 +33,17 @@ jobs:
       - name: Constraints
         run: pnpm run constraints
 
-      # Before lint: the type-aware ESLint config resolves vendor packages via
-      # their built declarations (tsconfig.typecheck.json -> vendor/*/lib),
-      # which `pnpm run typecheck` emits. Lint on a fresh checkout would otherwise
-      # see unresolved types and erupt with no-unsafe-* errors.
+      # Before lint: root typecheck validates the package/vendor reference graph
+      # and refreshes TSC intermediates so type-aware ESLint sees the same project
+      # boundaries as the build.
       - name: Typecheck (src + tests + examples)
         run: pnpm run typecheck
 
       - name: Lint
         run: pnpm run lint
 
-      # Doc-sync gates (doc-sync-enforcement RFC). doc-typecheck compiles the fenced ts blocks in
-      # the docs and resolves vendor packages via their built declarations, which
-      # the typecheck step above emits — so it runs after typecheck. The cordis
+      # Doc-sync gates (doc-sync-enforcement RFC). doc-typecheck compiles the
+      # fenced ts blocks against the root project-reference graph. The cordis
       # catalog freshness check, type-equiv check, and markdown wrap/link checks
       # only read source. Same `doc-sync` script the pre-push hook runs
       # (quality-gates RFC: one source of truth).
@@ -71,12 +69,13 @@ jobs:
         run: pnpm run test:snapshot
 
       # Before hygiene: publint validates the packed artifacts (lib/index.js),
-      # which only the tsdown bundling step emits.
+      # which only the tsdown bundling step emits, and verify-node-next-types
+      # validates the built declarations.
       - name: Build (tsc -b + tsdown bundles)
         run: pnpm run build
 
-      - name: Hygiene (knip + publint)
-        run: pnpm run knip && pnpm run publint
+      - name: Hygiene (knip + publint + constraints + NodeNext types)
+        run: pnpm run hygiene
 
       - name: Demo smoke test
         run: |

+ 7 - 6
AGENTS.md

@@ -139,14 +139,13 @@ pnpm run test:snapshot  # ACP snapshot tests (examples/*/tests/**/*.snapshot.ts)
 pnpm run test:snapshot:record  # re-record fixtures + goldens against the real
                     # API (needs DEEPSEEK_API_KEY); accept-the-diff = re-record
                     # (or `pnpm run test:snapshot -u` to refresh goldens only)
-pnpm run typecheck      # tsc -b tsconfig.build.json (declarations) + tsc -p
-                    # tsconfig.typecheck.json (tests/examples typecheck too)
+pnpm run typecheck      # tsc -b tsconfig.json
 pnpm run lint           # eslint .
 pnpm run lint:fix       # eslint . --fix
-pnpm run build          # tsc -b tsconfig.build.json && tsdown (JS bundles into lib/)
+pnpm run build          # tsc emits lib/types, then tsdown bundles runtime lib/index.*
 pnpm run knip           # dead-code / unused-dependency check
 pnpm run publint        # package.json publish-correctness check (every packages/*/* package)
-pnpm run hygiene        # knip + publint + workspace constraints
+pnpm run hygiene        # knip + publint + workspace constraints + NodeNext type-consumer check
 pnpm run doc-typecheck  # typecheck every ```ts block in README.md, docs/**/*.md,
                     # packages/*/*.md + packages/*/*/*.md (doc/code drift gate)
 pnpm run gen-cordis-catalog  # regenerate docs/cordis-catalog/events-and-services.md
@@ -161,6 +160,8 @@ pnpm run verify-package-paths  # assert every packages/<path> cited in Markdown
 pnpm run verify-rfc-classification  # assert every RFC lives in a valid
                     # {lifecycle}/{class}/ folder and docs/rfc/README.md lists it
                     # under the matching heading (closed class set + index completeness)
+pnpm run verify-node-next-types  # assert built declarations typecheck for a
+                    # standard external NodeNext ESM TypeScript consumer
 pnpm run doc-sync       # doc-typecheck + verify-cordis-catalog + verify-md-wrap + verify-md-links + verify-doc-refs + verify-package-paths + verify-rfc-classification + verify-type-equiv (CI runs this)
 pnpm run demo:echo      # run examples/echo-agent (no API key; type "echo hi" to
                     # see a tool call) — the mock skeleton
@@ -184,12 +185,12 @@ cordis.yml configs reference env vars with the `!!js` tag: `apiKey: !!js process
 
 **Lean on with-key e2e tests — we are DeepSeek and model inference is cheap.** A no-key test (mock adapter, or an operation that never reaches the model) is great for determinism and CI, but it can only prove the plumbing, not that the agent actually *works* against a real model. Do not ration real-API tests to save tokens: write many of them, cover the real flows (a real prompt that writes a file, a multi-turn conversation, tool use, cancellation mid-stream), and run them frequently while developing — locally and whenever you have a key in the environment. **Especially smoke tests**: a cheap with-key smoke test that boots the real example, sends one real prompt, and checks the world (a file on disk, a non-empty assistant turn) catches whole classes of "green unit tests, broken product" failures that mocks structurally cannot — the very gap that let the ACP inject bug ship (see [docs/postmortem/0001](docs/postmortem/0001-acp-default-export-drops-inject.md)). The self-skip rule is ONLY so CI (which has no secrets) stays green and so a contributor without a key isn't blocked — it is not a signal that real-API tests are expensive or second-class. When in doubt, add the with-key test AND run it.
 
-Dev/test/demo run **unbuilt** via tsx + the `paths` map in the root `tsconfig.json` (`vitest` resolves through `tsconfig.test.json`). Building is only needed for publishing/consumption outside the repo — with one exception: `pnpm run lint`'s type-aware rules resolve vendor packages through their built declarations (`tsconfig.typecheck.json` → `vendor/*/lib`), so run `pnpm run typecheck` once after a fresh clone (CI does the same) or lint reports unresolved-type `no-unsafe-*` errors.
+Dev/test/demo run **unbuilt** via tsx + the source `paths` map in the root `tsconfig.json` (`vitest` resolves through that same root config). Building is only needed for publishing/consumption outside the repo. Non-published code (`examples`, tests, and scripts) is checked by root `tsconfig.json`, which sets `noEmit` and references the package/vendor graph so those sources stay checked under their own tsconfig boundaries.
 
 ## Conventions
 
 - **Package naming**: every npm package in this repo is `@deepseek-ai/dsh-<name>` (vendored packages keep their upstream names and are `private: true`).
-- **ESM everywhere** (`"type": "module"`); imports between workspace packages use package names, never relative paths across package boundaries. In-package imports use explicit `.ts` extensions (allowImportingTsExtensions).
+- **ESM everywhere** (`"type": "module"`); imports between workspace packages use package names, never relative paths across package boundaries. In-package relative imports use explicit `.ts` extensions; `rewriteRelativeImportExtensions` turns those into `.js` in emitted JS, while declarations keep explicit `.ts` specifiers that NodeNext/Node16 TypeScript consumers can resolve to sibling `.d.ts` files. `lib/types/**/*.js` is a bundler-only intermediate, not a Node ESM entrypoint.
 - **`cordis` is a peerDependency** (+ devDependency) of every harness package, mirroring upstream convention.
 - **Registrations are effects**: anything a plugin contributes (adapter, tool, section, agent, event listener) goes through `ctx.effect()` / `ctx.on()` so disposal and HMR work. If you write a registry, `register()` must return the disposer.
 - **Typed events via declaration merging**: services declare their events in `declare module 'cordis' { interface Events { … } }`, and their ctx key in `interface Context`. Extensible unions use the merge-extensible-map pattern (see `ContentBlockMap`, `MessageSourceMap`).

+ 15 - 11
docs/cookbook/adding-a-package.md

@@ -5,29 +5,33 @@ The file-by-file checklist for a new `@deepseek-ai/dsh-<name>` package. (Verifie
 ## 1. Create the package
 
 ```
-packages/<name>/
+packages/<group>/<pkg>/
   package.json     # copy from packages/core/tools, adjust name/description/deps
-  tsconfig.json    # extends ../../tsconfig.base.json, rootDir src, outDir lib,
-                   # references: vendor/cosmokit, vendor/cordis (+ vendor/schemastery
-                   # if you use Config, + ../<dep> for each dsh dependency)
+  tsconfig.json    # extends ../../../tsconfig.base.json, rootDir src,
+                   # outDir lib/types, references: ../../../vendor/cosmokit,
+                   # ../../../vendor/cordis (+ ../../../vendor/schemastery if
+                   # you use Config, + ../../<group>/<dep> for each dsh dep)
   src/index.ts     # service default export or plugin (name/inject/apply/Config)
   tests/<x>.spec.ts
   README.md        # service API, events, extension points, design notes
 ```
 
-package.json invariants (enforced by `pnpm run constraints` / `scripts/check-workspace-constraints.ts`): `private: true`, `version: 0.0.1`, `type: module`, `cordis` in BOTH peerDependencies and devDependencies (same range). Mirror every dsh peer dependency in devDependencies. `schemastery` goes in `dependencies` (it is a runtime validator), matching agent-loop.
+Choose an existing group when one matches the package's role (`core`, `llm`, `bash`, `session-persistence`, `ui`, `util`, or `support`). A new group is allowed, but it is a pure container: no `package.json`, no source files, and packages still sit exactly one level below it.
+
+package.json invariants (enforced by `pnpm run constraints` / `scripts/check-workspace-constraints.ts`): `private: true`, `version: 0.0.1`, `type: module`, `main: "lib/index.js"`, `types: "lib/types/index.d.ts"`, `exports["."].types: "./lib/types/index.d.ts"`, `exports["."].default: "./lib/index.js"`, `cordis` in BOTH peerDependencies and devDependencies (same range). Mirror every dsh peer dependency in devDependencies. `schemastery` goes in `dependencies` (it is a runtime validator), matching agent-loop. The `files` list is precise: `lib/index.js`, `lib/types/**/*.d.ts`, `lib/types/**/*.d.ts.map`, and `src`; do not publish `lib/types` JS or JS-map intermediates or stale root declaration files. CLI app packages with a package `bin` include `lib/bin.js` immediately after `lib/index.js` in `files`.
+
+In-package relative imports use explicit `.ts` specifiers in source (for example, `export * from './types.ts'`). The compiler rewrites those to `.js` in emitted JS and leaves explicit `.ts` specifiers in declarations, which standard NodeNext/Node16 TypeScript consumers resolve to the sibling `.d.ts` files.
 
 ## 2. Register it in the root configs
 
 | File | Change |
 |---|---|
-| `tsconfig.base.json` | add `"@deepseek-ai/dsh-<name>": ["./packages/<name>/src"]` to `paths` |
-| `tsconfig.typecheck.json` | same entry (this file overrides the map wholesale) |
-| `tsconfig.build.json` | add `{ "path": "./packages/<name>" }` to `references` |
-| `scripts/publint-all.ts` | add `'packages/<name>'` to the array |
+| `tsconfig.base.json` | no edit for an existing group; for a new group, add a `./packages/<group>/*/src` candidate to the `@deepseek-ai/dsh-*` wildcard |
+| `tsconfig.json` | add `{ "path": "./packages/<group>/<pkg>" }` to `references` |
+| `tsconfig.build.json` | add `{ "path": "./packages/<group>/<pkg>" }` to `references` |
 | `knip.json` | only if the package has non-`*.spec.ts` entries (e.g. `*.e2e.ts` → add a per-workspace override like `packages/llm/llm-deepseek`) |
 
-Covered automatically by globs — no edits needed: root `package.json` workspaces, `tsdown.config.ts`, `vitest.config.ts`, `eslint.config.mjs`.
+Covered automatically by globs or package-manifest discovery — no edits needed: root `package.json` workspaces, `scripts/publint-all.ts`, `tsdown.config.ts`, `vitest.config.ts`, `eslint.config.mjs`, `scripts/check-workspace-constraints.ts`.
 
 ## 3. Decide the package topology
 
@@ -39,7 +43,7 @@ For a swappable capability, split interface / implementation / consumer into sep
 pnpm install        # registers the workspace
 pnpm run constraints && pnpm run typecheck && pnpm run lint
 pnpm run test:coverage  # 100% per-file over src (types.ts exempt)
-pnpm run build && pnpm run knip && pnpm run publint
+pnpm run build && pnpm run hygiene
 ```
 
 Test expectations: every registry/registration needs an HMR-safety test (register from a child fiber, dispose it, assert cleanup). Excessive tests are welcome — see AGENTS.md.

+ 9 - 7
docs/cookbook/adding-a-vendored-package.md

@@ -12,13 +12,13 @@ vendor/<dir>/
   README.md LICENSE # if upstream ships them
 ```
 
-`tsconfig.json` mirrors the other vendored packages — `rootDir: src`, `outDir: lib`, the strictness relaxations upstream code needs, and a `references` entry for every other vendored package it imports:
+`tsconfig.json` mirrors the other vendored packages — `rootDir: src`, `outDir: lib/types`, the strictness relaxations upstream code needs, and a `references` entry for every other vendored package it imports:
 
 ```jsonc
 {
   "extends": "../../tsconfig.base.json",
   "compilerOptions": {
-    "rootDir": "src", "outDir": "lib",
+    "rootDir": "src", "outDir": "lib/types",
     "noUncheckedIndexedAccess": false, "exactOptionalPropertyTypes": false,
     "noImplicitOverride": false, "noUnusedLocals": false, "noUnusedParameters": false
   },
@@ -27,19 +27,21 @@ vendor/<dir>/
 }
 ```
 
-`package.json` invariants: `"private": true` (vendored packages are never published), keep upstream's `name`/`version`/`exports`/`type`, and list its cordis deps in `peerDependencies` (matching the upstream manifest). Transitive upstream deps must themselves be vendored or already present — vendoring one package often means vendoring its dependency tree (e.g. `@cordisjs/plugin-http` pulls `@cordisjs/fetch-file`).
+`package.json` invariants: `"private": true` (vendored packages are never published), keep upstream's `name`/`version`/`exports`/`type`, point declaration metadata at `lib/types`, publish `.d.ts` and `.d.ts.map` declaration outputs, and list its cordis deps in `peerDependencies` (matching the upstream manifest). Transitive upstream deps must themselves be vendored or already present — vendoring one package often means vendoring its dependency tree (e.g. `@cordisjs/plugin-http` pulls `@cordisjs/fetch-file`).
+
+Local relative imports/exports in vendored TypeScript source use explicit `.ts` specifiers after copying. This is a repo-local build-shape divergence from upstream: `rewriteRelativeImportExtensions` emits `.js` runtime imports while declarations keep explicit `.ts` specifiers that NodeNext/Node16 TypeScript consumers can resolve.
 
 ## 2. Register it in the root configs
 
 | File | Change |
 |---|---|
 | `tsconfig.base.json` | add `"<npm-name>": ["./vendor/<dir>/src"]` to `paths` |
-| `tsconfig.typecheck.json` | add `"<npm-name>": ["./vendor/<dir>/lib"]` — this file points at built declarations, not src. If the package's `types` entry isn't `lib/index.d.ts`, point at that built file instead (e.g. `logger-console` maps to `./vendor/logger-console/lib/shared`, matching its `"types": "lib/shared.d.ts"`). |
+| `tsconfig.json` | add `{ "path": "./vendor/<dir>" }` to `references` |
 | `tsconfig.build.json` | add `{ "path": "./vendor/<dir>" }` to `references` (before the `packages/*` entries) |
 | `vendor/README.md` | add a manifest table row (dir, npm name, version, upstream repo, commit SHA) and log any local modifications |
 | `scripts/publint-all.ts` | only if the vendored package is itself published from here (vendored deps normally are not — skip) |
 
-Covered automatically by globs — no edits needed: root `package.json` workspaces (`vendor/*`), `tsdown.config.ts`, `vitest.config.ts`, `eslint.config.mjs`. A per-package `vendor/<dir>/tsdown.config.ts` is needed ONLY if the build shape diverges from the root default (dual ESM/CJS or multiple entries — see `vendor/schemastery` and `vendor/logger-console`).
+Covered automatically by globs — no edits needed: root `package.json` workspaces (`vendor/*`), `tsdown.config.ts`, `vitest.config.ts`, `eslint.config.mjs`. A per-package `vendor/<dir>/tsdown.config.ts` is needed ONLY if the build shape diverges from the root default (dual ESM/CJS or multiple entries — see `vendor/schemastery` and `vendor/logger-console`); its entry should read the JS emitted under `lib/types`.
 
 ## 3. Mind the manifest guard
 
@@ -49,8 +51,8 @@ Covered automatically by globs — no edits needed: root `package.json` workspac
 
 ```sh
 pnpm install        # registers the workspace
-pnpm run typecheck  # the base→lib path split means: run once after a fresh add
+pnpm run typecheck
 pnpm run build && pnpm run test && pnpm run constraints
 ```
 
-Note the `tsconfig` two-map split (called out in [AGENTS.md](../../AGENTS.md) § Secrets/.env): `lint`'s type-aware rules resolve vendored packages through their built `lib/` declarations, so run `pnpm run typecheck` (which builds them) once after adding the package or lint reports unresolved-type errors.
+The source `paths` map is shared by build and root typecheck configs. The important isolation boundary is the project-reference graph: vendored source must be referenced through its own `vendor/<dir>/tsconfig.json`, not pulled into a root strict program.

+ 8 - 7
docs/development.md

@@ -31,7 +31,7 @@ Run typecheck once after a fresh clone:
 pnpm run typecheck
 ```
 
-That first typecheck builds declaration output used by type-aware linting for vendored packages. Without it, `pnpm run lint` can report unresolved-type `no-unsafe-*` errors even when source code is fine.
+That first typecheck runs the package/vendor build graph and the root no-emit `tsconfig.json` graph for examples, tests, and scripts. The root graph uses the same source `paths` map but relies on project references so vendored code is checked under its own tsconfig settings.
 
 If you are preparing to push from a fresh clone or worktree, also build once:
 
@@ -39,7 +39,7 @@ If you are preparing to push from a fresh clone or worktree, also build once:
 pnpm run build
 ```
 
-`pnpm run hygiene` includes `publint`, which validates package entrypoints against the built `lib/*.js` files. A fresh worktree has no bundled JS until `pnpm run build` runs.
+`pnpm run hygiene` includes `publint`, which validates package entrypoints against the built `lib/*.js` files, and `verify-node-next-types`, which validates built declarations against a temporary NodeNext consumer. A fresh worktree has no bundled JS or declarations until `pnpm run build` runs.
 
 ## Environment variables
 
@@ -76,10 +76,10 @@ The GitHub workflow runs these gates on each pull request:
 - `pnpm run test:coverage`
 - `pnpm run test:snapshot`
 - `pnpm run build`
-- `pnpm run knip && pnpm run publint`
+- `pnpm run hygiene`
 - an echo-agent smoke test that checks the demo's tool call, tool result, and JSONL output
 
-`pnpm run hygiene` is the local shorthand for `pnpm run knip && pnpm run publint && pnpm run constraints`; CI splits `pnpm run constraints` into its own earlier step, then runs `pnpm run knip && pnpm run publint` after `pnpm run build`.
+`pnpm run hygiene` is the local shorthand for `pnpm run knip && pnpm run publint && pnpm run constraints && pnpm run verify-node-next-types`; CI also runs `pnpm run constraints` as an earlier fail-fast step, then runs the full hygiene script after `pnpm run build`.
 
 ## Daily commands
 
@@ -89,7 +89,7 @@ Use these from the repo root:
 pnpm run test           # unit tests
 pnpm run test:coverage  # unit tests with per-file coverage gates
 pnpm run test:e2e       # real-API tests; self-skips without DEEPSEEK_API_KEY
-pnpm run typecheck      # build declarations, then typecheck source, tests, and examples
+pnpm run typecheck      # build package/vendor outputs, then typecheck examples, tests, and scripts
 pnpm run lint           # eslint .
 pnpm run lint:fix       # eslint . --fix
 pnpm run doc-typecheck  # compile checked TypeScript snippets in Markdown docs
@@ -100,8 +100,9 @@ pnpm run verify-type-equiv  # fail if a ```ts type-equiv doc block drifts from i
 pnpm run doc-sync       # doc-typecheck, cordis-catalog freshness, markdown wrap/link, and type-equiv verification
 pnpm run gen-module-graph     # regenerate docs/module-graph.md from package peerDeps
 pnpm run verify-module-graph  # fail if docs/module-graph.md is stale
-pnpm run build          # build declarations and JS bundles
-pnpm run hygiene        # knip, publint, and workspace constraints
+pnpm run build          # emit lib/types intermediates, then bundle lib/index.* runtime files
+pnpm run verify-node-next-types  # fail if built declarations are not NodeNext-consumable
+pnpm run hygiene        # knip, publint, workspace constraints, and NodeNext declaration check
 ```
 
 When changing package public behavior, update the relevant README or JSDoc in the same change. `pnpm run doc-sync` catches checked TypeScript snippets, cordis events/services catalog drift, and hard-wrapped markdown prose, but broader prose/API sync still needs review.

+ 10 - 7
docs/module-graph.md

@@ -63,13 +63,13 @@ graph TD
   subagent-acp --> agent
   subagent-acp --> llm
   subagent-acp --> subagent
+  subagent-inprocess --> agent
+  subagent-inprocess --> llm
+  subagent-inprocess --> session
+  subagent-inprocess --> subagent
   subagent-mock --> agent
   subagent-mock --> llm
   subagent-mock --> subagent
-  subagent-spawn --> agent
-  subagent-spawn --> llm
-  subagent-spawn --> session
-  subagent-spawn --> subagent
   tool-subagent --> agent
   tool-subagent --> llm
   tool-subagent --> subagent
@@ -85,7 +85,9 @@ graph TD
   subagent-fork --> agent
   subagent-fork --> session
   subagent-fork --> subagent
-  subagent-fork --> subagent-spawn
+  subagent-fork --> subagent-inprocess
+  subagent-spawn --> subagent
+  subagent-spawn --> subagent-inprocess
 ```
 
 | Package | Depends on |
@@ -112,9 +114,10 @@ graph TD
 | `tool-bash` | `agent`, `bash`, `llm`, `tools` |
 | `agent-core` | `agent`, `agent-loop`, `invariants`, `llm`, `session`, `system-prompt`, `tool-bash`, `tools` |
 | `subagent-acp` | `agent`, `llm`, `subagent` |
+| `subagent-inprocess` | `agent`, `llm`, `session`, `subagent` |
 | `subagent-mock` | `agent`, `llm`, `subagent` |
-| `subagent-spawn` | `agent`, `llm`, `session`, `subagent` |
 | `tool-subagent` | `agent`, `llm`, `subagent`, `tools` |
 | `acp-agent` | `acp`, `agent-core`, `session-persistence-jsonl` |
 | `stdio-agent` | `agent`, `agent-core`, `session`, `session-persistence-jsonl`, `ui-stdio` |
-| `subagent-fork` | `agent`, `session`, `subagent`, `subagent-spawn` |
+| `subagent-fork` | `agent`, `session`, `subagent`, `subagent-inprocess` |
+| `subagent-spawn` | `subagent`, `subagent-inprocess` |

+ 1 - 0
docs/rfc/README.md

@@ -127,6 +127,7 @@ Do NOT write one for a mechanical or local choice (a variable name, a one-file r
 | [tsdown for JS bundling instead of dumble](implemented/process/2026-06-11-tsdown-over-dumble.md) | 2026-06-11 |
 | [Doc-sync enforcement](implemented/process/2026-06-11-doc-sync-enforcement.md) | 2026-06-11 |
 | [pnpm as the package manager instead of Yarn 4](implemented/process/2026-06-16-pnpm-over-yarn.md) | 2026-06-16 |
+| [TSC-first build and one tsconfig](implemented/process/2026-06-17-ts-build-config.md) | 2026-06-17 |
 | [Markdown cross-link validity linting](implemented/process/2026-06-18-markdown-cross-link-lint.md) | 2026-06-18 |
 | [Core-data-structures catalog and the `ts type-equiv` drift gate](implemented/process/2026-06-20-core-data-structures-catalog.md) | 2026-06-20 |
 | [Generated cordis events + services catalog](implemented/process/2026-06-20-generated-cordis-catalog.md) | 2026-06-20 |

+ 1 - 1
docs/rfc/implemented/architecture/2026-06-20-package-hierarchy.md

@@ -51,7 +51,7 @@ packages/
 
 The package list had been enumerated in five places. The uniform depth-2 layout lets most of them be derived instead:
 
-- `tsconfig.base.json` and `tsconfig.typecheck.json` each map every package through a single `@deepseek-ai/dsh-*` `paths` wildcard listing one candidate per group, in place of 18 per-package entries. (One subtlety this introduced: a path candidate contains `/*/`, which a naive regex comment-stripper mistakes for a block comment — `scripts/doc-typecheck.ts` reads the `paths` map via the TypeScript JSONC API rather than stripping comments by hand for exactly this reason.)
+- `tsconfig.base.json` maps every package through a single `@deepseek-ai/dsh-*` `paths` wildcard listing one candidate per group, in place of per-package entries. Root `tsconfig.json` reuses that source map and carries the explicit project references that keep package/vendor typecheck boundaries intact. (One subtlety this introduced: a path candidate contains `/*/`, which a naive regex comment-stripper mistakes for a block comment — `scripts/doc-typecheck.ts` reads the JSONC config through TypeScript's parser rather than stripping comments by hand for exactly this reason.)
 - `scripts/publint-all.ts` derives its list by reading the hierarchy (`packages/<group>/<pkg>`), resolving the `TODO(package-inventory)`.
 - `tsconfig.build.json`'s project `references` stay an explicit list — TypeScript project references have no wildcard form. Generating these from a manifest is left to a follow-up (see [discover package inventories](../../proposed/process/2026-06-20-discover-package-inventory.md)).
 

+ 2 - 2
docs/rfc/implemented/process/2026-06-11-doc-sync-enforcement.md

@@ -12,10 +12,10 @@ AGENTS.md promises that docs and code stay strictly in sync, but the promise was
 
 Two gates, mirroring the existing `scripts/` style (tsx ESM, one job each):
 
-1. **`doc-typecheck`** extracts every fenced ` ```ts ` block from `README.md`, `docs/**`, and `packages/*/README.md`, writes them to a temp project, and compiles with `tsc --noEmit`. The temp tsconfig copies only resolution-relevant options and the workspace `paths` map from `tsconfig.typecheck.json` (vendor → built `lib`, harness → `src`) — resolving vendor to `lib` is essential, or tsc type-checks raw vendor source and floods the run. A block that is a deliberate sketch opts out with an explicit ` ```ts ignore-check ` info string; the script reports the opt-out ratio and fails if it exceeds half, so the escape hatch can't quietly become the norm.
+1. **`doc-typecheck`** extracts every fenced ` ```ts ` block from `README.md`, `docs/**`, and `packages/*/README.md`, writes them to a temp project extending the root `tsconfig.json`, and compiles it with `tsc -b`. The temp project reuses the source `paths` map and the root project references, so documentation examples see source while vendored code remains checked under its own tsconfig settings. A block that is a deliberate sketch opts out with an explicit ` ```ts ignore-check ` info string; the script reports the opt-out ratio and fails if it exceeds half, so the escape hatch can't quietly become the norm.
 2. **`verify-event-taxonomy`** extracts the event names from the `interface Events` blocks across `packages/*/src` and from the taxonomy table in `docs/architecture.md`, and asserts the two sets match exactly. Verify, don't generate: the table keeps its hand-written Mode/Purpose columns; only the set of names is checked. (Landing this surfaced three events the table had been missing — `tools/change`, `llm/adapter-change`, `system-prompt/change`.) **Superseded** by [the generated cordis catalog](2026-06-20-generated-cordis-catalog.md): this gate and its `architecture.md` table are retired in favor of a fully-generated `docs/cordis-catalog/events-and-services.md` and its `verify-cordis-catalog` freshness gate. The other gates here (`doc-typecheck`, and the `verify-md-wrap` amendment below) are unaffected.
 
-Both run via a shared `doc-sync` package.json script that the lefthook pre-push hook and CI both invoke ([mechanical quality gates](2026-06-11-quality-gates.md): hooks and CI call the same scripts, so the gate fires locally before a push — not only after it). They run after `pnpm run typecheck` (which emits the vendor `lib/` that doc-typecheck resolves against). API-extractor golden reports ([the deferred API-extractor-reports proposal](../../proposed/process/2026-06-11-api-extractor-reports.md)) were deliberately **deferred** — low value for an internal monorepo where reviewers already see the source diff, and a heavy, finicky dependency.
+Both run via a shared `doc-sync` package.json script that the lefthook pre-push hook and CI both invoke ([mechanical quality gates](2026-06-11-quality-gates.md): hooks and CI call the same scripts, so the gate fires locally before a push — not only after it). They run after `pnpm run typecheck`, which validates the package/vendor build graph that doc-typecheck references. API-extractor golden reports ([the deferred API-extractor-reports proposal](../../proposed/process/2026-06-11-api-extractor-reports.md)) were deliberately **deferred** — low value for an internal monorepo where reviewers already see the source diff, and a heavy, finicky dependency.
 
 **Amendment (2026-06-17):** a third gate, **`verify-md-wrap`**, was later folded into `doc-sync`. It parses each in-scope Markdown file (`README.md`, `docs/**`, `packages/*/README.md`, plus `AGENTS.md` / `packages/AGENTS.md`) with `mdast-util-from-markdown` + GFM and fails on any `paragraph` node spanning more than one source line, enforcing the AGENTS.md "Markdown is not hard-wrapped" convention. Same verify-don't-generate principle: it reports hard-wraps and never rewrites, so it adds no formatting churn. `doc-sync` is now three gates.
 

+ 2 - 2
docs/rfc/implemented/process/2026-06-11-quality-gates.md

@@ -12,10 +12,10 @@ This codebase is developed primarily by coding agents. Agents follow enforced ga
 
 Every AGENTS.md promise gets a command that exits non-zero, wired into git hooks and CI both calling the same package.json scripts:
 
-- Max-strict TypeScript (`noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, …); tests and examples typecheck in CI via `tsconfig.typecheck.json` (vendored packages resolve as built declarations).
+- Max-strict TypeScript (`noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, …); examples, tests, and scripts typecheck in CI via the root no-emit `tsconfig.json` while package/vendor code stays behind its own project-reference boundary.
 - ESLint strict-type-checked + @stylistic (the house style, enforced); vendored code excluded.
 - Per-file 100% coverage on `packages/*/src` (v8); unreachable defensive guards carry `/* v8 ignore */ ` with stated reasons instead of deletion.
-- knip (dead code/deps), publint (package correctness), workspace constraints (workspace rules: private, cordis peer+dev, uniform version, ESM).
+- knip (dead code/deps), publint (package correctness), workspace constraints (workspace rules: private, cordis peer+dev, uniform version, ESM), and a NodeNext consumer typecheck for built package declarations.
 - lefthook pre-commit (lint staged, typecheck, vendor-manifest guard) and pre-push (tests, hygiene); CI runs the full matrix on node 24/26 plus a demo smoke test driving the echo-agent end to end.
 
 ## Consequences

+ 3 - 3
docs/rfc/implemented/process/2026-06-11-tsdown-over-dumble.md

@@ -15,12 +15,12 @@ Build output currently matters only for `pnpm run build` + publint (nothing publ
 Replace dumble with **tsdown** (rolldown-based, ~2.5M downloads/week, VoidZero-backed, actively released):
 
 - Root `tsdown.config.ts` with `workspace: ['vendor/*', 'packages/*']` (explicit globs, not `workspace: true`, which would also pick up `examples/*` — they have package.json files but are not pnpm workspaces).
-- Shared shape: entry `src/index.ts`, `outDir: 'lib'`, ESM, `platform: node`, `target: es2024`, `fixedExtension: false` (keeps `.js` for `"type": "module"` packages), `dts: false` (tsc -b owns declarations), `clean: false` (lib/ holds tsc's .d.ts output).
+- Shared shape: entry `lib/types/index.js`, `outDir: 'lib'`, ESM, `platform: node`, `target: es2024`, `fixedExtension: false` (keeps `.js` for `"type": "module"` packages), `dts: false` (tsc -b owns declarations), `clean: false` (lib/ also holds TSC's `lib/types` intermediate tree). The entry was originally `src/index.ts`; the [TSC-first build RFC](2026-06-17-ts-build-config.md) later moved tsdown to bundling TSC-emitted JS so TypeScript transform behavior comes from one compiler.
 - Two per-package overrides in vendor/ (ours, like the regenerated tsconfigs; logged in vendor/README.md): schemastery (dual `.mjs`/`.cjs` via `outExtensions`), logger-console (two single-entry passes so the shared base class is inlined into each entry instead of a hash-named chunk, matching upstream's published shape).
-- `scripts/build.ts` deleted; `pnpm run build` = `tsc -b && tsdown`.
+- `scripts/build.ts` deleted; `pnpm run build` = `tsc -b tsconfig.build.json && tsdown`.
 
 Alternatives considered: **direct esbuild script** (most established engine, zero wrapper risk, but hand-maintains the per-package spec table tsdown's workspace mode gives us); **pkgroll** (closest drop-in philosophically, but 78k dl/wk and Rollup-based — strictly weaker maintenance story than tsdown); **keep dumble** (perfect upstream alignment, unacceptable bus factor).
 
 ## Consequences
 
-Output file lists are byte-for-byte-list identical to dumble's (verified by snapshot diff at migration time); externals still come from each package's dependencies/peerDependencies. We give up dumble's exports-field inference — new packages with non-default shapes need a per-package `tsdown.config.ts` instead of just package.json fields. Future option: tsdown could also absorb declaration bundling (isolatedDeclarations) if `tsc -b` ever becomes the bottleneck; that would be a new RFC.
+Runtime bundle outputs still follow the dumble-era public entry shape (`lib/index.js`, plus package-specific variants such as `schemastery`'s `lib/index.mjs`/`lib/index.cjs` and `logger-console`'s `lib/browser.js`); declarations now live under `lib/types` per the [TSC-first build RFC](2026-06-17-ts-build-config.md). Externals still come from each package's dependencies/peerDependencies. We give up dumble's exports-field inference — new packages with non-default shapes need a per-package `tsdown.config.ts` instead of just package.json fields. Future option: tsdown could also absorb declaration bundling (isolatedDeclarations) if `tsc -b` ever becomes the bottleneck; that would be a new RFC.

+ 73 - 0
docs/rfc/implemented/process/2026-06-17-ts-build-config.md

@@ -0,0 +1,73 @@
+# RFC: TSC-first build and one tsconfig
+
+Status: implemented (accepted 2026-06-20)
+
+<!-- XXX: legacy ADR/RFC body format, not yet normalized to a unified RFC template. -->
+
+## Context
+
+The current TypeScript build and typecheck setup had these issues:
+
+- `build` used `tsc` to transform `.ts` to `.d.ts` files for packages under `packages/<group>/<pkg>` and `vendor/*`, and then used `tsdown` to transform `.ts` to bundled `.js` files. This made two tools do TypeScript transform.
+- `typecheck` tended to validate packages, vendor source, examples, tests, and scripts through one root typecheck config.
+
+The goal is to make build and typecheck use matching tsconfig boundaries and TypeScript resolution/transform behavior. Build should generate `.js`, `.d.ts`, `.js.map`, and `.d.ts.map` through one compiler and config, so publish output and type validation stay consistent.
+
+Validation found several concrete technical issues and possible routes:
+
+- `tsdown` uses `oxc` to transform TypeScript, which is not the same behavior as `tsc`.
+    - Bundled `.d.ts` emitted by `tsdown` conflicts with Cordis' internal relative module augmentation shape.
+    - The tsc output is affected by `allowImportingTsExtensions`, so we need to ensure that generated `.js` files do not import `.ts` files and generated `.d.ts` files keep explicit relative specifiers that NodeNext/Node16 accepts. Therefore, in-package relative imports use explicit `.ts` specifiers in TypeScript source and `rewriteRelativeImportExtensions` rewrites those specifiers to `.js` in emitted JS.
+    - Bundled `.js` emitted by `tsdown` is not the same behavior as per-file `.js` emitted by `tsc -b`, such as decorator transform behavior.
+- `vendor/*/src`, examples, tests, and scripts cannot all be plain-included in one root strict program.
+    - Directly typechecking `vendor/*/src` under the root strict config triggers many type errors outside this project's ownership.
+    - Package dependencies under `packages/*/*` on `vendor` are resolved to the `vendor/*/lib` for different tsconfig strictness.
+
+
+## Decision
+
+In-package relative imports use explicit `.ts` specifiers.
+
+`pnpm run build` is a two-stage build:
+
+- Stage 1: `tsc -b tsconfig.build.json` emits per-module `.js`, declarations `.d.ts`, JS sourcemaps `.js.map`, and declaration sourcemaps `.d.ts.map` into each package's `lib/types`. This is the authoritative TypeScript compilation result. For publish we keep `.d.ts` / `.d.ts.map` and ignore `.js` / `.js.map`.
+    - The build project uses the project-reference graph that `tsc -b` compiles. For example, root `tsconfig.build.json` references package and vendor tsconfigs. It validates and emits package/vendor build results.
+- Stage 2: a bundler reads the emitted JS under `lib/types` and writes the bundled runtime entry as `lib/index.js` or `lib/index.mjs` (follow current behavior). This stage is bundling only. It must not read TypeScript source or emit declarations.
+
+`tsdown` is no longer the owner of TypeScript compilation or declaration output.
+
+`pnpm run typecheck` runs build mode over the root `tsconfig.json`.
+- The root `tsconfig.json` is the single development/typecheck project. It typechecks examples, tests, and scripts with `noEmit`, and validates package/vendor source through references.
+- Referenced package/vendor projects keep the same emit behavior as build, so typecheck can refresh their `lib/types` outputs instead of using a separate no-emit graph. Project-specific strictness changes live in the owning `packages/*/*/tsconfig.json` or `vendor/*/tsconfig.json`.
+- The root no-emit project disables `rewriteRelativeImportExtensions`; it emits nothing and includes tests that import helpers across project-reference boundaries. Package/vendor emit projects keep the rewrite enabled.
+
+The command orchestration shape is:
+
+```sh
+pnpm run build:
+tsc -b tsconfig.build.json
+tsdown
+
+pnpm run verify-node-next-types:
+tsx scripts/verify-node-next-types.ts
+
+pnpm run typecheck:
+tsc -b tsconfig.json
+```
+
+`pnpm run demo:*` still runs `src` directly through tsx and root paths, without a compile step.
+
+## Consequences
+
+Build responsibilities are clearer:
+
+- Each module under `packages/<group>/<pkg>` and `vendor/*` has one local tsconfig for build, typecheck, and tools that run source directly, such as `tsx` and `vitest`.
+- The `build` command uses `tsconfig.build.json`. `tsc -b` owns the publishable per-module `.js` and `.d.ts` output, and the bundler owns only `lib/index.*`.
+    - `lib/types/*.d.ts` and `.d.ts.map` are the publish declaration output.
+    - `lib/types/*.d.ts` uses explicit `.ts` relative specifiers, which TypeScript's NodeNext/Node16 resolver maps to sibling `.d.ts` files.
+    - `lib/types/*.js` is only a bundler input and must not be used as a runtime entry or public import target.
+    - `lib/index.*` is the publish runtime output and is generated by the bundler, currently `tsdown`.
+- `pnpm run verify-node-next-types` scans built declarations for relative specifiers without file extensions, then typechecks a temporary external ESM consumer with `moduleResolution: "NodeNext"` against the built `types`/`exports` surface, so declaration specifier regressions fail before publish.
+- The `typecheck` command uses `tsconfig.json`. Examples, tests, and scripts are checked by the root no-emit project, while packages and vendor modules keep the same emit behavior as `build`. Package and vendor source stays behind project-reference boundaries.
+
+The Cordis vendor copy now has one more type-structure divergence from upstream. During upstream sync, that divergence must be reapplied or explicitly retired.

+ 3 - 3
eslint.config.mjs

@@ -36,7 +36,7 @@ export default tseslint.config(
     ],
     languageOptions: {
       parserOptions: {
-        project: ['./tsconfig.typecheck.json'],
+        project: ['./packages/*/*/tsconfig.json', './tsconfig.json'],
         tsconfigRootDir: import.meta.dirname,
       },
     },
@@ -81,13 +81,13 @@ export default tseslint.config(
 
   // --- tests: same rules, minus the friction that fights test ergonomics --
   {
-    files: ['packages/*/*/tests/**/*.ts'],
+    files: ['packages/*/*/tests/**/*.ts', 'examples/*/tests/**/*.ts'],
     extends: [
       ...tseslint.configs.strictTypeChecked,
     ],
     languageOptions: {
       parserOptions: {
-        project: ['./tsconfig.typecheck.json'],
+        project: ['./tsconfig.json'],
         tsconfigRootDir: import.meta.dirname,
       },
     },

+ 22 - 6
examples/coding-agent/cordis.yml

@@ -57,17 +57,21 @@
 
       Use the subagent tool to delegate a focused, self-contained subtask
       to a fresh child agent (it works in its own context and returns only
-      its final result) — give it a complete, standalone instruction.
+      its final result) — give it a complete, standalone instruction. Use
+      subagent_fork instead when the subtask needs THIS conversation's
+      context: the child inherits the log so far.
 
       Check the [exit code: N] marker on every command; investigate
       failures before moving on. Verify your work by running the code or
       tests. Keep answers brief and factual.
 
-# The subagent seam + an in-process spawn backend + the model-facing `subagent`
-# tool, as leaf entries after the app (which provides ctx.agents/ctx.tools). The
-# tool is bound to the `spawn` backend: a delegated task runs as a fresh child
-# agent on this same process. (fork is available too — load dsh-subagent-fork
-# and a second dsh-tool-subagent bound to it with a distinct toolName.)
+# The subagent seam + BOTH in-process backends + two model-facing tools, as leaf
+# entries after the app (which provides ctx.agents/ctx.tools). spawn (a fresh
+# child) and fork (a child seeded with the parent's completed-turn prefix) are
+# independent backends over the shared dsh-subagent-inprocess driver. Exposing
+# both transports is pure config: load each backend, then load dsh-tool-subagent
+# once per backend with a distinct toolName (the tool registry rejects a
+# duplicate name) — no code change.
 - id: subagent
   name: '@deepseek-ai/dsh-subagent'
 
@@ -76,7 +80,19 @@
   config:
     providerName: spawn
 
+- id: subagent-fork
+  name: '@deepseek-ai/dsh-subagent-fork'
+  config:
+    providerName: fork
+
 - id: tool-subagent
   name: '@deepseek-ai/dsh-tool-subagent'
   config:
     provider: spawn
+    toolName: subagent
+
+- id: tool-subagent-fork
+  name: '@deepseek-ai/dsh-tool-subagent'
+  config:
+    provider: fork
+    toolName: subagent_fork

+ 4 - 2
package.json

@@ -13,7 +13,8 @@
   ],
   "scripts": {
     "build": "tsc -b tsconfig.build.json && tsdown",
-    "typecheck": "tsc -b tsconfig.build.json && tsc -p tsconfig.typecheck.json",
+    "clean:build": "rm -rf .typecheck packages/*/*/lib vendor/*/lib *.tsbuildinfo",
+    "typecheck": "tsc -b tsconfig.json",
     "lint": "eslint .",
     "lint:fix": "eslint . --fix",
     "test": "vitest run",
@@ -30,13 +31,14 @@
     "verify-package-paths": "tsx scripts/verify-package-paths.ts",
     "verify-rfc-classification": "tsx scripts/verify-rfc-classification.ts",
     "verify-type-equiv": "tsx scripts/verify-type-equiv.ts",
+    "verify-node-next-types": "tsx scripts/verify-node-next-types.ts",
     "gen-cordis-catalog": "tsx scripts/gen-cordis-catalog.ts",
     "verify-cordis-catalog": "tsx scripts/gen-cordis-catalog.ts --check",
     "gen-module-graph": "tsx scripts/gen-module-graph.ts",
     "verify-module-graph": "tsx scripts/gen-module-graph.ts --check",
     "constraints": "tsx scripts/check-workspace-constraints.ts",
     "doc-sync": "pnpm run doc-typecheck && pnpm run verify-cordis-catalog && pnpm run verify-md-wrap && pnpm run verify-md-links && pnpm run verify-doc-refs && pnpm run verify-package-paths && pnpm run verify-rfc-classification && pnpm run verify-type-equiv",
-    "hygiene": "pnpm run knip && pnpm run publint && pnpm run constraints",
+    "hygiene": "pnpm run knip && pnpm run publint && pnpm run constraints && pnpm run verify-node-next-types",
     "demo:echo": "node --expose-internals --import tsx packages/ui/stdio-agent/src/bin.ts examples/echo-agent/cordis.yml",
     "demo:coding": "node --expose-internals --import tsx packages/ui/stdio-agent/src/bin.ts examples/coding-agent/cordis.yml",
     "demo:acp": "node --import tsx packages/ui/acp-agent/src/bin.ts examples/acp-agent/cordis.yml",

+ 1 - 1
packages/README.md

@@ -92,5 +92,5 @@ Each package has its own `README.md` with purpose, service API, events, extensio
 - **Declaration merging for events and ctx**: services declare their events in `declare module 'cordis' { interface Events { ... } }` and their ctx key in `interface Context`.
 - **Waterfall semantics**: `ctx.waterfall` listeners receive `(...args, next)` and MUST call `next()` to delegate; returning without it short-circuits (the veto mechanism).
 - **Extensible unions**: `ContentBlockMap`, `MessageSourceMap`, `FinishReasonMap`, `TurnTriggerMap`, `TurnEndReasonMap`, and `SessionEventMap` use the merge-extensible-map pattern so plugins can add variants via declaration merging.
-- **ESM everywhere**; imports use package names across package boundaries, `.ts` extensions within a package.
+- **ESM everywhere**; imports use package names across package boundaries and explicit `.ts` relative specifiers within a package.
 - **Tests**: vitest, colocated under `packages/<group>/<pkg>/tests/*.spec.ts`. Every registry needs an HMR-safety test. Err on the side of more tests.

+ 5 - 3
packages/bash/bash-local/package.json

@@ -5,17 +5,19 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/bash/bash-local/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 5 - 3
packages/bash/bash/package.json

@@ -5,17 +5,19 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/bash/bash/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 5 - 3
packages/bash/tool-bash/package.json

@@ -5,17 +5,19 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/bash/tool-bash/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 5 - 3
packages/core/agent-core/package.json

@@ -5,17 +5,19 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/core/agent-core/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 5 - 3
packages/core/agent-loop/package.json

@@ -5,17 +5,19 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/core/agent-loop/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 5 - 3
packages/core/agent/package.json

@@ -5,17 +5,19 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/core/agent/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 5 - 3
packages/core/session/package.json

@@ -5,17 +5,19 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/core/session/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 5 - 3
packages/core/system-prompt/package.json

@@ -5,17 +5,19 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/core/system-prompt/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 5 - 3
packages/core/tools/package.json

@@ -5,17 +5,19 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/core/tools/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 5 - 3
packages/llm/llm-deepseek/package.json

@@ -5,17 +5,19 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/llm/llm-deepseek/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 5 - 3
packages/llm/llm-pi-ai/package.json

@@ -5,17 +5,19 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/llm/llm-pi-ai/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 5 - 3
packages/llm/llm/package.json

@@ -5,17 +5,19 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/llm/llm/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 5 - 3
packages/session-persistence/session-persistence-jsonl/package.json

@@ -5,17 +5,19 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/session-persistence/session-persistence-jsonl/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 5 - 3
packages/session-persistence/session-persistence-sqlite/package.json

@@ -5,17 +5,19 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/session-persistence/session-persistence-sqlite/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 5 - 3
packages/session-persistence/session-persistence/package.json

@@ -5,17 +5,19 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/session-persistence/session-persistence/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 3 - 2
packages/subagent/README.md

@@ -5,11 +5,12 @@ The subagent seam: an agent delegating work to a child agent. Like the [bash](..
 | Package | Role | ctx key |
 |---|---|---|
 | `subagent/` | Abstract subagent seam: named-provider registry + vocabulary | `ctx.subagents` |
-| `subagent-spawn/` | In-process backend: a fresh child agent (+ the shared run driver) | (registers on `ctx.subagents`) |
+| `subagent-inprocess/` | Shared in-process run driver (pure lib; registers nothing) | — |
+| `subagent-spawn/` | In-process backend: a fresh child agent | (registers on `ctx.subagents`) |
 | `subagent-fork/` | In-process backend: a child seeded with the parent's completed-turn prefix | (registers on `ctx.subagents`) |
 | `subagent-acp/` | Out-of-process backend: a child agent in a spawned subprocess, driven over ACP | (registers on `ctx.subagents`) |
 | `tool-subagent/` | Model-facing `subagent` delegation tool over `ctx.subagents` | (registers on `ctx.tools`) |
 
-The interface lives at `subagent/subagent/`. The in-process `subagent-spawn` / `subagent-fork` and the out-of-process `subagent-acp` backends ship here; the test-only `dsh-subagent-mock` (in [support](../support/README.md)) is separate. All **product** packages except the mock.
+The interface lives at `subagent/subagent/`. The in-process `subagent-spawn` / `subagent-fork` backends share the `subagent-inprocess` driver (a pure library — both depend on it, neither on the other), and the out-of-process `subagent-acp` backend ships alongside them here; the test-only `dsh-subagent-mock` (in [support](../support/README.md)) is separate. All **product** packages except the mock.
 
 The proposal and design rationale: [docs/rfc/implemented/feature/2026-06-21-subagent-capability-seam.md](../../docs/rfc/implemented/feature/2026-06-21-subagent-capability-seam.md).

+ 5 - 3
packages/subagent/subagent-acp/package.json

@@ -5,17 +5,19 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/subagent/subagent-acp/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 7 - 4
packages/subagent/subagent-fork/package.json

@@ -5,17 +5,19 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",
@@ -23,7 +25,7 @@
     "@deepseek-ai/dsh-agent": "^0.0.1",
     "@deepseek-ai/dsh-session": "^0.0.1",
     "@deepseek-ai/dsh-subagent": "^0.0.1",
-    "@deepseek-ai/dsh-subagent-spawn": "^0.0.1",
+    "@deepseek-ai/dsh-subagent-inprocess": "^0.0.1",
     "cordis": "^4.0.0-rc.6"
   },
   "dependencies": {
@@ -36,6 +38,7 @@
     "@deepseek-ai/dsh-llm": "workspace:^",
     "@deepseek-ai/dsh-session": "workspace:^",
     "@deepseek-ai/dsh-subagent": "workspace:^",
+    "@deepseek-ai/dsh-subagent-inprocess": "workspace:^",
     "@deepseek-ai/dsh-subagent-spawn": "workspace:^",
     "@deepseek-ai/dsh-system-prompt": "workspace:^",
     "@deepseek-ai/dsh-tools": "workspace:^",

+ 5 - 3
packages/subagent/subagent-fork/src/index.ts

@@ -2,8 +2,10 @@
  * The in-process FORK subagent backend: registers a {@link SubagentProvider} on
  * `ctx.subagents` that runs each child as a child {@link Agent} SEEDED with a
  * prefix of the parent's session log — so the child inherits the parent's
- * conversation context instead of starting fresh. Shares the run driver with
- * `@deepseek-ai/dsh-subagent-spawn`; the only difference is the seed.
+ * conversation context instead of starting fresh. The run mechanics live in
+ * `@deepseek-ai/dsh-subagent-inprocess` ({@link startInProcessRun}); this
+ * backend just computes the seed. The spawn backend is an independent peer over
+ * the same driver.
  *
  * The seed boundary is the crux: at the moment a subagent tool's `execute`
  * runs, the parent's CURRENT turn is open and unbalanced (it holds the
@@ -23,7 +25,7 @@ import z from 'schemastery'
 import type { SessionEvent } from '@deepseek-ai/dsh-session'
 import type { Agent } from '@deepseek-ai/dsh-agent'
 import type { SubagentCapabilities, SubagentProvider, SubagentStartRequest } from '@deepseek-ai/dsh-subagent'
-import { startInProcessRun } from '@deepseek-ai/dsh-subagent-spawn'
+import { startInProcessRun } from '@deepseek-ai/dsh-subagent-inprocess'
 
 export const name = 'subagent-fork'
 export const inject = ['subagents', 'agents']

+ 2 - 2
packages/subagent/subagent-fork/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"
@@ -27,7 +27,7 @@
       "path": "../subagent"
     },
     {
-      "path": "../subagent-spawn"
+      "path": "../subagent-inprocess"
     }
   ]
 }

+ 28 - 0
packages/subagent/subagent-inprocess/README.md

@@ -0,0 +1,28 @@
+# @deepseek-ai/dsh-subagent-inprocess
+
+The shared **in-process subagent run driver**. A pure library (no provider, no registration) that the in-process backends — [spawn](../subagent-spawn/README.md) (a fresh child) and [fork](../subagent-fork/README.md) (a child seeded with a prefix of the parent's log) — both build on. The backends are thin shells that differ ONLY in the session seed they pass; everything downstream lives here, so neither backend depends on the other.
+
+## What it exports
+
+### `startInProcessRun(ctx, request, options): SubagentRun`
+
+Runs a child as a child [`Agent`](../../core/agent) on the same cordis context (`ctx.agents`):
+
+1. computes child depth = `depthOf(parent) + 1`; if `request.maxDepth` is set and exceeded, throws `SubagentDepthError` (the `depthLimit` capability);
+2. creates a child via `ctx.agents.create` with a fresh `AgentId`/`SessionId`, the parent's `cwd` + `parentSession` lineage, the optional `options.seed` (fork's completed-turn prefix; omitted for a fresh child), and `agentOptions` (the child inherits the **parent's model** by default — a child with no model can't run — overridable via `request.agentOptions.model`; the system prompt is NOT inherited);
+3. drives the one-shot: `child.send(prompt)` then `await child.whenIdle()` (ordering matters — `send` enqueues synchronously, so `whenIdle` observes the queued work and resolves on the child's `running → idle` transition, never before the turn starts);
+4. reads the result, scoped to the child's OWN events (everything at or after `seedLength`, so a seeded child that produced no message of its own never returns the seeded parent's last message): the last `assistant/message` content (deep-cloned — the log is frozen) and the last `turn/end.reason` mapped to a `SubagentStopReason`.
+
+`dispose()` delegates to `AgentHandle.dispose()` (stop loop → await quiescence → remove session); `cancel()` cancels the child's in-flight turn. A cancel landing before any `turn/end` (the pre-turn window) still settles `aborted`, honoring the cancel contract rather than the generic no-turn `error`.
+
+### `InProcessRunOptions`
+
+`{ providerName: string; seed?: SessionEvent[] }` — the per-backend inputs: the provider name (for error context) and the optional child-session seed.
+
+### `depthOf(agent): number`
+
+Delegation depth rides on a merge-extensible `AgentOptions.subagentDepth` field (0 for a top-level agent, parent + 1 for a child), so a nested spawn reads its parent's depth from `parent.options.subagentDepth`. `depthOf` reads it (absent ⇒ 0).
+
+### `SubagentDepthError`
+
+Thrown by `startInProcessRun` when a spawn would exceed the request's `maxDepth` cap; carries `attemptedDepth` and `maxDepth`.

+ 42 - 0
packages/subagent/subagent-inprocess/package.json

@@ -0,0 +1,42 @@
+{
+  "name": "@deepseek-ai/dsh-subagent-inprocess",
+  "description": "Shared in-process subagent run driver: drives a child agent on ctx.agents (used by the spawn and fork backends)",
+  "version": "0.0.1",
+  "private": true,
+  "type": "module",
+  "main": "lib/index.js",
+  "types": "lib/types/index.d.ts",
+  "exports": {
+    ".": {
+      "types": "./lib/types/index.d.ts",
+      "default": "./lib/index.js"
+    },
+    "./src/*": "./src/*",
+    "./package.json": "./package.json"
+  },
+  "files": [
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
+    "src"
+  ],
+  "license": "BSD-3-Clause",
+  "peerDependencies": {
+    "@deepseek-ai/dsh-agent": "^0.0.1",
+    "@deepseek-ai/dsh-llm": "^0.0.1",
+    "@deepseek-ai/dsh-session": "^0.0.1",
+    "@deepseek-ai/dsh-subagent": "^0.0.1",
+    "cordis": "^4.0.0-rc.6"
+  },
+  "devDependencies": {
+    "@deepseek-ai/dsh-agent": "workspace:^",
+    "@deepseek-ai/dsh-agent-loop": "workspace:^",
+    "@deepseek-ai/dsh-invariants": "workspace:^",
+    "@deepseek-ai/dsh-llm": "workspace:^",
+    "@deepseek-ai/dsh-session": "workspace:^",
+    "@deepseek-ai/dsh-subagent": "workspace:^",
+    "@deepseek-ai/dsh-system-prompt": "workspace:^",
+    "@deepseek-ai/dsh-tools": "workspace:^",
+    "cordis": "^4.0.0-rc.6"
+  }
+}

+ 11 - 10
packages/subagent/subagent-spawn/src/in-process.ts → packages/subagent/subagent-inprocess/src/index.ts

@@ -1,15 +1,16 @@
 /**
- * The shared in-process subagent run driver. A subagent backend that runs the
- * child as a child {@link Agent} on the SAME cordis context (`ctx.agents`) —
- * the cheapest transport, reusing the agent factory's quiescent
- * {@link AgentHandle} teardown. Both in-process backends use this:
- * `@deepseek-ai/dsh-subagent-spawn` (a fresh child) and
- * `@deepseek-ai/dsh-subagent-fork` (a child seeded with a prefix of the
- * parent's log) differ ONLY in the `seed` they pass — everything downstream
- * (drive the child, read its final output, map the stop reason, dispose) is
- * identical and lives here.
+ * The shared in-process subagent run driver: run a child as a child
+ * {@link Agent} on the SAME cordis context (`ctx.agents`) — the cheapest
+ * transport, reusing the agent factory's quiescent {@link AgentHandle}
+ * teardown. The concrete in-process backends are thin shells over this driver,
+ * differing ONLY in the `seed` they pass (a fresh child vs. a child seeded with
+ * a prefix of the parent's log); everything downstream — drive the child, read
+ * its final output, map the stop reason, dispose — is identical and lives here.
  *
- * @module @deepseek-ai/dsh-subagent-spawn/in-process
+ * This package owns no provider and registers nothing; it is a pure library the
+ * backend packages depend on, so neither backend needs to know about the other.
+ *
+ * @module @deepseek-ai/dsh-subagent-inprocess
  */
 
 import { randomUUID } from 'node:crypto'

+ 85 - 0
packages/subagent/subagent-inprocess/tests/subagent-inprocess.spec.ts

@@ -0,0 +1,85 @@
+import { describe, expect, it } from 'vitest'
+import { Context } from 'cordis'
+import LlmService from '@deepseek-ai/dsh-llm'
+import SessionStore from '@deepseek-ai/dsh-session'
+import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
+import ToolRegistry from '@deepseek-ai/dsh-tools'
+import AgentRegistry, { AgentId, type Agent } from '@deepseek-ai/dsh-agent'
+import AgentLoop from '@deepseek-ai/dsh-agent-loop'
+import * as Invariants from '@deepseek-ai/dsh-invariants'
+import SubagentService from '@deepseek-ai/dsh-subagent'
+import { MockAdapter, textResponse } from '../../../core/agent-loop/tests/mock-adapter.ts'
+import { depthOf, SubagentDepthError, startInProcessRun } from '../src/index.ts'
+
+type Script = ConstructorParameters<typeof MockAdapter>[0]
+
+/**
+ * Drives the shared in-process run driver DIRECTLY (no provider package), so the
+ * driver's own contract — depth read/cap, the one-shot drive, the result read —
+ * is covered independently of which backend (spawn/fork) calls it. The only
+ * mocked boundary is the model; the real agent loop, SubagentService, and
+ * dsh-invariants are mounted, so a malformed child session log fails the test.
+ */
+async function setup(script: Script) {
+  const ctx = new Context()
+  await ctx.plugin(LlmService)
+  await ctx.plugin(SessionStore)
+  await ctx.plugin(SystemPrompt)
+  await ctx.plugin(ToolRegistry)
+  await ctx.plugin(AgentRegistry)
+  await ctx.plugin(Invariants)
+  await ctx.plugin(AgentLoop, { agents: [] })
+  await ctx.plugin(SubagentService)
+  ctx.llm.registerAdapter(['mock'], new MockAdapter(script))
+  const parent = ctx.agentLoop.create(AgentId('parent'), { model: 'mock' })
+  return { ctx, parent }
+}
+
+function text(blocks: { type: string; text?: string }[]): string {
+  return blocks.filter(b => b.type === 'text').map(b => b.text).join('')
+}
+
+describe('depthOf', () => {
+  it('reads 0 for an agent with no subagentDepth, the set value otherwise', async () => {
+    const { parent } = await setup([])
+    expect(depthOf(parent)).toBe(0)
+    const withDepth = { options: { subagentDepth: 3 } } as unknown as Agent
+    expect(depthOf(withDepth)).toBe(3)
+  })
+})
+
+describe('startInProcessRun', () => {
+  it('drives a fresh child (no seed) to completion and returns its output', async () => {
+    const { ctx, parent } = await setup([textResponse('driver child answer')])
+    const run = startInProcessRun(ctx, { prompt: [{ type: 'text', text: 'do X' }], parent }, { providerName: 'spawn' })
+    const result = await run.result
+    expect(result.stopReason).toBe('completed')
+    expect(text(result.output)).toBe('driver child answer')
+    expect(depthOf(ctx.agents.get(run.id)!)).toBe(1)
+    await run.dispose()
+  })
+
+  it('throws SubagentDepthError when the child would exceed maxDepth', async () => {
+    const { ctx, parent } = await setup([])
+    expect(() => startInProcessRun(ctx, { prompt: [{ type: 'text', text: 'p' }], parent, maxDepth: 0 }, { providerName: 'spawn' }))
+      .toThrow(SubagentDepthError)
+  })
+
+  it('seeds the child session when a seed is supplied', async () => {
+    // Drive the parent through one real turn, then seed the child with that
+    // completed-turn prefix — the child must SEE the parent's history but its
+    // result is scoped to its OWN events (not the seeded parent message).
+    const { ctx, parent } = await setup([textResponse('parent turn'), textResponse('seeded child reply')])
+    parent.send([{ type: 'text', text: 'parent q' }])
+    await parent.whenIdle()
+    const seed = parent.session.events.slice()
+    const run = startInProcessRun(ctx, { prompt: [{ type: 'text', text: 'child q' }], parent }, { providerName: 'fork', seed })
+    const result = await run.result
+    expect(result.stopReason).toBe('completed')
+    expect(text(result.output)).toBe('seeded child reply')
+    const child = ctx.agents.get(run.id)!
+    // The child inherited the parent's prefix.
+    expect(child.session.events.slice(0, seed.length).some(e => e.type === 'user/message')).toBe(true)
+    await run.dispose()
+  })
+})

+ 30 - 0
packages/subagent/subagent-inprocess/tsconfig.json

@@ -0,0 +1,30 @@
+{
+  "extends": "../../../tsconfig.base.json",
+  "compilerOptions": {
+    "rootDir": "src",
+    "outDir": "lib/types"
+  },
+  "include": [
+    "src"
+  ],
+  "references": [
+    {
+      "path": "../../../vendor/cosmokit"
+    },
+    {
+      "path": "../../../vendor/cordis"
+    },
+    {
+      "path": "../../core/agent"
+    },
+    {
+      "path": "../../llm/llm"
+    },
+    {
+      "path": "../../core/session"
+    },
+    {
+      "path": "../subagent"
+    }
+  ]
+}

+ 2 - 12
packages/subagent/subagent-spawn/README.md

@@ -2,17 +2,11 @@
 
 The in-process **spawn** subagent backend: a [`SubagentProvider`](../subagent/README.md) that runs each child as a **fresh** child [`Agent`](../../core/agent) on the same cordis context (`ctx.agents`) — its own session, its own (or the parent's) model, zero inherited conversation. The cheapest transport, reusing the agent factory's quiescent [`AgentHandle`](../../core/agent) teardown.
 
-It also exports the **shared in-process run driver** (`startInProcessRun`) that the [fork](../subagent-fork/README.md) backend builds on — spawn and fork differ only in the session seed.
+The run mechanics live in the shared [`@deepseek-ai/dsh-subagent-inprocess`](../subagent-inprocess/README.md) driver (`startInProcessRun`); this backend just passes **no seed** (a fresh child). The [fork](../subagent-fork/README.md) backend is an independent peer over the same driver — neither knows about the other.
 
 ## What it does
 
-`start(request)` →
-1. computes child depth = `depthOf(parent) + 1`; if `request.maxDepth` is set and exceeded, throws `SubagentDepthError` (the `depthLimit` capability);
-2. creates a child via `ctx.agents.create` with a fresh `AgentId`/`SessionId`, the parent's `cwd` + `parentSession` lineage, and `agentOptions` (the child inherits the **parent's model** by default — a child with no model can't run — overridable via `request.agentOptions.model`; the system prompt is NOT inherited);
-3. drives the one-shot: `child.send(prompt)` then `await child.whenIdle()` (ordering matters — `send` enqueues synchronously, so `whenIdle` observes the queued work and resolves on the child's `running → idle` transition, never before the turn starts);
-4. reads the result: the last `assistant/message` content (deep-cloned — the log is frozen) and the last `turn/end.reason` mapped to a `SubagentStopReason`.
-
-`dispose()` delegates to `AgentHandle.dispose()` (stop loop → await quiescence → remove session); `cancel()` cancels the child's in-flight turn.
+`start(request)` delegates to `startInProcessRun(ctx, request, { providerName })` with no seed: a fresh child agent with the parent's `cwd`/`parentSession` lineage and (by default) the parent's model. See the [driver README](../subagent-inprocess/README.md) for the full lifecycle (depth check, one-shot drive, result read, dispose).
 
 ## Capabilities
 
@@ -23,7 +17,3 @@ It also exports the **shared in-process run driver** (`startInProcessRun`) that
 | Key | Meaning |
 |---|---|
 | `providerName` | Registry name on `ctx.subagents` (default `spawn`). |
-
-## Depth tracking
-
-Delegation depth rides on a merge-extensible `AgentOptions.subagentDepth` field (0 for a top-level agent, parent + 1 for a child), so a nested spawn reads its parent's depth from `parent.options.subagentDepth`. Read it with the exported `depthOf(agent)`.

+ 7 - 6
packages/subagent/subagent-spawn/package.json

@@ -5,25 +5,25 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",
   "peerDependencies": {
-    "@deepseek-ai/dsh-agent": "^0.0.1",
-    "@deepseek-ai/dsh-llm": "^0.0.1",
-    "@deepseek-ai/dsh-session": "^0.0.1",
     "@deepseek-ai/dsh-subagent": "^0.0.1",
+    "@deepseek-ai/dsh-subagent-inprocess": "^0.0.1",
     "cordis": "^4.0.0-rc.6"
   },
   "dependencies": {
@@ -38,6 +38,7 @@
     "@deepseek-ai/dsh-llm-deepseek": "workspace:^",
     "@deepseek-ai/dsh-session": "workspace:^",
     "@deepseek-ai/dsh-subagent": "workspace:^",
+    "@deepseek-ai/dsh-subagent-inprocess": "workspace:^",
     "@deepseek-ai/dsh-system-prompt": "workspace:^",
     "@deepseek-ai/dsh-tool-bash": "workspace:^",
     "@deepseek-ai/dsh-tool-subagent": "workspace:^",

+ 4 - 7
packages/subagent/subagent-spawn/src/index.ts

@@ -5,9 +5,9 @@
  * context). The cheapest transport, reusing the agent factory's quiescent
  * teardown.
  *
- * The fork sibling (`@deepseek-ai/dsh-subagent-fork`) shares this package's run
- * driver ({@link startInProcessRun}) and differs ONLY in seeding the child with
- * a prefix of the parent's log.
+ * The run mechanics live in `@deepseek-ai/dsh-subagent-inprocess`
+ * ({@link startInProcessRun}); this backend just passes NO seed (a fresh
+ * child). The fork backend is an independent peer over the same driver.
  *
  * Plugin export shape: named `name`/`inject`/`Config`/`apply`, NO default.
  *
@@ -17,10 +17,7 @@
 import type { Context } from 'cordis'
 import z from 'schemastery'
 import type { SubagentCapabilities, SubagentProvider, SubagentStartRequest } from '@deepseek-ai/dsh-subagent'
-import { startInProcessRun } from './in-process.ts'
-
-export { startInProcessRun, depthOf, SubagentDepthError } from './in-process.ts'
-export type { InProcessRunOptions } from './in-process.ts'
+import { startInProcessRun } from '@deepseek-ai/dsh-subagent-inprocess'
 
 export const name = 'subagent-spawn'
 export const inject = ['subagents', 'agents']

+ 1 - 1
packages/subagent/subagent-spawn/tests/subagent-spawn.spec.ts

@@ -12,7 +12,7 @@ import * as Invariants from '@deepseek-ai/dsh-invariants'
 import SubagentService from '@deepseek-ai/dsh-subagent'
 import { MockAdapter, maxTokensResponse, textResponse } from '../../../core/agent-loop/tests/mock-adapter.ts'
 import * as spawn from '../src/index.ts'
-import { depthOf, SubagentDepthError } from '../src/in-process.ts'
+import { depthOf, SubagentDepthError } from '@deepseek-ai/dsh-subagent-inprocess'
 
 type Script = ConstructorParameters<typeof MockAdapter>[0]
 

+ 3 - 9
packages/subagent/subagent-spawn/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"
@@ -18,16 +18,10 @@
       "path": "../../../vendor/schemastery"
     },
     {
-      "path": "../../core/agent"
-    },
-    {
-      "path": "../../llm/llm"
-    },
-    {
-      "path": "../../core/session"
+      "path": "../subagent"
     },
     {
-      "path": "../subagent"
+      "path": "../subagent-inprocess"
     }
   ]
 }

+ 5 - 3
packages/subagent/subagent/package.json

@@ -5,17 +5,19 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/subagent/subagent/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 5 - 3
packages/subagent/tool-subagent/package.json

@@ -5,17 +5,19 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/subagent/tool-subagent/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 5 - 3
packages/support/invariants/package.json

@@ -5,17 +5,19 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/support/invariants/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 5 - 3
packages/support/llm-replay/package.json

@@ -5,17 +5,19 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/support/llm-replay/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 5 - 3
packages/support/subagent-mock/package.json

@@ -5,17 +5,19 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/support/subagent-mock/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 5 - 3
packages/support/ui-stdio/package.json

@@ -5,17 +5,19 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/support/ui-stdio/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 7 - 4
packages/ui/acp-agent/package.json

@@ -5,24 +5,27 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "bin": {
     "dsh-acp-agent": "lib/bin.js"
   },
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./bin": {
-      "types": "./lib/bin.d.ts",
+      "types": "./lib/types/bin.d.ts",
       "default": "./lib/bin.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/bin.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/ui/acp-agent/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 4 - 3
packages/ui/acp-agent/tsdown.config.ts

@@ -3,11 +3,12 @@ import { defineConfig } from 'tsdown'
 /**
  * acp-agent ships TWO entries: the plugin (`index`) and the CLI `bin` (`bin`),
  * the latter referenced by package.json `bin`/`exports["./bin"]`. The root
- * tsdown builds only `src/index.ts`, so this override adds `bin.ts`.
- * Declarations come from `tsc -b` (dts: false), matching every package.
+ * tsdown builds only `lib/types/index.js`, so this override adds
+ * `lib/types/bin.js`. Declarations come from `tsc -b` (dts: false),
+ * matching every package.
  */
 export default defineConfig({
-  entry: ['src/index.ts', 'src/bin.ts'],
+  entry: ['lib/types/index.js', 'lib/types/bin.js'],
   outDir: 'lib',
   format: ['esm'],
   platform: 'node',

+ 5 - 3
packages/ui/acp/package.json

@@ -5,17 +5,19 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/ui/acp/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 7 - 4
packages/ui/stdio-agent/package.json

@@ -5,24 +5,27 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "bin": {
     "dsh-stdio-agent": "lib/bin.js"
   },
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./bin": {
-      "types": "./lib/bin.d.ts",
+      "types": "./lib/types/bin.d.ts",
       "default": "./lib/bin.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/bin.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/ui/stdio-agent/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 4 - 3
packages/ui/stdio-agent/tsdown.config.ts

@@ -3,11 +3,12 @@ import { defineConfig } from 'tsdown'
 /**
  * stdio-agent ships TWO entries: the plugin (`index`) and the CLI `bin`
  * (`bin`), the latter referenced by package.json `bin`/`exports["./bin"]`.
- * The root tsdown builds only `src/index.ts`, so this override adds `bin.ts`.
- * Declarations come from `tsc -b` (dts: false), matching every package.
+ * The root tsdown builds only `lib/types/index.js`, so this override adds
+ * `lib/types/bin.js`. Declarations come from `tsc -b` (dts: false),
+ * matching every package.
  */
 export default defineConfig({
-  entry: ['src/index.ts', 'src/bin.ts'],
+  entry: ['lib/types/index.js', 'lib/types/bin.js'],
   outDir: 'lib',
   format: ['esm'],
   platform: 'node',

+ 5 - 3
packages/util/brand/package.json

@@ -5,17 +5,19 @@
   "private": true,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "src"
   ],
   "license": "BSD-3-Clause",

+ 1 - 1
packages/util/brand/tsconfig.json

@@ -2,7 +2,7 @@
   "extends": "../../../tsconfig.base.json",
   "compilerOptions": {
     "rootDir": "src",
-    "outDir": "lib"
+    "outDir": "lib/types"
   },
   "include": [
     "src"

+ 36 - 0
pnpm-lock.yaml

@@ -385,6 +385,9 @@ importers:
       '@deepseek-ai/dsh-subagent':
         specifier: workspace:^
         version: link:../subagent
+      '@deepseek-ai/dsh-subagent-inprocess':
+        specifier: workspace:^
+        version: link:../subagent-inprocess
       '@deepseek-ai/dsh-subagent-spawn':
         specifier: workspace:^
         version: link:../subagent-spawn
@@ -398,6 +401,36 @@ importers:
         specifier: ^4.0.0-rc.6
         version: 4.0.0-rc.6(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.4)
 
+  packages/subagent/subagent-inprocess:
+    devDependencies:
+      '@deepseek-ai/dsh-agent':
+        specifier: workspace:^
+        version: link:../../core/agent
+      '@deepseek-ai/dsh-agent-loop':
+        specifier: workspace:^
+        version: link:../../core/agent-loop
+      '@deepseek-ai/dsh-invariants':
+        specifier: workspace:^
+        version: link:../../support/invariants
+      '@deepseek-ai/dsh-llm':
+        specifier: workspace:^
+        version: link:../../llm/llm
+      '@deepseek-ai/dsh-session':
+        specifier: workspace:^
+        version: link:../../core/session
+      '@deepseek-ai/dsh-subagent':
+        specifier: workspace:^
+        version: link:../subagent
+      '@deepseek-ai/dsh-system-prompt':
+        specifier: workspace:^
+        version: link:../../core/system-prompt
+      '@deepseek-ai/dsh-tools':
+        specifier: workspace:^
+        version: link:../../core/tools
+      cordis:
+        specifier: ^4.0.0-rc.6
+        version: 4.0.0-rc.6(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.4)
+
   packages/subagent/subagent-spawn:
     dependencies:
       schemastery:
@@ -431,6 +464,9 @@ importers:
       '@deepseek-ai/dsh-subagent':
         specifier: workspace:^
         version: link:../subagent
+      '@deepseek-ai/dsh-subagent-inprocess':
+        specifier: workspace:^
+        version: link:../subagent-inprocess
       '@deepseek-ai/dsh-system-prompt':
         specifier: workspace:^
         version: link:../../core/system-prompt

+ 51 - 0
scripts/check-workspace-constraints.ts

@@ -33,6 +33,18 @@ interface PackageManifest {
   version?: string
   private?: boolean
   type?: string
+  main?: string
+  types?: string
+  bin?: string | Record<string, string>
+  exports?: Record<
+    string,
+    | {
+      types?: string
+      default?: string
+    }
+    | undefined
+  >
+  files?: string[]
   peerDependencies?: Record<string, string>
   devDependencies?: Record<string, string>
 }
@@ -73,6 +85,29 @@ function workspaceManifests(): WorkspaceManifest[] {
   return manifests
 }
 
+const dshPackageFiles = [
+  'lib/index.js',
+  'lib/types/**/*.d.ts',
+  'lib/types/**/*.d.ts.map',
+  'src',
+] as const
+
+const dshBinPackageFiles = [
+  'lib/index.js',
+  'lib/bin.js',
+  'lib/types/**/*.d.ts',
+  'lib/types/**/*.d.ts.map',
+  'src',
+] as const
+
+function sameStringList(actual: readonly string[] | undefined, expected: readonly string[]): boolean {
+  return !!actual && actual.length === expected.length && actual.every((value, index) => value === expected[index])
+}
+
+function expectedDshPackageFiles(manifest: PackageManifest): readonly string[] {
+  return manifest.bin ? dshBinPackageFiles : dshPackageFiles
+}
+
 function checkWorkspace({ dir, manifest }: WorkspaceManifest): string[] {
   const errors: string[] = []
   const label = manifest.name ?? dir
@@ -100,6 +135,22 @@ function checkWorkspace({ dir, manifest }: WorkspaceManifest): string[] {
     if (manifest.type !== 'module') {
       errors.push(`${label}: package.json must set "type": "module"`)
     }
+    if (manifest.main !== 'lib/index.js') {
+      errors.push(`${label}: package.json must set "main": "lib/index.js"`)
+    }
+    if (manifest.types !== 'lib/types/index.d.ts') {
+      errors.push(`${label}: package.json must set "types": "lib/types/index.d.ts"`)
+    }
+    if (manifest.exports?.['.']?.types !== './lib/types/index.d.ts') {
+      errors.push(`${label}: package.json exports["."].types must be "./lib/types/index.d.ts"`)
+    }
+    if (manifest.exports?.['.']?.default !== './lib/index.js') {
+      errors.push(`${label}: package.json exports["."].default must be "./lib/index.js"`)
+    }
+    const expectedFiles = expectedDshPackageFiles(manifest)
+    if (!sameStringList(manifest.files, expectedFiles)) {
+      errors.push(`${label}: package.json files must be ${JSON.stringify(expectedFiles)}`)
+    }
   }
 
   return errors.map(error => `${relative(root, join(root, dir, 'package.json'))}: ${error}`)

+ 25 - 34
scripts/doc-typecheck.ts

@@ -3,12 +3,12 @@
  * Markdown so documentation can't drift from the API it documents.
  *
  * Every ```ts block in README.md, docs/** and packages/* /README.md is
- * extracted to a temp file and compiled with `tsc --noEmit` against the
- * workspace sources (resolved through the same `paths` map vitest uses, so no
- * build is required first). A block that is a deliberate sketch rather than
- * compilable code opts out with an explicit ` ```ts ignore-check ` info string
- * — the opt-out is visible in the source, and this script reports the ratio so
- * the escape hatch can't quietly become the norm. A third info string,
+ * extracted to a temp typecheck project and compiled against the workspace
+ * sources through the same project-reference boundaries used by repo
+ * typecheck. A block that is a deliberate sketch rather than compilable code
+ * opts out with an explicit ` ```ts ignore-check ` info string — the opt-out
+ * is visible in the source, and this script reports the ratio so the escape
+ * hatch can't quietly become the norm. A third info string,
  * doc-typecheck.ts recognizes two more fence variants and skips both (each is a
  * separately-checked category, not an unchecked sketch, so neither counts in the
  * opt-out ratio): ` ```ts type-equiv ` is a verbatim source-type paste that
@@ -88,16 +88,9 @@ function extractBlocks(absPath: string): Block[] {
   return blocks
 }
 
-/**
- * Read the workspace `paths` map from tsconfig.typecheck.json (JSONC). This map
- * resolves vendored packages to their BUILT declarations (`lib`) and harness
- * packages to source (`src`) — the same resolution `pnpm run lint`/`typecheck` use.
- * Resolving vendor to `lib` (not `src`) is essential: otherwise tsc type-checks
- * raw vendor source and floods the run with unrelated errors. Requires the
- * vendor `lib/` to exist (a fresh clone runs `pnpm run build` first; CI does too).
- */
-function workspacePaths(): Record<string, string[]> {
-  const file = join(root, 'tsconfig.typecheck.json')
+/** Reuse the repo typecheck graph references from a temp project one directory below root. */
+function workspaceReferences(): { path: string }[] {
+  const file = join(root, 'tsconfig.json')
   // Parse with TypeScript's own JSONC reader, not a hand-rolled comment strip:
   // a regex strip mistakes the `/*/` in a wildcard path candidate
   // (`./packages/core/*/src`) for a block comment and corrupts the map.
@@ -106,27 +99,24 @@ function workspacePaths(): Record<string, string[]> {
     throw new Error(`doc-typecheck: cannot read ${file}: ${ts.flattenDiagnosticMessageText(result.error.messageText, '\n')}`)
   }
   // `config` is typed `any` by the TS API; narrow it to the one field we read.
-  const config = result.config as { compilerOptions: { paths: Record<string, string[]> } }
-  return config.compilerOptions.paths
+  const { references } = result.config as { compilerOptions: { paths: Record<string, string[]> }; references: { path: string }[] }
+  return references.map(({ path }) => {
+    const relativeToTemp = path.startsWith('./') ? `../${path.slice(2)}` : `../${path}`
+    return { path: relativeToTemp }
+  })
 }
 
-/** The standalone tsconfig for the temp project (copies base resolution, no
- * composite/declaration settings that would fight `--noEmit`). */
+/** The standalone tsconfig for the temp typecheck project. */
 function tempTsconfig(): string {
   return JSON.stringify({
+    extends: '../tsconfig.json',
     compilerOptions: {
-      target: 'es2024',
-      module: 'esnext',
-      moduleResolution: 'bundler',
-      allowImportingTsExtensions: true,
-      strict: true,
-      noEmit: true,
-      skipLibCheck: true,
-      types: ['node'],
-      baseUrl: root,
-      ignoreDeprecations: '6.0',
-      paths: workspacePaths(),
+      noUnusedLocals: false,
+      noUnusedParameters: false,
+      tsBuildInfoFile: './tsconfig.tsbuildinfo',
     },
+    include: ['block-*.ts'],
+    references: workspaceReferences(),
   })
 }
 
@@ -164,11 +154,12 @@ try {
   })
 
   try {
-    execFileSync('node_modules/.bin/tsc', ['-p', join(tmp, 'tsconfig.json')], { cwd: root, stdio: 'pipe' })
+    execFileSync('node_modules/.bin/tsc', ['-b', join(tmp, 'tsconfig.json')], { cwd: root, stdio: 'pipe' })
   } catch (error: unknown) {
-    const out = (error as { stdout?: Buffer }).stdout?.toString() ?? ''
+    const failed = error as { stdout?: Buffer; stderr?: Buffer }
+    const out = `${failed.stdout?.toString() ?? ''}${failed.stderr?.toString() ?? ''}`
     // Rewrite "block-N.ts(line,col)" to the real "file:fenceLine" for triage.
-    const remapped = out.replace(/block-(\d+)\.ts\((\d+),(\d+)\)/g, (_m, idx: string, ln: string, col: string) => {
+    const remapped = out.replace(/(?:[^\s:()]*[/\\])?block-(\d+)\.ts\((\d+),(\d+)\)/g, (_m, idx: string, ln: string, col: string) => {
       const block = fileForBlock.get(`block-${idx}.ts`)
       if (!block) return `block-${idx}.ts(${ln},${col})`
       return `${block.file} (block at line ${block.line}, +${ln}:${col})`

+ 160 - 0
scripts/verify-node-next-types.ts

@@ -0,0 +1,160 @@
+/**
+ * Verify that built package declarations are consumable by a standard external
+ * TypeScript ESM project using NodeNext resolution.
+ *
+ * Run after `pnpm run build` has emitted declaration files under package
+ * `lib/types` directories.
+ */
+
+import { execFileSync } from 'node:child_process'
+import { existsSync, globSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
+import { dirname, resolve } from 'node:path'
+
+const root = resolve(import.meta.dirname, '..')
+
+interface ExportTarget {
+  types?: string
+}
+
+interface PackageManifest {
+  name?: string
+  types?: string
+  exports?: Record<string, ExportTarget | string | null>
+}
+
+interface WorkspacePackage {
+  dir: string
+  name: string
+  manifest: PackageManifest
+}
+
+function readPackage(path: string): WorkspacePackage | null {
+  const manifest = JSON.parse(readFileSync(path, 'utf8')) as PackageManifest
+  if (!manifest.name) return null
+  return { dir: dirname(path), name: manifest.name, manifest }
+}
+
+function workspacePackages(): WorkspacePackage[] {
+  return [
+    ...globSync('vendor/*/package.json', { cwd: root }),
+    ...globSync('packages/*/*/package.json', { cwd: root }),
+  ]
+    .map(path => readPackage(resolve(root, path)))
+    .filter(pkg => pkg !== null)
+    .sort((a, b) => a.name.localeCompare(b.name))
+}
+
+const declarationSpecifierPattern = /(?:from\s*|import\s*\(\s*|import\s+|declare\s+module\s*)["'](\.{0,2}(?:\/[^"']*)?)["']/g
+const hasExtension = /\.[^/.]+$/
+
+function relativeSpecifiersMissingExtensions(): string[] {
+  const errors: string[] = []
+  const files = [
+    ...globSync('vendor/*/lib/types/**/*.d.ts', { cwd: root }),
+    ...globSync('packages/*/*/lib/types/**/*.d.ts', { cwd: root }),
+  ].sort()
+
+  for (const file of files) {
+    const text = readFileSync(resolve(root, file), 'utf8')
+    for (const match of text.matchAll(declarationSpecifierPattern)) {
+      const specifier = match[1]
+      if (!specifier) continue
+      const isRelative = specifier === '.' || specifier.startsWith('./') || specifier.startsWith('../')
+      if (isRelative && !hasExtension.test(specifier)) errors.push(`${file}: ${specifier}`)
+    }
+  }
+
+  return errors
+}
+
+function publicSpecifiers(pkg: WorkspacePackage): string[] {
+  const specifiers = new Set<string>()
+  if (pkg.manifest.types) specifiers.add(pkg.name)
+
+  for (const [key, target] of Object.entries(pkg.manifest.exports ?? {})) {
+    if (key.includes('*') || key === './package.json') continue
+    if (typeof target !== 'object' || target === null || !target.types) continue
+    specifiers.add(key === '.' ? pkg.name : `${pkg.name}/${key.slice(2)}`)
+  }
+
+  return [...specifiers].sort()
+}
+
+function linkPackage(pkg: WorkspacePackage, nodeModules: string): void {
+  const parts = pkg.name.split('/')
+  const link = resolve(nodeModules, ...parts)
+  mkdirSync(dirname(link), { recursive: true })
+  symlinkSync(pkg.dir, link, 'dir')
+}
+
+const packages = workspacePackages()
+const badSpecifiers = relativeSpecifiersMissingExtensions()
+if (badSpecifiers.length > 0) {
+  console.error('verify-node-next-types: declaration files still contain relative specifiers without file extensions.')
+  console.error(badSpecifiers.join('\n'))
+  process.exit(1)
+}
+
+const missingOutputs = packages
+  .filter(pkg => pkg.manifest.types && !existsSync(resolve(pkg.dir, pkg.manifest.types)))
+  .map(pkg => `${pkg.name}: missing ${pkg.manifest.types}`)
+
+if (missingOutputs.length > 0) {
+  console.error('verify-node-next-types: build outputs are missing; run `pnpm run build` first.')
+  console.error(missingOutputs.join('\n'))
+  process.exit(1)
+}
+
+const tmp = mkdtempSync(resolve(root, '.node-next-types-'))
+let failed = false
+
+try {
+  const nodeModules = resolve(tmp, 'node_modules')
+  mkdirSync(nodeModules, { recursive: true })
+  for (const pkg of packages) linkPackage(pkg, nodeModules)
+
+  const rootTypes = resolve(root, 'node_modules/@types/node')
+  if (existsSync(rootTypes)) {
+    const typesDir = resolve(nodeModules, '@types')
+    mkdirSync(typesDir, { recursive: true })
+    symlinkSync(rootTypes, resolve(typesDir, 'node'), 'dir')
+  }
+
+  writeFileSync(resolve(tmp, 'package.json'), `${JSON.stringify({ type: 'module', private: true }, null, 2)}\n`)
+  writeFileSync(resolve(tmp, 'tsconfig.json'), `${JSON.stringify({
+    compilerOptions: {
+      target: 'es2024',
+      module: 'NodeNext',
+      moduleResolution: 'NodeNext',
+      strict: true,
+      // Third-party SDK declarations can have their own lib-check noise under a
+      // symlinked temp install. The explicit scan above owns our regression:
+      // relative specifiers without file extensions in built declarations.
+      skipLibCheck: true,
+      preserveSymlinks: true,
+      noEmit: true,
+      types: ['node'],
+    },
+    include: ['index.ts'],
+  }, null, 2)}\n`)
+
+  const imports = packages.flatMap(publicSpecifiers)
+    .map((specifier, index) => `import * as mod${index} from ${JSON.stringify(specifier)};\nvoid mod${index};`)
+    .join('\n')
+  writeFileSync(resolve(tmp, 'index.ts'), `${imports}\n`)
+
+  execFileSync(resolve(root, 'node_modules/.bin/tsc'), ['-p', resolve(tmp, 'tsconfig.json'), '--pretty', 'false'], {
+    cwd: root,
+    stdio: 'pipe',
+  })
+  console.log(`verify-node-next-types: ${packages.length} workspace package declaration surface(s) compile under NodeNext.`)
+} catch (error: unknown) {
+  failed = true
+  const output = error as { stdout?: Buffer; stderr?: Buffer }
+  console.error('verify-node-next-types: NodeNext consumer typecheck failed.\n')
+  console.error(`${output.stdout?.toString() ?? ''}${output.stderr?.toString() ?? ''}`)
+} finally {
+  rmSync(tmp, { recursive: true, force: true })
+}
+
+if (failed) process.exit(1)

+ 6 - 6
tsconfig.base.json

@@ -4,12 +4,14 @@
     "module": "esnext",
     "moduleResolution": "bundler",
     "declaration": true,
-    "emitDeclarationOnly": true,
+    "sourceMap": true,
+    "declarationMap": true,
     "composite": true,
     "incremental": true,
     "skipLibCheck": true,
     "esModuleInterop": true,
     "allowImportingTsExtensions": true,
+    "rewriteRelativeImportExtensions": true,
     "verbatimModuleSyntax": false,
     "strict": true,
     "noUncheckedIndexedAccess": true,
@@ -19,11 +21,9 @@
     "noUnusedLocals": true,
     "noUnusedParameters": true,
     "types": ["node"],
-    // Source-level resolution for the build graph: without this, a fresh
-    // checkout's first `tsc -b` resolves sibling vendor plugins through their
-    // package.json types (vendor/*/lib/*.d.ts) which don't exist yet — TS2307
-    // until a second run. Derived configs that want lib resolution
-    // (tsconfig.typecheck.json) override this map wholesale.
+    // Source-level resolution for every repo-local graph. Project references,
+    // not declaration path aliases, keep each package/vendor source compiled
+    // under its own tsconfig boundary.
     "paths": {
       "cordis": ["./vendor/cordis/src"],
       "cosmokit": ["./vendor/cosmokit/src"],

+ 1 - 0
tsconfig.build.json

@@ -35,6 +35,7 @@
     { "path": "./packages/subagent/subagent" },
     { "path": "./packages/support/subagent-mock" },
     { "path": "./packages/subagent/tool-subagent" },
+    { "path": "./packages/subagent/subagent-inprocess" },
     { "path": "./packages/subagent/subagent-spawn" },
     { "path": "./packages/subagent/subagent-fork" },
     { "path": "./packages/subagent/subagent-acp" }

+ 51 - 1
tsconfig.json

@@ -1,4 +1,54 @@
 {
   "extends": "./tsconfig.base.json",
-  "files": []
+  "compilerOptions": {
+    "noEmit": true,
+    "rewriteRelativeImportExtensions": false
+  },
+  "include": [
+    "examples/*/src/**/*.ts",
+    "examples/*/start.ts",
+    "examples/*/tests/**/*.ts",
+    "packages/*/*/tests/**/*.ts",
+    "scripts/**/*.ts"
+  ],
+  "references": [
+    { "path": "./vendor/cosmokit" },
+    { "path": "./vendor/schemastery" },
+    { "path": "./vendor/cordis" },
+    { "path": "./vendor/loader" },
+    { "path": "./vendor/include" },
+    { "path": "./vendor/group" },
+    { "path": "./vendor/timer" },
+    { "path": "./vendor/hmr" },
+    { "path": "./vendor/logger-console" },
+    { "path": "./packages/util/brand" },
+    { "path": "./packages/llm/llm" },
+    { "path": "./packages/core/session" },
+    { "path": "./packages/session-persistence/session-persistence" },
+    { "path": "./packages/session-persistence/session-persistence-jsonl" },
+    { "path": "./packages/session-persistence/session-persistence-sqlite" },
+    { "path": "./packages/core/system-prompt" },
+    { "path": "./packages/core/agent" },
+    { "path": "./packages/core/tools" },
+    { "path": "./packages/core/agent-loop" },
+    { "path": "./packages/core/agent-core" },
+    { "path": "./packages/bash/bash" },
+    { "path": "./packages/llm/llm-deepseek" },
+    { "path": "./packages/llm/llm-pi-ai" },
+    { "path": "./packages/bash/bash-local" },
+    { "path": "./packages/bash/tool-bash" },
+    { "path": "./packages/support/invariants" },
+    { "path": "./packages/ui/acp" },
+    { "path": "./packages/ui/acp-agent" },
+    { "path": "./packages/ui/stdio-agent" },
+    { "path": "./packages/support/ui-stdio" },
+    { "path": "./packages/support/llm-replay" },
+    { "path": "./packages/subagent/subagent" },
+    { "path": "./packages/support/subagent-mock" },
+    { "path": "./packages/subagent/tool-subagent" },
+    { "path": "./packages/subagent/subagent-inprocess" },
+    { "path": "./packages/subagent/subagent-spawn" },
+    { "path": "./packages/subagent/subagent-fork" },
+    { "path": "./packages/subagent/subagent-acp" }
+  ]
 }

+ 0 - 10
tsconfig.test.json

@@ -1,10 +0,0 @@
-{
-  "extends": "./tsconfig.json",
-  "compilerOptions": {
-    "noEmit": true,
-    "emitDeclarationOnly": false,
-    "composite": false,
-    "types": ["node"]
-  },
-  "include": ["vendor/*/src", "packages/*/*/src", "packages/*/*/tests", "examples"]
-}

+ 0 - 32
tsconfig.typecheck.json

@@ -1,32 +0,0 @@
-{
-  "extends": "./tsconfig.base.json",
-  "compilerOptions": {
-    "noEmit": true,
-    "emitDeclarationOnly": false,
-    "composite": false,
-    "incremental": false,
-    "types": ["node"],
-    "paths": {
-      "cordis": ["./vendor/cordis/lib"],
-      "cosmokit": ["./vendor/cosmokit/lib"],
-      "schemastery": ["./vendor/schemastery/lib"],
-      "@cordisjs/plugin-loader": ["./vendor/loader/lib"],
-      "@cordisjs/plugin-include": ["./vendor/include/lib"],
-      "@cordisjs/plugin-group": ["./vendor/group/lib"],
-      "@cordisjs/plugin-timer": ["./vendor/timer/lib"],
-      "@cordisjs/plugin-hmr": ["./vendor/hmr/lib"],
-      "@cordisjs/plugin-logger-console": ["./vendor/logger-console/lib/shared"],
-      "@deepseek-ai/dsh-*": [
-        "./packages/core/*/src",
-        "./packages/llm/*/src",
-        "./packages/bash/*/src",
-        "./packages/subagent/*/src",
-        "./packages/session-persistence/*/src",
-        "./packages/ui/*/src",
-        "./packages/util/*/src",
-        "./packages/support/*/src"
-      ]
-    }
-  },
-  "include": ["packages/*/*/src", "packages/*/*/tests", "examples", "scripts"]
-}

+ 4 - 4
tsdown.config.ts

@@ -2,9 +2,9 @@ import { defineConfig } from 'tsdown'
 
 /**
  * JS bundling for all workspace packages (vendor and the packages hierarchy).
- * Declarations are NOT produced here — `tsc -b tsconfig.build.json` owns
- * .d.ts output (composite project references); hence `dts: false` and
- * `clean: false` (lib/ already holds tsc's declarations).
+ * TypeScript source is compiled first by `tsc -b tsconfig.build.json`; tsdown
+ * reads only the emitted JS under lib/types and writes lib/index.* runtime
+ * bundles. Declarations are NOT produced here, hence `dts: false`.
  *
  * Per-package shape overrides live in `<package>/tsdown.config.ts`
  * (schemastery: dual ESM+CJS; logger-console: extra browser entry).
@@ -14,7 +14,7 @@ export default defineConfig({
   // package.json), but only vendor and the packages hierarchy are pnpm
   // workspaces.
   workspace: ['vendor/*', 'packages/*/*'],
-  entry: ['src/index.ts'],
+  entry: ['lib/types/index.js'],
   outDir: 'lib',
   format: ['esm'],
   platform: 'node',

+ 4 - 3
vendor/README.md

@@ -31,9 +31,10 @@ Intentionally **not** vendored (verified unused by this set): `reggol`, `@cordis
 Keep this log exhaustive — every divergence from upstream must be listed.
 
 1. **`hmr/src/index.ts`**: removed the `./locales/en-US.yml` / `./locales/zh-CN.yml` imports, the `.i18n({...})` call on the `Config` schema, and the `src/locales/` directory. Rationale: those imports require a runtime YAML loader hook (`@cordisjs/unyaml`) that we do not vendor; the i18n texts only localize config descriptions.
-2. **All `package.json` files**: regenerated — added `private: true`, added `src` to `files` and a `./src/*` export where missing, removed upstream `devDependencies`/`scripts`/`repository` fields. Dependency and peer-dependency ranges preserved, except `hmr` declares `esbuild` as a direct dev dependency because its source imports the `BuildFailure` type and pnpm's strict workspace resolution requires the owner package to name that dependency.
-3. **All `tsconfig.json` files**: regenerated to extend the repo-root `tsconfig.base.json` and declare project references.
-4. **`schemastery/tsdown.config.ts` and `logger-console/tsdown.config.ts`**: ours, not upstream files — per-package build-shape overrides (dual ESM+CJS output; separate node/browser entries) for the repo-root tsdown build. Like the regenerated tsconfigs, they are not part of the upstream sync surface.
+2. **All `package.json` files**: regenerated — added `private: true`, added precise `files` entries for bundled runtime files and `lib/types/**/*.d.ts` / `.d.ts.map`, preserved `src` in `files` only for packages whose previous file list already shipped it, added a `./src/*` export where missing, pointed declaration metadata at `lib/types`, and removed upstream `devDependencies`/`scripts`/`repository` fields. Dependency and peer-dependency ranges preserved, except `hmr` declares `esbuild` as a direct dev dependency because its source imports the `BuildFailure` type and pnpm's strict workspace resolution requires the owner package to name that dependency.
+3. **All `tsconfig.json` files**: regenerated to extend the repo-root `tsconfig.base.json`, emit TypeScript intermediates to `lib/types`, and declare project references.
+4. **Vendored TypeScript source internal specifiers**: changed local relative imports/exports from upstream's specifier shape to explicit `.ts` specifiers so TypeScript rewrites emitted JS to `.js` while declarations keep explicit, NodeNext-safe `.ts` specifiers. This includes `loader/src/config/isolate.ts` using `declare module './entry.ts'`.
+5. **`schemastery/tsdown.config.ts` and `logger-console/tsdown.config.ts`**: ours, not upstream files — per-package build-shape overrides (dual ESM+CJS output; separate node/browser entries) for the repo-root tsdown build. They read the JS emitted under `lib/types` and then write the publish runtime entries under `lib/`. Like the regenerated tsconfigs, they are not part of the upstream sync surface.
 
 ## Sync procedure
 

+ 5 - 3
vendor/cordis/package.json

@@ -6,18 +6,20 @@
   "sideEffects": false,
   "type": "module",
   "main": "lib/index.js",
-  "types": "lib/index.d.ts",
+  "types": "lib/types/index.d.ts",
   "bin": "bin.js",
   "exports": {
     ".": {
-      "types": "./lib/index.d.ts",
+      "types": "./lib/types/index.d.ts",
       "default": "./lib/index.js"
     },
     "./src/*": "./src/*",
     "./package.json": "./package.json"
   },
   "files": [
-    "lib",
+    "lib/index.js",
+    "lib/types/**/*.d.ts",
+    "lib/types/**/*.d.ts.map",
     "bin.js"
   ],
   "author": "Shigma <shigma10826@gmail.com>",

+ 6 - 6
vendor/cordis/src/context.ts

@@ -1,10 +1,10 @@
 import { Dict } from 'cosmokit'
-import { EventsService } from './events'
-import { LoggerService } from './logger'
-import { ReflectService } from './reflect'
-import { InjectKey, RegistryService } from './registry'
-import { getTraceable, symbols } from './utils'
-import { Fiber } from './fiber'
+import { EventsService } from './events.ts'
+import { LoggerService } from './logger.ts'
+import { ReflectService } from './reflect.ts'
+import { InjectKey, RegistryService } from './registry.ts'
+import { getTraceable, symbols } from './utils.ts'
+import { Fiber } from './fiber.ts'
 
 /**
  * Public shape of a Cordis context.

+ 4 - 4
vendor/cordis/src/events.ts

@@ -1,7 +1,7 @@
 import { defineProperty, Promisify } from 'cosmokit'
-import { Context } from './context'
-import { Fiber, FiberState } from './fiber'
-import { DisposableList, symbols } from './utils'
+import { Context } from './context.ts'
+import { Fiber, FiberState } from './fiber.ts'
+import { DisposableList, symbols } from './utils.ts'
 
 /** Return whether an event result should stop a bail-style dispatch. */
 export function isBailed(value: any) {
@@ -25,7 +25,7 @@ export type ThisType<F> = F extends (this: infer T, ...args: any) => any ? T : n
  */
 export type DispatchMode = 'emit' | 'parallel' | 'serial' | 'bail' | 'waterfall'
 
-declare module './context' {
+declare module './context.ts' {
   export interface Context {
     /* eslint-disable max-len */
     parallel<K extends keyof Events>(name: K, ...args: Parameters<Events[K]>): Promise<void>

+ 5 - 5
vendor/cordis/src/fiber.ts

@@ -1,11 +1,11 @@
 import { Awaitable, defineProperty, Dict, isNullable } from 'cosmokit'
-import { Context } from './context'
-import { Plugin } from './registry'
-import { buildOuterStack, composeError, DisposableList, getTraceable, isConstructor, isObject, symbols } from './utils'
-import { Impl } from './reflect'
+import { Context } from './context.ts'
+import { Plugin } from './registry.ts'
+import { buildOuterStack, composeError, DisposableList, getTraceable, isConstructor, isObject, symbols } from './utils.ts'
+import { Impl } from './reflect.ts'
 import { StandardSchemaV1 } from '@standard-schema/spec'
 
-declare module './context' {
+declare module './context.ts' {
   export interface Context extends Pick<Fiber, 'effect'> {
     fiber: Fiber
   }

+ 7 - 7
vendor/cordis/src/index.ts

@@ -1,14 +1,14 @@
 /** Core context type and root context implementation. */
-export * from './context'
+export * from './context.ts'
 /** Event bus, dispatch modes, and event augmentation types. */
-export * from './events'
+export * from './events.ts'
 /** Plugin fiber lifecycle, effects, and config validation helpers. */
-export * from './fiber'
+export * from './fiber.ts'
 /** Logger facade, logger service, message, exporter, and formatting types. */
-export * from './logger'
+export * from './logger.ts'
 /** Plugin registry, dependency injection, and plugin entrypoint types. */
-export * from './registry'
+export * from './registry.ts'
 /** Base service class and service lifecycle symbols. */
-export * from './service'
+export * from './service.ts'
 /** Shared internal helpers used by context, services, and plugin fibers. */
-export * from './utils'
+export * from './utils.ts'

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio