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

# Conflicts:
#	docs/module-graph.md
#	packages/subagent/README.md
This commit is contained in:
Tianyi Cui
2026-06-22 14:58:03 +08:00
132 changed files with 1026 additions and 425 deletions

View File

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

View File

@@ -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 pnpm run test:snapshot:record # re-record fixtures + goldens against the real
# API (needs DEEPSEEK_API_KEY); accept-the-diff = re-record # API (needs DEEPSEEK_API_KEY); accept-the-diff = re-record
# (or `pnpm run test:snapshot -u` to refresh goldens only) # (or `pnpm run test:snapshot -u` to refresh goldens only)
pnpm run typecheck # tsc -b tsconfig.build.json (declarations) + tsc -p pnpm run typecheck # tsc -b tsconfig.json
# tsconfig.typecheck.json (tests/examples typecheck too)
pnpm run lint # eslint . pnpm run lint # eslint .
pnpm run lint:fix # eslint . --fix 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 knip # dead-code / unused-dependency check
pnpm run publint # package.json publish-correctness check (every packages/*/* package) 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, pnpm run doc-typecheck # typecheck every ```ts block in README.md, docs/**/*.md,
# packages/*/*.md + packages/*/*/*.md (doc/code drift gate) # packages/*/*.md + packages/*/*/*.md (doc/code drift gate)
pnpm run gen-cordis-catalog # regenerate docs/cordis-catalog/events-and-services.md 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 pnpm run verify-rfc-classification # assert every RFC lives in a valid
# {lifecycle}/{class}/ folder and docs/rfc/README.md lists it # {lifecycle}/{class}/ folder and docs/rfc/README.md lists it
# under the matching heading (closed class set + index completeness) # 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 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 pnpm run demo:echo # run examples/echo-agent (no API key; type "echo hi" to
# see a tool call) — the mock skeleton # 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. **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 ## Conventions
- **Package naming**: every npm package in this repo is `@deepseek-ai/dsh-<name>` (vendored packages keep their upstream names and are `private: true`). - **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. - **`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. - **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`). - **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`).

View File

@@ -5,29 +5,33 @@ The file-by-file checklist for a new `@deepseek-ai/dsh-<name>` package. (Verifie
## 1. Create the package ## 1. Create the package
``` ```
packages/<name>/ packages/<group>/<pkg>/
package.json # copy from packages/core/tools, adjust name/description/deps package.json # copy from packages/core/tools, adjust name/description/deps
tsconfig.json # extends ../../tsconfig.base.json, rootDir src, outDir lib, tsconfig.json # extends ../../../tsconfig.base.json, rootDir src,
# references: vendor/cosmokit, vendor/cordis (+ vendor/schemastery # outDir lib/types, references: ../../../vendor/cosmokit,
# if you use Config, + ../<dep> for each dsh dependency) # ../../../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) src/index.ts # service default export or plugin (name/inject/apply/Config)
tests/<x>.spec.ts tests/<x>.spec.ts
README.md # service API, events, extension points, design notes 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 ## 2. Register it in the root configs
| File | Change | | File | Change |
|---|---| |---|---|
| `tsconfig.base.json` | add `"@deepseek-ai/dsh-<name>": ["./packages/<name>/src"]` to `paths` | | `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.typecheck.json` | same entry (this file overrides the map wholesale) | | `tsconfig.json` | add `{ "path": "./packages/<group>/<pkg>" }` to `references` |
| `tsconfig.build.json` | add `{ "path": "./packages/<name>" }` to `references` | | `tsconfig.build.json` | add `{ "path": "./packages/<group>/<pkg>" }` to `references` |
| `scripts/publint-all.ts` | add `'packages/<name>'` to the array |
| `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`) | | `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 ## 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 install # registers the workspace
pnpm run constraints && pnpm run typecheck && pnpm run lint pnpm run constraints && pnpm run typecheck && pnpm run lint
pnpm run test:coverage # 100% per-file over src (types.ts exempt) 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. 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.

View File

@@ -12,13 +12,13 @@ vendor/<dir>/
README.md LICENSE # if upstream ships them 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 ```jsonc
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"rootDir": "src", "outDir": "lib", "rootDir": "src", "outDir": "lib/types",
"noUncheckedIndexedAccess": false, "exactOptionalPropertyTypes": false, "noUncheckedIndexedAccess": false, "exactOptionalPropertyTypes": false,
"noImplicitOverride": false, "noUnusedLocals": false, "noUnusedParameters": 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 ## 2. Register it in the root configs
| File | Change | | File | Change |
|---|---| |---|---|
| `tsconfig.base.json` | add `"<npm-name>": ["./vendor/<dir>/src"]` to `paths` | | `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) | | `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 | | `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) | | `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 ## 3. Mind the manifest guard
@@ -49,8 +51,8 @@ Covered automatically by globs — no edits needed: root `package.json` workspac
```sh ```sh
pnpm install # registers the workspace 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 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.

View File

@@ -31,7 +31,7 @@ Run typecheck once after a fresh clone:
pnpm run typecheck 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: 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 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 ## Environment variables
@@ -76,10 +76,10 @@ The GitHub workflow runs these gates on each pull request:
- `pnpm run test:coverage` - `pnpm run test:coverage`
- `pnpm run test:snapshot` - `pnpm run test:snapshot`
- `pnpm run build` - `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 - 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 ## Daily commands
@@ -89,7 +89,7 @@ Use these from the repo root:
pnpm run test # unit tests pnpm run test # unit tests
pnpm run test:coverage # unit tests with per-file coverage gates 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 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 # eslint .
pnpm run lint:fix # eslint . --fix pnpm run lint:fix # eslint . --fix
pnpm run doc-typecheck # compile checked TypeScript snippets in Markdown docs 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 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 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 verify-module-graph # fail if docs/module-graph.md is stale
pnpm run build # build declarations and JS bundles pnpm run build # emit lib/types intermediates, then bundle lib/index.* runtime files
pnpm run hygiene # knip, publint, and workspace constraints 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. 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.

View File

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

View File

@@ -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 | | [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 | | [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 | | [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 | | [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 | | [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 | | [Generated cordis events + services catalog](implemented/process/2026-06-20-generated-cordis-catalog.md) | 2026-06-20 |

View File

@@ -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: 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)`. - `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)). - `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)).

View File

@@ -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): 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. 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. **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.

View File

@@ -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: 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. - 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. - 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. - 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 ## Consequences

View File

@@ -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): 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). - 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). - 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). 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 ## 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.

View File

@@ -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.

View File

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

View File

@@ -57,17 +57,21 @@
Use the subagent tool to delegate a focused, self-contained subtask 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 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 Check the [exit code: N] marker on every command; investigate
failures before moving on. Verify your work by running the code or failures before moving on. Verify your work by running the code or
tests. Keep answers brief and factual. tests. Keep answers brief and factual.
# The subagent seam + an in-process spawn backend + the model-facing `subagent` # The subagent seam + BOTH in-process backends + two model-facing tools, as leaf
# tool, as leaf entries after the app (which provides ctx.agents/ctx.tools). The # entries after the app (which provides ctx.agents/ctx.tools). spawn (a fresh
# tool is bound to the `spawn` backend: a delegated task runs as a fresh child # child) and fork (a child seeded with the parent's completed-turn prefix) are
# agent on this same process. (fork is available too — load dsh-subagent-fork # independent backends over the shared dsh-subagent-inprocess driver. Exposing
# and a second dsh-tool-subagent bound to it with a distinct toolName.) # 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 - id: subagent
name: '@deepseek-ai/dsh-subagent' name: '@deepseek-ai/dsh-subagent'
@@ -76,7 +80,19 @@
config: config:
providerName: spawn providerName: spawn
- id: subagent-fork
name: '@deepseek-ai/dsh-subagent-fork'
config:
providerName: fork
- id: tool-subagent - id: tool-subagent
name: '@deepseek-ai/dsh-tool-subagent' name: '@deepseek-ai/dsh-tool-subagent'
config: config:
provider: spawn provider: spawn
toolName: subagent
- id: tool-subagent-fork
name: '@deepseek-ai/dsh-tool-subagent'
config:
provider: fork
toolName: subagent_fork

View File

@@ -13,7 +13,8 @@
], ],
"scripts": { "scripts": {
"build": "tsc -b tsconfig.build.json && tsdown", "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": "eslint .",
"lint:fix": "eslint . --fix", "lint:fix": "eslint . --fix",
"test": "vitest run", "test": "vitest run",
@@ -30,13 +31,14 @@
"verify-package-paths": "tsx scripts/verify-package-paths.ts", "verify-package-paths": "tsx scripts/verify-package-paths.ts",
"verify-rfc-classification": "tsx scripts/verify-rfc-classification.ts", "verify-rfc-classification": "tsx scripts/verify-rfc-classification.ts",
"verify-type-equiv": "tsx scripts/verify-type-equiv.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", "gen-cordis-catalog": "tsx scripts/gen-cordis-catalog.ts",
"verify-cordis-catalog": "tsx scripts/gen-cordis-catalog.ts --check", "verify-cordis-catalog": "tsx scripts/gen-cordis-catalog.ts --check",
"gen-module-graph": "tsx scripts/gen-module-graph.ts", "gen-module-graph": "tsx scripts/gen-module-graph.ts",
"verify-module-graph": "tsx scripts/gen-module-graph.ts --check", "verify-module-graph": "tsx scripts/gen-module-graph.ts --check",
"constraints": "tsx scripts/check-workspace-constraints.ts", "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", "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: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: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", "demo:acp": "node --import tsx packages/ui/acp-agent/src/bin.ts examples/acp-agent/cordis.yml",

View File

@@ -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`. - **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). - **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. - **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. - **Tests**: vitest, colocated under `packages/<group>/<pkg>/tests/*.spec.ts`. Every registry needs an HMR-safety test. Err on the side of more tests.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,11 +5,12 @@ The subagent seam: an agent delegating work to a child agent. Like the [bash](..
| Package | Role | ctx key | | Package | Role | ctx key |
|---|---|---| |---|---|---|
| `subagent/` | Abstract subagent seam: named-provider registry + vocabulary | `ctx.subagents` | | `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-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`) | | `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`) | | `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). 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).

View File

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

View File

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

View File

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

View File

@@ -2,8 +2,10 @@
* The in-process FORK subagent backend: registers a {@link SubagentProvider} on * 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 * `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 * 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 * conversation context instead of starting fresh. The run mechanics live in
* `@deepseek-ai/dsh-subagent-spawn`; the only difference is the seed. * `@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` * 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 * 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 { SessionEvent } from '@deepseek-ai/dsh-session'
import type { Agent } from '@deepseek-ai/dsh-agent' import type { Agent } from '@deepseek-ai/dsh-agent'
import type { SubagentCapabilities, SubagentProvider, SubagentStartRequest } from '@deepseek-ai/dsh-subagent' 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 name = 'subagent-fork'
export const inject = ['subagents', 'agents'] export const inject = ['subagents', 'agents']

View File

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

View File

@@ -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`.

View File

@@ -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"
}
}

View File

@@ -1,15 +1,16 @@
/** /**
* The shared in-process subagent run driver. A subagent backend that runs the * The shared in-process subagent run driver: run a child as a child
* child as a child {@link Agent} on the SAME cordis context (`ctx.agents`) * {@link Agent} on the SAME cordis context (`ctx.agents`) the cheapest
* the cheapest transport, reusing the agent factory's quiescent * transport, reusing the agent factory's quiescent {@link AgentHandle}
* {@link AgentHandle} teardown. Both in-process backends use this: * teardown. The concrete in-process backends are thin shells over this driver,
* `@deepseek-ai/dsh-subagent-spawn` (a fresh child) and * differing ONLY in the `seed` they pass (a fresh child vs. a child seeded with
* `@deepseek-ai/dsh-subagent-fork` (a child seeded with a prefix of the * a prefix of the parent's log); everything downstream drive the child, read
* parent's log) differ ONLY in the `seed` they pass everything downstream * its final output, map the stop reason, dispose is identical and lives here.
* (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' import { randomUUID } from 'node:crypto'

View File

@@ -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()
})
})

View File

@@ -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"
}
]
}

View File

@@ -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. 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 ## What it does
`start(request)` `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).
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.
## Capabilities ## Capabilities
@@ -23,7 +17,3 @@ It also exports the **shared in-process run driver** (`startInProcessRun`) that
| Key | Meaning | | Key | Meaning |
|---|---| |---|---|
| `providerName` | Registry name on `ctx.subagents` (default `spawn`). | | `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)`.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

36
pnpm-lock.yaml generated
View File

@@ -385,6 +385,9 @@ importers:
'@deepseek-ai/dsh-subagent': '@deepseek-ai/dsh-subagent':
specifier: workspace:^ specifier: workspace:^
version: link:../subagent version: link:../subagent
'@deepseek-ai/dsh-subagent-inprocess':
specifier: workspace:^
version: link:../subagent-inprocess
'@deepseek-ai/dsh-subagent-spawn': '@deepseek-ai/dsh-subagent-spawn':
specifier: workspace:^ specifier: workspace:^
version: link:../subagent-spawn version: link:../subagent-spawn
@@ -398,6 +401,36 @@ importers:
specifier: ^4.0.0-rc.6 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) 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: packages/subagent/subagent-spawn:
dependencies: dependencies:
schemastery: schemastery:
@@ -431,6 +464,9 @@ importers:
'@deepseek-ai/dsh-subagent': '@deepseek-ai/dsh-subagent':
specifier: workspace:^ specifier: workspace:^
version: link:../subagent version: link:../subagent
'@deepseek-ai/dsh-subagent-inprocess':
specifier: workspace:^
version: link:../subagent-inprocess
'@deepseek-ai/dsh-system-prompt': '@deepseek-ai/dsh-system-prompt':
specifier: workspace:^ specifier: workspace:^
version: link:../../core/system-prompt version: link:../../core/system-prompt

View File

@@ -33,6 +33,18 @@ interface PackageManifest {
version?: string version?: string
private?: boolean private?: boolean
type?: string 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> peerDependencies?: Record<string, string>
devDependencies?: Record<string, string> devDependencies?: Record<string, string>
} }
@@ -73,6 +85,29 @@ function workspaceManifests(): WorkspaceManifest[] {
return manifests 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[] { function checkWorkspace({ dir, manifest }: WorkspaceManifest): string[] {
const errors: string[] = [] const errors: string[] = []
const label = manifest.name ?? dir const label = manifest.name ?? dir
@@ -100,6 +135,22 @@ function checkWorkspace({ dir, manifest }: WorkspaceManifest): string[] {
if (manifest.type !== 'module') { if (manifest.type !== 'module') {
errors.push(`${label}: package.json must set "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}`) return errors.map(error => `${relative(root, join(root, dir, 'package.json'))}: ${error}`)

View File

@@ -3,12 +3,12 @@
* Markdown so documentation can't drift from the API it documents. * Markdown so documentation can't drift from the API it documents.
* *
* Every ```ts block in README.md, docs/** and packages/* /README.md is * Every ```ts block in README.md, docs/** and packages/* /README.md is
* extracted to a temp file and compiled with `tsc --noEmit` against the * extracted to a temp typecheck project and compiled against the workspace
* workspace sources (resolved through the same `paths` map vitest uses, so no * sources through the same project-reference boundaries used by repo
* build is required first). A block that is a deliberate sketch rather than * typecheck. A block that is a deliberate sketch rather than compilable code
* compilable code opts out with an explicit ` ```ts ignore-check ` info string * opts out with an explicit ` ```ts ignore-check ` info string — the opt-out
* — the opt-out is visible in the source, and this script reports the ratio so * is visible in the source, and this script reports the ratio so the escape
* the escape hatch can't quietly become the norm. A third info string, * 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 * 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 * 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 * opt-out ratio): ` ```ts type-equiv ` is a verbatim source-type paste that
@@ -88,16 +88,9 @@ function extractBlocks(absPath: string): Block[] {
return blocks return blocks
} }
/** /** Reuse the repo typecheck graph references from a temp project one directory below root. */
* Read the workspace `paths` map from tsconfig.typecheck.json (JSONC). This map function workspaceReferences(): { path: string }[] {
* resolves vendored packages to their BUILT declarations (`lib`) and harness const file = join(root, 'tsconfig.json')
* 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')
// Parse with TypeScript's own JSONC reader, not a hand-rolled comment strip: // Parse with TypeScript's own JSONC reader, not a hand-rolled comment strip:
// a regex strip mistakes the `/*/` in a wildcard path candidate // a regex strip mistakes the `/*/` in a wildcard path candidate
// (`./packages/core/*/src`) for a block comment and corrupts the map. // (`./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')}`) 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. // `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[]> } } const { references } = result.config as { compilerOptions: { paths: Record<string, string[]> }; references: { path: string }[] }
return config.compilerOptions.paths 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 /** The standalone tsconfig for the temp typecheck project. */
* composite/declaration settings that would fight `--noEmit`). */
function tempTsconfig(): string { function tempTsconfig(): string {
return JSON.stringify({ return JSON.stringify({
extends: '../tsconfig.json',
compilerOptions: { compilerOptions: {
target: 'es2024', noUnusedLocals: false,
module: 'esnext', noUnusedParameters: false,
moduleResolution: 'bundler', tsBuildInfoFile: './tsconfig.tsbuildinfo',
allowImportingTsExtensions: true,
strict: true,
noEmit: true,
skipLibCheck: true,
types: ['node'],
baseUrl: root,
ignoreDeprecations: '6.0',
paths: workspacePaths(),
}, },
include: ['block-*.ts'],
references: workspaceReferences(),
}) })
} }
@@ -164,11 +154,12 @@ try {
}) })
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) { } 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. // 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`) const block = fileForBlock.get(`block-${idx}.ts`)
if (!block) return `block-${idx}.ts(${ln},${col})` if (!block) return `block-${idx}.ts(${ln},${col})`
return `${block.file} (block at line ${block.line}, +${ln}:${col})` return `${block.file} (block at line ${block.line}, +${ln}:${col})`

View File

@@ -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)

View File

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

View File

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

View File

@@ -1,4 +1,54 @@
{ {
"extends": "./tsconfig.base.json", "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" }
]
} }

View File

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

View File

@@ -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"]
}

View File

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

7
vendor/README.md vendored
View File

@@ -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. 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. 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. 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` and declare project references. 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. **`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. 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 ## Sync procedure

View File

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

View File

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

View File

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

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More