Merge branch 'codex/simp-compaction-surface' into codex/simp-agent-entry-state

This commit is contained in:
Tianyi Cui
2026-07-18 13:54:35 +08:00
36 changed files with 790 additions and 150 deletions

View File

@@ -110,9 +110,14 @@ jobs:
fi
echo "DEEPSEEK_API_KEY present."
# The e2e suites boot the example bins in `lib` mode (DSH_EXAMPLE_MODE=lib):
# the built artifact under plain Node, resolving plugins through real package
# exports — the shape a real consumer runs. That requires a prior build.
- name: Build (lib for the e2e example bins)
run: pnpm run build
# Real-API end-to-end tests only. The keyless gates (lint/typecheck/
# coverage/snapshot/build/etc.) already run in ci.yml on every push/PR;
# no need to repeat them or build first (tests run unbuilt via tsx).
# coverage/snapshot/etc.) already run in ci.yml on every push/PR.
# DEEPSEEK_BASE_URL is pinned to the external API; the secret is scoped to
# this step (and preflight) only — never exposed to checkout/setup/install.
- name: E2E tests (real DeepSeek API)
@@ -120,4 +125,5 @@ jobs:
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY_EXTERNAL }}
DEEPSEEK_BASE_URL: https://api.deepseek.com
DSH_E2E_MAX_WORKERS: 14
DSH_EXAMPLE_MODE: lib
run: pnpm run test:e2e

View File

@@ -99,7 +99,7 @@ Real-API tests and demos read `DEEPSEEK_API_KEY`, optional `DEEPSEEK_BASE_URL`,
## Conventions
- Every npm package is `@deepseek-ai/dsh-<name>`; vendored packages keep upstream names and are `private: true`. `cordis` is a peerDependency (+ dev) of every harness package.
- ESM everywhere (`"type": "module"`). Cross-package imports use package names, never relative paths; in-package relative imports use explicit `.ts` extensions. Dev/test/demo run unbuilt via tsx + the root tsconfig `paths` map; builds are for outside consumers only.
- ESM everywhere (`"type": "module"`). Cross-package imports use package names; in-package relative imports include `.ts`. CI subprocesses that boot examples or Cordis configs run built `lib/` under plain Node; only explicit source-path regressions use tsx ([testing policy](docs/testing.md#test-subprocess-launch-modes)).
- **Registrations are effects**: every contribution goes through `ctx.effect()` / `ctx.on()`; a registry's `register()` returns the disposer.
- **Typed events use declaration merging** and merge-extensible maps. Event JSDoc needs `@mode` and payload `@param`; scoped keys absent from payloads need `@dshScopeScan unsupported`. Public service methods document parameters and non-void returns.
- **Switch on discriminant tags.** Closed unions end in `assertNever`; merge-extensible unions fall through a documented default.

View File

@@ -191,6 +191,7 @@ Generated by `pnpm run gen-rfc-index` from the RFC tree — never edit by hand;
| [A gated Known-Limitations section in every package README](implemented/process/2026-07-10-readme-known-limitations-gate.md) | 2026-07-10 |
| [Package Model Experience contract](implemented/process/2026-07-12-package-model-experience-contract.md) | 2026-07-12 |
| [TypeScript Program-backed semantic gates](implemented/process/2026-07-14-typescript-program-backed-semantic-gates.md) | 2026-07-14 |
| [Run CI examples from built lib](implemented/process/2026-07-17-run-ci-examples-from-built-lib.md) | 2026-07-17 |
### Testing

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-17-run-ci-examples-from-built-lib.md: aae88ee965b4e2211f3a53aeb9c0ee4944d95c2a
2026-07-17-run-ci-examples-from-built-lib.zh.md: 0cd3822a71b8f7399be93beaac74b2177fcbe7ca

View File

@@ -0,0 +1,42 @@
# RFC: Run CI examples from built lib
Status: implemented
English | [中文](2026-07-17-run-ci-examples-from-built-lib.zh.md)
## Problem
CI boots examples and Cordis-backed test projects through `node --import tsx` and the root tsconfig `paths` map. This adds TypeScript transformation cost and changes package resolution: imports resolve to workspace source instead of following package `exports` into built `lib/`.
These runs therefore do not test the same code or resolution behavior as an installed consumer. A package can pass CI while its built export graph is incomplete or resolves differently.
## Decision
Execution has two modes. `src` is the default local-development mode and uses tsx; `lib` is the strict CI mode and starts built bins with plain Node, without tsx or tsconfig path mapping.
- CI subprocesses that boot an example or a checked-in `cordis.yml` use `lib` mode.
- TypeScript fixtures that only implement an ACP or MCP peer and do not load Cordis run directly with Node. An explicit source-path regression may remain in `src` mode.
### Resolution topology
Every test Cordis config must resolve its bare modules by walking upward from the config directory.
- `examples/` is one pnpm workspace member and provides the shared `examples/node_modules` resolution root.
- Every checked-in test Cordis config, including snapshot configs and package-owned fixtures, lives under its corresponding `examples/<agent>/` tree. A config owned by `packages/<group>/<package>/` maps to `examples/<agent>/tests/fixtures/<group>/<package>/cordis.yml`; the test driver and assertions remain package-local.
- Every package named by an example Cordis config is declared in both `examples/package.json` for `lib` resolution and the root `tsconfig.json` references for `src` mode.
### Launch policy
The shared Loader test harness selects `src` or `lib` from `DSH_EXAMPLE_MODE`. CI builds first and selects `lib`; an unset mode keeps the fast local source loop.
## Alternatives considered
- **Keep CI on tsx** — rejected because it preserves transformation overhead and source-only resolution behavior.
- **Use lib everywhere** — rejected because local development would require a build before every run. Dual mode keeps that cost out of the development loop.
- **Build a private `node_modules` tree per test** — rejected because it duplicates consumer scaffolding. The `examples/` workspace root gives every Cordis config one real and declared resolution path.
## Consequences
- CI validates built package exports without tsx changing module resolution; local development retains the no-build source loop.
- CI must build before these tests, and manual `lib` runs can observe stale local artifacts.
- Cordis config dependencies are not visible to normal TypeScript import analysis, so `examples/package.json` and the root tsconfig references must stay synchronized with the configs.

View File

@@ -0,0 +1,42 @@
# RFC: 在 CI 中从构建后的 lib 运行示例
Status: implemented
[English](2026-07-17-run-ci-examples-from-built-lib.md) | 中文
## 问题
CI 通过 `node --import tsx` 和根 tsconfig 的 `paths` 映射启动示例与加载 Cordis 配置的测试项目。这种方式既增加了 TypeScript 转换开销也改变了包解析行为import 会解析到 workspace 源码,而不是经包的 `exports` 进入构建后的 `lib/`
因此这些测试没有覆盖已安装消费方实际运行的代码和解析路径。即使包的构建导出图不完整或解析结果不同CI 仍可能通过。
## 决策
执行机制包含两种模式。`src` 是本地开发的默认模式并使用 tsx`lib` 是严格的 CI 模式,通过 plain Node 启动构建后的 bin不加载 tsx也不使用 tsconfig 路径映射。
- CI 中启动示例或签入仓库的 `cordis.yml` 的子进程使用 `lib` 模式。
- 仅实现 ACP 或 MCP 对端、且不加载 Cordis 的 TypeScript fixture测试前置数据直接由 Node 运行。只有显式验证源码路径的回归测试可以保留 `src` 模式。
### 解析拓扑
每个测试 Cordis 配置都必须能从配置文件所在目录向上解析裸模块。
- `examples/` 作为一个 pnpm workspace 成员,提供统一的 `examples/node_modules` 解析根目录。
- 所有签入仓库的测试 Cordis 配置,包括快照配置和包内测试 fixture都放在对应的 `examples/<agent>/` 目录树下。归属 `packages/<group>/<package>/` 的配置映射到 `examples/<agent>/tests/fixtures/<group>/<package>/cordis.yml`;测试驱动和断言仍留在包内。
- 示例 Cordis 配置中引用的每个包都同时登记在 `examples/package.json` 和根 `tsconfig.json` 的 references 中,分别支持 `lib``src` 解析。
### 启动策略
共享 Loader 测试 harness 通过 `DSH_EXAMPLE_MODE` 选择 `src``lib`。CI 先构建再选择 `lib`;未设置模式时保留快速的本地源码开发回路。
## 曾考虑的替代方案
- **CI 继续使用 tsx**:不予采纳,因为它会保留转换开销和仅适用于源码的解析行为。
- **所有环境只使用 lib**:不予采纳,因为本地开发每次运行前都必须构建。双模式避免把这项成本带入开发回路。
- **每个测试单独构造 `node_modules`**:不予采纳,因为它会重复消费方脚手架。以 `examples/` 作为 workspace 根,可让每个 Cordis 配置通过同一条真实且显式声明的路径解析模块。
## 后果
- CI 可以验证构建后的包导出,不再受 tsx 模块解析影响;本地开发仍保留免构建的源码回路。
- CI 必须先构建再运行这些测试;手动执行 `lib` 模式时可能读取陈旧的本地产物。
- 常规 TypeScript import 分析无法识别 Cordis 配置依赖,因此 `examples/package.json`、根 tsconfig references 与配置文件必须保持同步。

View File

@@ -26,7 +26,12 @@ An e2e assertion re-runs the command or re-reads the file externally; a keyword
- Product-visible plugins require a non-unit REAL-composition test. Hand-built `ctx.plugin(...)` suites are insufficient: boot test-only `cordis.yml` through Loader and app/process, mock only external/nondeterministic boundaries, and assert model-visible request/log, durable state, or user-visible output. Keep opt-ins out of shipped defaults.
- A guard only guards if the regression actually fails it. For a plugin without `inject` (bundle/composition plugins), a Loader smoke stays green under a broken export shape — add an explicit `expect('default' in mod).toBe(false)` plus an `unwrapExports` round-trip assertion, and prove it: introduce the regression, watch red, revert.
- "Real entry path" means the published artifact: the package `bin` points at built `lib/bin.js` under plain `node`, which tsx masks (settle races, module resolution, a swallowed load failure exiting 0). The same applies to any non-index runtime entry the built package resolves at run time (the worker-thread runtime's sibling `lib/worker.cjs`). Keep the built-artifact smokes green (`packages/ui/*/tests/built-bin.e2e.ts`, `packages/code-runtime/code-runtime-worker/tests/built-lib.e2e.ts`), and assert a genuinely-missing config exits non-zero.
- An e2e that spawns an example from a temp cwd sets `TSX_TSCONFIG_PATH` to the repo-root tsconfig, or it silently falls back to stale built `lib/` ([examples/AGENTS.md](../examples/AGENTS.md)).
## Test subprocess launch modes
- CI and build-having test lanes run every example or Cordis-config subprocess from built `lib/` through the shared dual-mode launcher. Do not hand-write `--import tsx` for these subprocesses.
- Protocol and operating-system fixtures that do not load Cordis run erasable `.ts` directly with Node, without tsx or the root paths map.
- Only a test whose subject is source-path resolution may select `src`; state that contract in the test.
## When a snapshot test is required

View File

@@ -1,6 +1,6 @@
# AGENTS.md — Examples
Runnable harness compositions. **Examples are NOT workspaces**: their private `package.json` files are dependency-free stubs, and the cordis Loader boots each `cordis.yml` unbuilt through `tsx` plus the root tsconfig paths.
Runnable harness compositions. `examples/` is one workspace member and the module-resolution root for runnable and test Cordis configs; it is not a build target. [package.json](package.json) declares the packages loaded by those configs, while each leaf's private `package.json` remains metadata only.
Extract reusable logic into `packages/`, where per-file coverage and README gates apply. Examples keep only `cordis.yml` wiring, demo artifacts, and e2e/snapshot scenarios; app package bins own boot glue.
@@ -13,7 +13,7 @@ Each example has both:
Mock-only examples require only the keyless tier; state that exception in the test.
Keyless stdio smokes use `@deepseek-ai/dsh-loader-smoke` for isolation, root-tsconfig loading, subprocess lifecycle, diagnostics, EOF, and cleanup; tests supply paths, environment, input, and assertions.
Keyless stdio smokes use `@deepseek-ai/dsh-loader-smoke`; tests supply paths, environment, input, and assertions. Every checked-in test Cordis config lives under its corresponding `examples/<agent>/` leaf. Map a package-owned config to `examples/<agent>/tests/fixtures/<group>/<package>/cordis.yml`, keep its driver and assertions package-local, and declare every package it names in both root `tsconfig.json` references and `examples/package.json`.
Do not inventory example tests here; the `tests/` trees and root scripts are authoritative.

View File

@@ -1,6 +1,6 @@
# Test-only composition: keep time-context opt-in while exercising its real Loader/app path.
- id: mock-llm
name: '../../../../../examples/echo-agent/src/mock-llm.ts'
name: '../../../../src/mock-llm.ts'
- id: bash
name: '@deepseek-ai/dsh-bash-local'

46
examples/package.json Normal file
View File

@@ -0,0 +1,46 @@
{
"name": "dsh-examples",
"private": true,
"version": "0.0.1",
"type": "module",
"description": "Workspace umbrella for runnable demos and example-owned test compositions: declares their cordis.yml packages so plain Node resolves real exports→lib. Not a build target.",
"dependencies": {
"@cordisjs/plugin-hmr": "workspace:*",
"@cordisjs/plugin-include": "workspace:*",
"@deepseek-ai/dsh-acp-demo": "workspace:*",
"@deepseek-ai/dsh-bash-local": "workspace:*",
"@deepseek-ai/dsh-bash-sandbox": "workspace:*",
"@deepseek-ai/dsh-code-runtime-worker": "workspace:*",
"@deepseek-ai/dsh-compact-basic": "workspace:*",
"@deepseek-ai/dsh-fs-local": "workspace:*",
"@deepseek-ai/dsh-fs-policy": "workspace:*",
"@deepseek-ai/dsh-hooks-claude": "workspace:*",
"@deepseek-ai/dsh-hooks-codex": "workspace:*",
"@deepseek-ai/dsh-llm": "workspace:*",
"@deepseek-ai/dsh-llm-deepseek": "workspace:*",
"@deepseek-ai/dsh-llm-replay": "workspace:*",
"@deepseek-ai/dsh-permission": "workspace:*",
"@deepseek-ai/dsh-repeat-tool-guard": "workspace:*",
"@deepseek-ai/dsh-sandbox-local": "workspace:*",
"@deepseek-ai/dsh-spill-local": "workspace:*",
"@deepseek-ai/dsh-spill-policy": "workspace:*",
"@deepseek-ai/dsh-stdio-demo": "workspace:*",
"@deepseek-ai/dsh-subagent": "workspace:*",
"@deepseek-ai/dsh-subagent-fork": "workspace:*",
"@deepseek-ai/dsh-subagent-spawn": "workspace:*",
"@deepseek-ai/dsh-time-context": "workspace:*",
"@deepseek-ai/dsh-timeout-policy": "workspace:*",
"@deepseek-ai/dsh-token-meter": "workspace:*",
"@deepseek-ai/dsh-tool-cordis": "workspace:*",
"@deepseek-ai/dsh-tool-fs": "workspace:*",
"@deepseek-ai/dsh-tool-fs-search": "workspace:*",
"@deepseek-ai/dsh-tool-subagent": "workspace:*",
"@deepseek-ai/dsh-tool-todo": "workspace:*",
"@deepseek-ai/dsh-tool-workflow": "workspace:*",
"@deepseek-ai/dsh-tools": "workspace:*",
"@deepseek-ai/dsh-user-approval": "workspace:*",
"@deepseek-ai/dsh-web": "workspace:*",
"@deepseek-ai/dsh-web-fetch-local": "workspace:*",
"@deepseek-ai/dsh-workflow-workerthread": "workspace:*"
}
}

View File

@@ -5,15 +5,16 @@
"ignoreWorkspaces": ["vendor/*", "python/sdk-runtime", "website"],
"workspaces": {
".": {
"project": ["scripts/**/*.ts"]
},
"examples": {
"entry": [
"examples/echo-agent/src/*.ts",
"examples/echo-agent/tests/**/*.e2e.ts",
"examples/coding-agent/tests/**/*.e2e.ts",
"examples/cordis-agent/tests/**/*.e2e.ts",
"examples/acp-agent/tests/**/*.e2e.ts",
"examples/*/tests/**/*.snapshot.ts"
"echo-agent/src/*.ts",
"*/tests/**/*.e2e.ts",
"*/tests/**/*.snapshot.ts"
],
"project": ["scripts/**/*.ts", "examples/**/*.ts"]
"project": ["**/*.ts"],
"ignoreDependencies": ["@deepseek-ai/.+", "@cordisjs/.+"]
},
"packages/*/*": {
"entry": ["tests/**/*.spec.ts"],
@@ -52,8 +53,7 @@
},
"packages/support/acp-snapshot": {
"entry": ["tests/**/*.spec.ts", "tests/fixtures/fake-acp-agent.ts"],
"project": ["src/**/*.ts", "tests/**/*.ts"],
"ignoreDependencies": ["cordis"]
"project": ["src/**/*.ts", "tests/**/*.ts"]
},
"packages/support/loader-smoke": {
"entry": ["tests/**/*.spec.ts", "tests/fixtures/*.ts"],

View File

@@ -33,6 +33,7 @@
"@deepseek-ai/dsh-agent-loop": "workspace:^",
"@deepseek-ai/dsh-agent-loop-testkit": "workspace:^",
"@deepseek-ai/dsh-llm": "workspace:^",
"@deepseek-ai/dsh-loader-smoke": "workspace:^",
"@deepseek-ai/dsh-session": "workspace:^",
"@deepseek-ai/dsh-system-prompt": "workspace:^",
"@deepseek-ai/dsh-tools": "workspace:^",

View File

@@ -4,12 +4,17 @@ import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { afterEach, describe, expect, it } from 'vitest'
import type { SessionEvent } from '@deepseek-ai/dsh-session'
import { type SessionEvent } from '@deepseek-ai/dsh-session'
import { resolveExampleLaunch } from '@deepseek-ai/dsh-loader-smoke'
// Keep the Loader config under examples so both modes exercise the same deployable
// topology: local fixture source plus bare plugins owned by the examples workspace.
const binScript = fileURLToPath(new URL('../../../examples/stdio-demo/src/bin.ts', import.meta.url))
const configPath = fileURLToPath(new URL('./fixtures/cordis.yml', import.meta.url))
const configPath = fileURLToPath(new URL(
'../../../../examples/echo-agent/tests/fixtures/context/time-context/cordis.yml',
import.meta.url,
))
const repoTsconfig = fileURLToPath(new URL('../../../../tsconfig.json', import.meta.url))
const tsxLoader = fileURLToPath(import.meta.resolve('tsx'))
const PROCESS_TIMEOUT_MS = 30_000
const TEST_TIMEOUT_MS = PROCESS_TIMEOUT_MS + 15_000
const FIRST_REPLY = '[main turn 1] You said: "Time sampled while preparing turn 1, step 1:'
@@ -39,21 +44,22 @@ async function runTwoTurns(): Promise<{ stdout: string; stderr: string }> {
workdir = await mkdtemp(join(tmpdir(), 'time-context-e2e-'))
const cwd = workdir
return new Promise((resolve, reject) => {
const proc = spawn(
process.execPath,
['--expose-internals', '--import', tsxLoader, binScript, configPath],
{
cwd,
env: {
...process.env,
TZ: 'Asia/Shanghai',
TSX_TSCONFIG_PATH: repoTsconfig,
DSH_HOME: join(cwd, '.dsh'),
DSH_AGENTS_HOME: join(cwd, '.agents'),
},
stdio: ['pipe', 'pipe', 'pipe'],
const launch = resolveExampleLaunch({
srcBin: binScript,
configArgs: [configPath],
tsconfigPath: repoTsconfig,
exposeInternals: true,
env: {
TZ: 'Asia/Shanghai',
DSH_HOME: join(cwd, '.dsh'),
DSH_AGENTS_HOME: join(cwd, '.agents'),
},
)
})
const proc = spawn(launch.command, launch.args, {
cwd,
env: { ...process.env, ...launch.env },
stdio: ['pipe', 'pipe', 'pipe'],
})
child = proc
let stdout = ''
let stderr = ''

View File

@@ -10,6 +10,9 @@
{ "path": "../../../vendor/cordis" },
{ "path": "../../../vendor/schemastery" },
{ "path": "../../llm/llm" },
{ "path": "../../core/agent" }
{ "path": "../../core/agent" },
{ "path": "../../core/system-prompt" },
{ "path": "../../core/agent" },
{ "path": "../../support/loader-smoke" }
]
}

View File

@@ -2,7 +2,7 @@
* Minimal MCP server over stdio for e2e testing of the dsh-mcp-client plugin.
* Registers controlled tools with predictable behavior for asserting edge cases.
*
* Run: node --import tsx fixture-server.ts
* Run: node fixture-server.ts
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'

View File

@@ -26,9 +26,7 @@ import { apply } from '@deepseek-ai/dsh-mcp-client/src/index.ts'
import { publicToolName } from '@deepseek-ai/dsh-mcp-client/src/tools.ts'
import type { Config } from '@deepseek-ai/dsh-mcp-client'
const tsxLoader = fileURLToPath(import.meta.resolve('tsx'))
const fixtureServerPath = fileURLToPath(new URL('./fixture-server.ts', import.meta.url))
const repoTsconfig = fileURLToPath(new URL('../../../../tsconfig.json', import.meta.url))
// Resolve package-local .bin for pnpm-hoisted MCP server binaries.
const packageDir = fileURLToPath(new URL('..', import.meta.url))
@@ -86,8 +84,8 @@ describe('fixture server — controlled scenarios', () => {
transport: 'stdio',
serverName: 'fixture',
command: process.execPath,
args: ['--import', tsxLoader, fixtureServerPath],
env: { TSX_TSCONFIG_PATH: repoTsconfig },
args: [fixtureServerPath],
env: {},
cwd: packageDir,
toolCallTimeoutMs: 15_000,
}
@@ -170,8 +168,8 @@ describe('fixture server — duplicate serverName', () => {
transport: 'stdio',
serverName: 'dup',
command: process.execPath,
args: ['--import', tsxLoader, fixtureServerPath],
env: { TSX_TSCONFIG_PATH: repoTsconfig },
args: [fixtureServerPath],
env: {},
cwd: packageDir,
toolCallTimeoutMs: 15_000,
}
@@ -191,8 +189,8 @@ describe('fixture server — disposal', () => {
transport: 'stdio',
serverName: 'fixture',
command: process.execPath,
args: ['--import', tsxLoader, fixtureServerPath],
env: { TSX_TSCONFIG_PATH: repoTsconfig },
args: [fixtureServerPath],
env: {},
cwd: packageDir,
toolCallTimeoutMs: 15_000,
})

View File

@@ -35,6 +35,7 @@
"devDependencies": {
"@deepseek-ai/dsh-agent": "workspace:^",
"@deepseek-ai/dsh-llm": "workspace:^",
"@deepseek-ai/dsh-loader-smoke": "workspace:^",
"@deepseek-ai/dsh-subagent": "workspace:^",
"@deepseek-ai/dsh-subagent-subprocess": "workspace:^",
"@cordisjs/plugin-loader": "^1.0.0-rc.5",

View File

@@ -2,8 +2,8 @@
* Minimal no-network ACP child process for keyless backend tests. Environment variables script its
* text and stop reason, a cancel-cooperative or cancel-ignoring hang, permission requests, and a
* readiness marker. Disposal fixtures can delay an EOF flush, ignore EOF but exit and mark
* SIGTERM, or trap SIGTERM to require SIGKILL. The specs spawn this non-test module under tsx with
* an explicit tsconfig, mirroring real example boot.
* SIGTERM, or trap SIGTERM to require SIGKILL. The specs run this protocol-only fixture directly
* with Node's type stripping; it imports no harness code or workspace paths.
* @module @deepseek-ai/dsh-subagent-acp/tests/mock-acp-server
*/

View File

@@ -6,6 +6,7 @@ import { afterEach, describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import type { Agent } from '@deepseek-ai/dsh-agent'
import SubagentService from '@deepseek-ai/dsh-subagent'
import { resolveExampleLaunch } from '@deepseek-ai/dsh-loader-smoke'
import * as acp from '../src/index.ts'
/**
@@ -17,9 +18,22 @@ import * as acp from '../src/index.ts'
// The real acp-agent example: its bin + cordis.yml (the live DeepSeek config).
const binScript = fileURLToPath(new URL('../../../examples/acp-demo/src/bin.ts', import.meta.url))
const exampleConfig = fileURLToPath(new URL('../../../../examples/acp-agent/cordis.yml', import.meta.url))
const tsxLoader = fileURLToPath(import.meta.resolve('tsx'))
const repoTsconfig = fileURLToPath(new URL('../../../../tsconfig.json', import.meta.url))
// How to launch the child acp-agent (src via tsx / lib via plain node, per DSH_EXAMPLE_MODE).
// buildChildEnv scrubs ambient creds but keeps these extras, so the model key is
// forwarded explicitly; TSX_TSCONFIG_PATH is added by the resolver in src mode only.
const childLaunch = resolveExampleLaunch({
srcBin: binScript,
configArgs: ['--config', exampleConfig],
tsconfigPath: repoTsconfig,
env: {
...process.env.DEEPSEEK_API_KEY !== undefined ? { DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY } : {},
...process.env.DEEPSEEK_BASE_URL !== undefined ? { DEEPSEEK_BASE_URL: process.env.DEEPSEEK_BASE_URL } : {},
DSH_PERMISSION_MODE: 'danger-full-access',
},
})
/** The ACP backend ignores the parent, but the seam requires one. */
const fakeParent = { id: 'parent', session: { header: {} } } as unknown as Agent
@@ -40,18 +54,11 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('ACP backend with-key e2e (drive
await ctx.plugin(SubagentService)
await ctx.plugin(acp, {
providerName: 'acp',
command: process.execPath,
args: ['--import', tsxLoader, binScript, '--config', exampleConfig],
command: childLaunch.command,
args: childLaunch.args,
cwd: workdir,
permission: 'reject',
// The child harness needs the key to reach the model; forward it
// explicitly (buildChildEnv scrubs ambient creds but keeps these extras).
env: {
...process.env.DEEPSEEK_API_KEY !== undefined ? { DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY } : {},
...process.env.DEEPSEEK_BASE_URL !== undefined ? { DEEPSEEK_BASE_URL: process.env.DEEPSEEK_BASE_URL } : {},
TSX_TSCONFIG_PATH: repoTsconfig,
DSH_PERMISSION_MODE: 'danger-full-access',
},
env: childLaunch.env as Record<string, string>,
})
const run = await ctx.subagents.start('acp', {
@@ -76,17 +83,12 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('ACP backend with-key e2e (drive
await ctx.plugin(SubagentService)
await ctx.plugin(acp, {
providerName: 'acp',
command: process.execPath,
args: ['--import', tsxLoader, binScript, '--config', exampleConfig],
command: childLaunch.command,
args: childLaunch.args,
cwd: workdir,
// The child needs to act (run bash), so approve its permission prompts.
permission: 'allow',
env: {
...process.env.DEEPSEEK_API_KEY !== undefined ? { DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY } : {},
...process.env.DEEPSEEK_BASE_URL !== undefined ? { DEEPSEEK_BASE_URL: process.env.DEEPSEEK_BASE_URL } : {},
TSX_TSCONFIG_PATH: repoTsconfig,
DSH_PERMISSION_MODE: 'danger-full-access',
},
env: childLaunch.env as Record<string, string>,
})
const run = await ctx.subagents.start('acp', {

View File

@@ -21,8 +21,6 @@ import { acpStopReason, acpContentText, DEFAULT_DISPOSE_EOF_GRACE_MS, DEFAULT_DI
*/
const mockServer = fileURLToPath(new URL('./mock-acp-server.ts', import.meta.url))
const tsxLoader = fileURLToPath(import.meta.resolve('tsx'))
const repoTsconfig = fileURLToPath(new URL('../../../../tsconfig.json', import.meta.url))
/** A throwaway parent Agent — the ACP backend ignores it, but the seam requires one. */
const fakeParent = { id: 'parent', session: { header: {} } } as unknown as Agent
@@ -46,11 +44,9 @@ async function setup(mockEnv: SetupEnv = {}, permission: 'allow' | 'reject' = 'r
await ctx.plugin(acp, {
providerName: 'acp',
command: process.execPath,
args: ['--import', tsxLoader, mockServer],
args: [mockServer],
permission,
// The mock-server scripting vars must reach the child; TSX_TSCONFIG_PATH lets
// tsx resolve @deepseek-ai/* from a child cwd outside the repo.
env: { ...mockEnv, TSX_TSCONFIG_PATH: repoTsconfig },
env: mockEnv,
})
return ctx
}
@@ -197,10 +193,10 @@ describe('dsh-subagent-acp', () => {
try {
const spec: AcpRunSpec = {
command: process.execPath,
args: ['--import', tsxLoader, mockServer],
args: [mockServer],
cwd: process.cwd(),
permission: 'reject',
env: { MOCK_TRAP_SIGTERM: '1', MOCK_TEXT: 'x', MOCK_READY_FILE: ready, TSX_TSCONFIG_PATH: repoTsconfig },
env: { MOCK_TRAP_SIGTERM: '1', MOCK_TEXT: 'x', MOCK_READY_FILE: ready },
// Short on BOTH tiers: the trap ignores EOF and SIGTERM, so dispose must
// burn the EOF window, then the SIGTERM window, then SIGKILL — keep each
// small so the whole ladder finishes well within the 4000ms bound.
@@ -240,7 +236,7 @@ describe('dsh-subagent-acp', () => {
try {
const spec: AcpRunSpec = {
command: process.execPath,
args: ['--import', tsxLoader, mockServer],
args: [mockServer],
cwd: process.cwd(),
permission: 'reject',
// MOCK_HANG so the prompt never resolves on its own — we tear down a live
@@ -249,7 +245,7 @@ describe('dsh-subagent-acp', () => {
// wider grace.
env: {
MOCK_HANG: '1', MOCK_TEXT: 'x', MOCK_READY_FILE: ready,
MOCK_FLUSH_ON_EOF: flushed, MOCK_FLUSH_DELAY_MS: '400', TSX_TSCONFIG_PATH: repoTsconfig,
MOCK_FLUSH_ON_EOF: flushed, MOCK_FLUSH_DELAY_MS: '400',
},
disposeEofGraceMs: 2000,
disposeGraceMs: 50,
@@ -280,12 +276,12 @@ describe('dsh-subagent-acp', () => {
try {
const spec: AcpRunSpec = {
command: process.execPath,
args: ['--import', tsxLoader, mockServer],
args: [mockServer],
cwd: process.cwd(),
permission: 'reject',
env: {
MOCK_HANG: '1', MOCK_IGNORE_EOF: '1', MOCK_TEXT: 'x',
MOCK_READY_FILE: ready, MOCK_SIGTERM_FILE: sigterm, TSX_TSCONFIG_PATH: repoTsconfig,
MOCK_READY_FILE: ready, MOCK_SIGTERM_FILE: sigterm,
},
// Tiny EOF grace so the ignored-EOF window elapses fast, then SIGTERM.
disposeEofGraceMs: 150,
@@ -404,9 +400,9 @@ describe('dsh-subagent-acp', () => {
await ctx.plugin(acp, {
providerName: 'acp',
command: process.execPath,
args: ['--import', tsxLoader, mockServer],
args: [mockServer],
permission: 'reject',
env: { MOCK_TRAP_SIGTERM: '1', MOCK_TEXT: 'x', MOCK_READY_FILE: ready, TSX_TSCONFIG_PATH: repoTsconfig },
env: { MOCK_TRAP_SIGTERM: '1', MOCK_TEXT: 'x', MOCK_READY_FILE: ready },
disposeEofGraceMs: 150,
disposeGraceMs: 150,
})
@@ -455,10 +451,10 @@ describe('dsh-subagent-acp', () => {
request(),
{
command: process.execPath,
args: ['--import', tsxLoader, mockServer],
args: [mockServer],
cwd: process.cwd(),
permission: 'reject',
env: { MOCK_CRASH_ON_PROMPT: '1', TSX_TSCONFIG_PATH: repoTsconfig },
env: { MOCK_CRASH_ON_PROMPT: '1' },
disposeEofGraceMs: DEFAULT_DISPOSE_EOF_GRACE_MS,
disposeGraceMs: DEFAULT_DISPOSE_GRACE_MS,
onError: (error, stopReason) => { errors.push({ message: error.message, stopReason }) },
@@ -493,10 +489,10 @@ describe('dsh-subagent-acp', () => {
request(),
{
command: process.execPath,
args: ['--import', tsxLoader, mockServer],
args: [mockServer],
cwd: process.cwd(),
permission: 'reject',
env: { MOCK_CRASH_ON_PROMPT: '1', TSX_TSCONFIG_PATH: repoTsconfig },
env: { MOCK_CRASH_ON_PROMPT: '1' },
disposeEofGraceMs: DEFAULT_DISPOSE_EOF_GRACE_MS,
disposeGraceMs: DEFAULT_DISPOSE_GRACE_MS,
onError: () => { throw new Error('sink boom') },

View File

@@ -28,6 +28,9 @@
},
{
"path": "../subagent-subprocess"
},
{
"path": "../../support/loader-smoke"
}
]
}

View File

@@ -23,7 +23,7 @@
"license": "BSD-3-Clause",
"dependencies": {
"@agentclientprotocol/sdk": "0.25.1",
"tsx": "^4.22.4",
"@deepseek-ai/dsh-loader-smoke": "workspace:*",
"vitest": "^4.1.8"
},
"peerDependencies": {

View File

@@ -1,6 +1,6 @@
/**
* Shared launcher for ACP tests that drive an unbuilt agent subprocess over
* JSON-RPC stdio. It owns the tsx loader, workspace-resolution environment,
* Shared launcher for ACP tests that drive an agent subprocess over JSON-RPC
* stdio. It owns source-or-built launch resolution, workspace environment,
* stdout tee, SDK client, update collection, permission fallback, and process
* shutdown so e2e and snapshot suites do not each reconstruct that boundary.
*
@@ -9,7 +9,6 @@
import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { Readable, Writable } from 'node:stream'
import {
ClientSideConnection,
@@ -20,15 +19,14 @@ import {
type RequestPermissionResponse,
type SessionNotification,
} from '@agentclientprotocol/sdk'
import { resolveExampleLaunch } from '@deepseek-ai/dsh-loader-smoke'
// The child runs from a temp directory outside the repo, where a bare
// `--import tsx` cannot resolve. Resolve this package's loader once instead.
const tsxLoader = fileURLToPath(import.meta.resolve('tsx'))
/** The unbuilt agent entry, leaf config, and workspace tsconfig an ACP test boots. */
/** The source/built agent entry, leaf config, and workspace tsconfig an ACP test boots. */
export interface AgentUnderTest {
/** The agent bin entry (for example `packages/examples/acp-demo/src/bin.ts`). */
/** The agent source bin entry (for example `packages/examples/acp-demo/src/bin.ts`). */
binScript: string
/** Explicit built-mode entry for fixtures whose source path is not under `src/`. */
libBinScript?: string | undefined
/** The leaf `cordis.yml` loaded by the bin. */
configPath: string
/** The repo tsconfig whose paths resolve unbuilt workspace imports. */
@@ -77,18 +75,23 @@ export interface LaunchedAcpTestAgent {
*/
export function launchAcpTestAgent(options: AcpTestLaunchOptions): LaunchedAcpTestAgent {
const { agent, cwd } = options
const launch = resolveExampleLaunch({
srcBin: agent.binScript,
libBin: agent.libBinScript,
configArgs: ['--config', options.configPath ?? agent.configPath],
tsconfigPath: agent.tsconfigPath,
env: {
...options.env,
DSH_HOME: join(cwd, '.dsh'),
DSH_AGENTS_HOME: join(cwd, '.agents'),
},
})
const child = spawn(
process.execPath,
['--import', tsxLoader, agent.binScript, '--config', options.configPath ?? agent.configPath],
launch.command,
launch.args,
{
cwd,
env: {
...process.env,
...options.env,
TSX_TSCONFIG_PATH: agent.tsconfigPath,
DSH_HOME: join(cwd, '.dsh'),
DSH_AGENTS_HOME: join(cwd, '.agents'),
},
env: { ...process.env, ...launch.env },
stdio: ['pipe', 'pipe', 'pipe'],
},
)

View File

@@ -28,17 +28,19 @@ vi.mock('node:fs/promises', async (importOriginal) => {
/**
* Unit tests for the subprocess harness, driven through the REAL spawn path
* (tsx loader, temp cwd, env plumbing) against the scripted fake ACP bin in
* (mode-aware launcher, temp cwd, env plumbing) against the scripted fake ACP bin in
* ./fixtures/fake-acp-agent.ts. Each case writes a `behavior.json` next to a
* throwaway fixture path; the fake bin echoes observable facts (env, seeded
* workspace, permission outcomes) into `agent_message_chunk` text, so the
* assertions read plain `rawStdout`.
*/
const fakeAgent = fileURLToPath(new URL('./fixtures/fake-acp-agent.ts', import.meta.url))
const AGENT: AgentUnderTest = {
binScript: fileURLToPath(new URL('./fixtures/fake-acp-agent.ts', import.meta.url)),
binScript: fakeAgent,
libBinScript: fakeAgent,
// The fake bin ignores its config argv; any real path documents the shape.
configPath: fileURLToPath(new URL('./fixtures/fake-acp-agent.ts', import.meta.url)),
configPath: fakeAgent,
tsconfigPath: fileURLToPath(new URL('../../../../tsconfig.json', import.meta.url)),
}
@@ -242,6 +244,23 @@ describe('runScenario', () => {
)).rejects.toThrow(/agent stderr:\nfake agent requested startup failure/)
})
it('preserves launch-resolution errors when no child process exists', async () => {
const { dir, fixtureFile } = await scenario({})
vi.stubEnv('DSH_EXAMPLE_MODE', 'lib')
try {
await expect(runScenario(
{ steps: [] },
{
agent: { ...AGENT, binScript: join(dir, 'outside-src.ts'), libBinScript: undefined },
mode: 'replay',
fixtureFile,
},
)).rejects.toThrow(/expected a "\/src\/" segment/)
} finally {
vi.unstubAllEnvs()
}
})
it('drives a full turn: initialize (terminal caps), session, prompt, permission stub, harvest', { timeout: 20_000 }, async () => {
const { fixtureFile } = await scenario({
permissionProbe: true,

View File

@@ -32,9 +32,11 @@ import {
* spec once with `ACP_SNAPSHOT_SPEC_BOOTSTRAP=1`, then review and commit the resulting tree.
*/
const fakeAgent = fileURLToPath(new URL('./fixtures/fake-acp-agent.ts', import.meta.url))
const AGENT = {
binScript: fileURLToPath(new URL('./fixtures/fake-acp-agent.ts', import.meta.url)),
configPath: fileURLToPath(new URL('./fixtures/fake-acp-agent.ts', import.meta.url)),
binScript: fakeAgent,
libBinScript: fakeAgent,
configPath: fakeAgent,
tsconfigPath: fileURLToPath(new URL('../../../../tsconfig.json', import.meta.url)),
}

View File

@@ -7,5 +7,7 @@
"include": [
"src"
],
"references": []
"references": [
{ "path": "../loader-smoke" }
]
}

View File

@@ -1,10 +1,10 @@
# `@deepseek-ai/dsh-loader-smoke`
Shared subprocess harness for keyless example smokes that boot the real stdio-agent bin and a real `cordis.yml` through the Cordis Loader. A test supplies absolute bin/config/tsconfig paths, optional environment overrides, and stdin lines; `runLoaderSmoke` owns the isolated cwd, DSH homes, tsx path resolution, 30-second process deadline, captured diagnostics, forced kill, EOF, and cleanup.
Shared subprocess harness for tests that boot an app and `cordis.yml` through the Cordis Loader. `resolveExampleLaunch` selects local `src` mode (tsx and root tsconfig paths) or CI `lib` mode (plain Node and package exports) from an explicit mode or `DSH_EXAMPLE_MODE`.
Successful runs return stdout and stderr only after a zero exit. Non-zero exits and deadlines reject with both captured streams. `LOADER_SMOKE_TEST_TIMEOUT_MS` leaves Vitest enough room for the process-owned diagnostic timeout to fire first.
`runLoaderSmoke` owns the isolated cwd, DSH homes, stdin, diagnostics, deadline, termination, and cleanup. It returns both streams after a zero exit and rejects with both streams on failure.
This is support-tier test infrastructure, not product API. The consumers are the Loader-path smokes under `examples/{echo-agent,coding-agent,cordis-agent}`.
This is support-tier test infrastructure, not product API.
## Model Experience
@@ -12,6 +12,6 @@ None, as this test-only harness boots example processes and inspects their strea
## Known Limitations and Deferred Work
- **Only the unbuilt tsx/Loader path is exercised** — built-bin artifacts remain the responsibility of their separate e2e smokes.
- **Built mode requires a prior build** — the config must also resolve every named package upward through `examples/node_modules`.
- **Captured stdout and stderr are unbounded** — a runaway child can consume memory until the deadline kills it.
- **Timeout kills only the direct child** — a process tree spawned by a faulty fixture can outlive the smoke and needs external cleanup.

View File

@@ -2,6 +2,14 @@
* Shared subprocess harness for keyless example smokes that boot a real
* `cordis.yml` through the stdio-agent bin and Cordis Loader.
*
* It also owns the mode-aware launch resolver every example subprocess harness shares
* ({@link resolveExampleLaunch}): booting an example bin from TypeScript source under `tsx` (the
* zero-build dev path, resolving `@deepseek-ai/dsh-*` / `@cordisjs/*` through the tsconfig `paths`
* map) or from built `lib/` under plain Node (resolving bare packages through real `exports`, as an
* installed consumer does, while Node type-strips relative example-local TypeScript plugins).
* Consolidating that spawn glue here retires the copies in the ACP snapshot harness and the example
* e2e drivers (the `TODO(acp-test-harness)`).
*
* @module @deepseek-ai/dsh-loader-smoke
*/
@@ -9,26 +17,125 @@ import { spawn } from 'node:child_process'
import { mkdtemp, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
const DEFAULT_PROCESS_TIMEOUT_MS = 30_000
const TSX_LOADER = fileURLToPath(import.meta.resolve('tsx'))
/** Vitest deadline that leaves room for the subprocess-owned 30-second diagnostic timeout. */
export const LOADER_SMOKE_TEST_TIMEOUT_MS = DEFAULT_PROCESS_TIMEOUT_MS + 15_000
/** Which artifact an example bin is booted from: unbuilt `src` via tsx, or built `lib` via plain Node. */
export type ExampleMode = 'src' | 'lib'
/** Environment variable selecting the mode; CI and pre-push set it to `lib`, dev leaves it unset (`src`). */
export const EXAMPLE_MODE_ENV = 'DSH_EXAMPLE_MODE'
/**
* Parse an {@link ExampleMode} from a raw string, defaulting to `src` when absent so an unset
* environment reproduces the dev/tsx behavior. Throws on any other value rather than silently
* falling back, so a typo in a gate's env fails loud.
* @param raw - the raw value; defaults to `process.env.DSH_EXAMPLE_MODE`.
* @returns the validated mode.
*/
export function resolveExampleMode(raw: string | undefined = process.env[EXAMPLE_MODE_ENV]): ExampleMode {
switch (raw) {
case undefined:
case '':
case 'src':
return 'src'
case 'lib':
return 'lib'
default:
throw new Error(`${EXAMPLE_MODE_ENV} must be 'src' or 'lib', got ${JSON.stringify(raw)}.`)
}
}
/** Inputs to {@link resolveExampleLaunch}. */
export interface ExampleLaunchOptions {
/** Absolute path to the example bin's TypeScript source entry (`<pkg>/src/bin.ts`); the `lib` bin is derived from it. */
readonly srcBin: string
/** Explicit plain-Node entry for `lib` mode; test fixtures may point this at Node-type-strippable TypeScript. */
readonly libBin?: string | undefined
/** Arguments passed after the bin — the config, positional (`[configPath]`) or flagged (`['--config', configPath]`). */
readonly configArgs?: readonly string[]
/** The mode to launch in; defaults to {@link resolveExampleMode} of the environment. */
readonly mode?: ExampleMode
/** Absolute repo tsconfig whose `paths` map resolves unbuilt workspace imports. Required in `src` mode, ignored in `lib`. */
readonly tsconfigPath?: string
/** Prepend `--expose-internals` (the Cordis Loader's bare-plugin resolver needs it for some bins); defaults to `false`. */
readonly exposeInternals?: boolean
/** Extra environment entries the mode-specific ones layer over; the caller then merges the result over `process.env`. */
readonly env?: NodeJS.ProcessEnv
}
/** The resolved spawn: `spawn(command, args, { env: { ...process.env, ...env } })`. */
export interface ExampleLaunch {
/** The executable to spawn — always the current Node binary. */
readonly command: string
/** Node flags, the resolved bin, then the caller's `configArgs`. */
readonly args: string[]
/** Mode-specific environment (`TSX_TSCONFIG_PATH` in `src`, nothing added in `lib`) layered over the caller's `env`. */
readonly env: NodeJS.ProcessEnv
}
/** Derive the built-lib bin (`<pkg>/lib/<name>.js`) from a source bin (`<pkg>/src/<name>.ts`). */
function toLibBin(srcBin: string): string {
const markerLength = '/src/'.length
const cut = Math.max(srcBin.lastIndexOf('/src/'), srcBin.lastIndexOf('\\src\\'))
if (cut === -1) {
throw new Error(`resolveExampleLaunch: expected a "/src/" segment or Windows equivalent in bin path ${JSON.stringify(srcBin)}.`)
}
const separator = srcBin.slice(cut, cut + 1)
const tail = srcBin.slice(cut + markerLength).replace(/\.ts$/, '.js')
return `${srcBin.slice(0, cut)}${separator}lib${separator}${tail}`
}
/**
* Resolve how to spawn an example bin in the selected mode.
*
* `src` yields `node [--expose-internals] --import <tsx> <srcBin> <configArgs>` with `TSX_TSCONFIG_PATH`
* set so the tsconfig `paths` map resolves workspace imports to source. `lib` yields
* `node [--expose-internals] <libBin> <configArgs>` under plain Node with no tsx and no paths map, so
* bare package plugins resolve through real package `exports` into built `lib/`; relative example-local
* TypeScript plugins remain source files loaded through Node's built-in type stripping. Bare resolution
* requires the config to live below a workspace that declares its `cordis.yml` package dependencies.
*
* @param options - the source bin, config arguments, mode, and environment.
* @returns the command, argument vector, and mode-specific environment to spawn with.
*/
export function resolveExampleLaunch(options: ExampleLaunchOptions): ExampleLaunch {
const mode = options.mode ?? resolveExampleMode()
const configArgs = options.configArgs ?? []
const flags = options.exposeInternals === true ? ['--expose-internals'] : []
const env: NodeJS.ProcessEnv = { ...options.env }
if (mode === 'src') {
if (options.tsconfigPath === undefined) {
throw new Error("resolveExampleLaunch: 'src' mode needs tsconfigPath for the workspace paths map.")
}
const tsxLoader = import.meta.resolve('tsx')
env.TSX_TSCONFIG_PATH = options.tsconfigPath
return { command: process.execPath, args: [...flags, '--import', tsxLoader, options.srcBin, ...configArgs], env }
}
return { command: process.execPath, args: [...flags, options.libBin ?? toLibBin(options.srcBin), ...configArgs], env }
}
/** Inputs that vary between real-Loader example smokes. */
export interface LoaderSmokeOptions {
/** Human-readable example name used in failure diagnostics. */
readonly label: string
/** Prefix for the isolated temporary process cwd. */
readonly tempDirPrefix: string
/** Absolute stdio-agent bin path. */
/** Absolute stdio-agent bin SOURCE path (`<pkg>/src/bin.ts`); the `lib` bin is derived from it. */
readonly binScript: string
/** Explicit plain-Node entry for `lib` mode; intended for test fixtures outside a package `src/` tree. */
readonly libBinScript?: string | undefined
/** Absolute real Loader config path. */
readonly configPath: string
/** Absolute repo tsconfig path used for unbuilt workspace-package resolution. */
/** Absolute repo tsconfig path used for unbuilt workspace-package resolution (required in `src` mode). */
readonly tsconfigPath: string
/** Boot from source via tsx (`src`) or built lib via plain Node (`lib`); defaults to the environment's mode. */
readonly mode?: ExampleMode
/** Environment overrides layered over the parent and isolated DSH homes. */
readonly env?: Readonly<NodeJS.ProcessEnv>
/** Lines written to stdin before EOF; omitted means immediate EOF. */
@@ -48,30 +155,29 @@ export interface LoaderSmokeResult {
/**
* Boot one real Loader tree from an isolated cwd, write the requested stdin
* script, close stdin, and await a clean exit. The helper owns process kill and
* temp-directory cleanup on every outcome.
* @param options - example paths, environment, stdin, and diagnostic identity.
* temp-directory cleanup on every outcome, and picks src/lib via {@link resolveExampleLaunch}.
* @param options - example paths, mode, environment, stdin, and diagnostic identity.
* @returns captured stdout and stderr after a zero exit.
*/
export async function runLoaderSmoke(options: LoaderSmokeOptions): Promise<LoaderSmokeResult> {
const cwd = await mkdtemp(join(tmpdir(), options.tempDirPrefix))
const processTimeoutMs = options.processTimeoutMs ?? DEFAULT_PROCESS_TIMEOUT_MS
const launch = resolveExampleLaunch({
srcBin: options.binScript,
libBin: options.libBinScript,
configArgs: [options.configPath],
...options.mode !== undefined ? { mode: options.mode } : {},
tsconfigPath: options.tsconfigPath,
exposeInternals: true,
env: { DSH_HOME: join(cwd, '.dsh'), DSH_AGENTS_HOME: join(cwd, '.agents'), ...options.env },
})
try {
return await new Promise((resolve, reject) => {
const child = spawn(
process.execPath,
['--expose-internals', '--import', TSX_LOADER, options.binScript, options.configPath],
{
cwd,
env: {
...process.env,
DSH_HOME: join(cwd, '.dsh'),
DSH_AGENTS_HOME: join(cwd, '.agents'),
...options.env,
TSX_TSCONFIG_PATH: options.tsconfigPath,
},
stdio: ['pipe', 'pipe', 'pipe'],
},
)
const child = spawn(launch.command, launch.args, {
cwd,
env: { ...process.env, ...launch.env },
stdio: ['pipe', 'pipe', 'pipe'],
})
let stdout = ''
let stderr = ''
let deferredFailure: Error | undefined

View File

@@ -0,0 +1,111 @@
import { afterEach, describe, expect, it } from 'vitest'
import {
EXAMPLE_MODE_ENV,
resolveExampleLaunch,
resolveExampleMode,
} from '@deepseek-ai/dsh-loader-smoke'
const SRC_BIN = '/repo/packages/examples/stdio-demo/src/bin.ts'
const TSCONFIG = '/repo/tsconfig.json'
const originalMode = process.env[EXAMPLE_MODE_ENV]
afterEach(() => {
if (originalMode === undefined) Reflect.deleteProperty(process.env, EXAMPLE_MODE_ENV)
else process.env[EXAMPLE_MODE_ENV] = originalMode
})
describe('resolveExampleMode', () => {
it('defaults absent/empty/src to src', () => {
Reflect.deleteProperty(process.env, EXAMPLE_MODE_ENV)
expect(resolveExampleMode()).toBe('src')
expect(resolveExampleMode('')).toBe('src')
expect(resolveExampleMode('src')).toBe('src')
})
it('accepts lib', () => {
expect(resolveExampleMode('lib')).toBe('lib')
})
it('throws on any other value', () => {
expect(() => resolveExampleMode('prod')).toThrow(/must be 'src' or 'lib'/)
})
it('reads the environment when no argument is given', () => {
process.env[EXAMPLE_MODE_ENV] = 'lib'
expect(resolveExampleMode()).toBe('lib')
Reflect.deleteProperty(process.env, EXAMPLE_MODE_ENV)
expect(resolveExampleMode()).toBe('src')
})
})
describe('resolveExampleLaunch', () => {
it('src mode: --import tsx on the source bin with the tsconfig paths env', () => {
const { command, args, env } = resolveExampleLaunch({
srcBin: SRC_BIN,
configArgs: ['./cordis.yml'],
mode: 'src',
tsconfigPath: TSCONFIG,
})
expect(command).toBe(process.execPath)
expect(args).toContain('--import')
expect(args).toContain(SRC_BIN)
expect(args[args.length - 1]).toBe('./cordis.yml')
expect(args).not.toContain('--expose-internals')
expect(env.TSX_TSCONFIG_PATH).toBe(TSCONFIG)
})
it('src mode: throws without a tsconfig path', () => {
expect(() => resolveExampleLaunch({ srcBin: SRC_BIN, mode: 'src' })).toThrow(/needs tsconfigPath/)
})
it('lib mode: plain node on the derived lib bin, no tsx and no paths env', () => {
const { args, env } = resolveExampleLaunch({
srcBin: SRC_BIN,
configArgs: ['--config', './cordis.yml'],
mode: 'lib',
env: { DSH_HOME: '/tmp/home' },
})
expect(args).not.toContain('--import')
expect(args).toContain('/repo/packages/examples/stdio-demo/lib/bin.js')
expect(args.slice(-2)).toEqual(['--config', './cordis.yml'])
expect(env.TSX_TSCONFIG_PATH).toBeUndefined()
expect(env.DSH_HOME).toBe('/tmp/home')
})
it('lib mode: uses an explicit plain-Node bin when provided', () => {
const fixture = '/repo/fixture.ts'
const { args } = resolveExampleLaunch({ srcBin: fixture, libBin: fixture, mode: 'lib' })
expect(args).toContain(fixture)
})
it('prepends --expose-internals when requested', () => {
const { args } = resolveExampleLaunch({ srcBin: SRC_BIN, mode: 'lib', exposeInternals: true })
expect(args[0]).toBe('--expose-internals')
})
it('lib mode: rewrites only the last /src/ segment', () => {
const { args } = resolveExampleLaunch({
srcBin: '/repo/src/packages/examples/acp-demo/src/bin.ts',
mode: 'lib',
})
expect(args).toContain('/repo/src/packages/examples/acp-demo/lib/bin.js')
})
it('lib mode: derives the built bin from a Windows source path', () => {
const { args } = resolveExampleLaunch({
srcBin: String.raw`D:\repo\src\packages\examples\acp-demo\src\bin.ts`,
mode: 'lib',
})
expect(args).toContain(String.raw`D:\repo\src\packages\examples\acp-demo\lib\bin.js`)
})
it('lib mode: throws when the bin has no /src/ segment', () => {
expect(() => resolveExampleLaunch({ srcBin: '/repo/lib/bin.js', mode: 'lib' })).toThrow(/"\/src\/" segment/)
})
it('defaults the mode from the environment', () => {
process.env[EXAMPLE_MODE_ENV] = 'lib'
const { args } = resolveExampleLaunch({ srcBin: SRC_BIN })
expect(args).toContain('/repo/packages/examples/stdio-demo/lib/bin.js')
})
})

View File

@@ -16,6 +16,7 @@ describe('runLoaderSmoke', () => {
binScript: fixture('success'),
configPath,
tsconfigPath,
mode: 'src',
env: { LOADER_SMOKE_MARKER: 'present' },
stdinLines: ['one', 'two'],
})
@@ -43,6 +44,7 @@ describe('runLoaderSmoke', () => {
label: 'failure fixture',
tempDirPrefix: 'loader-smoke-fail-',
binScript: fixture('fail'),
libBinScript: fixture('fail'),
configPath,
tsconfigPath,
})).rejects.toThrow('failure fixture exited 7. stdout:\n\nstderr:\nfixture failed')
@@ -53,6 +55,7 @@ describe('runLoaderSmoke', () => {
label: 'hanging fixture',
tempDirPrefix: 'loader-smoke-hang-',
binScript: fixture('hang'),
libBinScript: fixture('hang'),
configPath,
tsconfigPath,
processTimeoutMs: 100,

126
pnpm-lock.yaml generated
View File

@@ -90,6 +90,120 @@ importers:
specifier: ^4.1.8
version: 4.1.8(@types/node@22.20.0)(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(vite@8.0.16(@types/node@22.20.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
examples:
dependencies:
'@cordisjs/plugin-hmr':
specifier: workspace:*
version: link:../vendor/hmr
'@cordisjs/plugin-include':
specifier: workspace:*
version: link:../vendor/include
'@deepseek-ai/dsh-acp-demo':
specifier: workspace:*
version: link:../packages/examples/acp-demo
'@deepseek-ai/dsh-bash-local':
specifier: workspace:*
version: link:../packages/bash/bash-local
'@deepseek-ai/dsh-bash-sandbox':
specifier: workspace:*
version: link:../packages/bash/bash-sandbox
'@deepseek-ai/dsh-code-runtime-worker':
specifier: workspace:*
version: link:../packages/code-runtime/code-runtime-worker
'@deepseek-ai/dsh-compact-basic':
specifier: workspace:*
version: link:../packages/compact/compact-basic
'@deepseek-ai/dsh-fs-local':
specifier: workspace:*
version: link:../packages/fs/fs-local
'@deepseek-ai/dsh-fs-policy':
specifier: workspace:*
version: link:../packages/fs/fs-policy
'@deepseek-ai/dsh-hooks-claude':
specifier: workspace:*
version: link:../packages/hooks/hooks-claude
'@deepseek-ai/dsh-hooks-codex':
specifier: workspace:*
version: link:../packages/hooks/hooks-codex
'@deepseek-ai/dsh-llm':
specifier: workspace:*
version: link:../packages/llm/llm
'@deepseek-ai/dsh-llm-deepseek':
specifier: workspace:*
version: link:../packages/llm/llm-deepseek
'@deepseek-ai/dsh-llm-replay':
specifier: workspace:*
version: link:../packages/support/llm-replay
'@deepseek-ai/dsh-permission':
specifier: workspace:*
version: link:../packages/ui/permission
'@deepseek-ai/dsh-repeat-tool-guard':
specifier: workspace:*
version: link:../packages/guard/repeat-tool-guard
'@deepseek-ai/dsh-sandbox-local':
specifier: workspace:*
version: link:../packages/sandbox/sandbox-local
'@deepseek-ai/dsh-spill-local':
specifier: workspace:*
version: link:../packages/spill/spill-local
'@deepseek-ai/dsh-spill-policy':
specifier: workspace:*
version: link:../packages/spill/spill-policy
'@deepseek-ai/dsh-stdio-demo':
specifier: workspace:*
version: link:../packages/examples/stdio-demo
'@deepseek-ai/dsh-subagent':
specifier: workspace:*
version: link:../packages/subagent/subagent
'@deepseek-ai/dsh-subagent-fork':
specifier: workspace:*
version: link:../packages/subagent/subagent-fork
'@deepseek-ai/dsh-subagent-spawn':
specifier: workspace:*
version: link:../packages/subagent/subagent-spawn
'@deepseek-ai/dsh-time-context':
specifier: workspace:*
version: link:../packages/context/time-context
'@deepseek-ai/dsh-timeout-policy':
specifier: workspace:*
version: link:../packages/timeout/timeout-policy
'@deepseek-ai/dsh-token-meter':
specifier: workspace:*
version: link:../packages/llm/token-meter
'@deepseek-ai/dsh-tool-cordis':
specifier: workspace:*
version: link:../packages/cordis/tool-cordis
'@deepseek-ai/dsh-tool-fs':
specifier: workspace:*
version: link:../packages/fs/tool-fs
'@deepseek-ai/dsh-tool-fs-search':
specifier: workspace:*
version: link:../packages/fs/tool-fs-search
'@deepseek-ai/dsh-tool-subagent':
specifier: workspace:*
version: link:../packages/subagent/tool-subagent
'@deepseek-ai/dsh-tool-todo':
specifier: workspace:*
version: link:../packages/todo/tool-todo
'@deepseek-ai/dsh-tool-workflow':
specifier: workspace:*
version: link:../packages/workflow/tool-workflow
'@deepseek-ai/dsh-tools':
specifier: workspace:*
version: link:../packages/core/tools
'@deepseek-ai/dsh-user-approval':
specifier: workspace:*
version: link:../packages/ui/user-approval
'@deepseek-ai/dsh-web':
specifier: workspace:*
version: link:../packages/web/web
'@deepseek-ai/dsh-web-fetch-local':
specifier: workspace:*
version: link:../packages/web/web-fetch-local
'@deepseek-ai/dsh-workflow-workerthread':
specifier: workspace:*
version: link:../packages/workflow/workflow-workerthread
packages/bash/bash:
devDependencies:
'@deepseek-ai/dsh-sandbox':
@@ -293,6 +407,9 @@ importers:
'@deepseek-ai/dsh-llm':
specifier: workspace:^
version: link:../../llm/llm
'@deepseek-ai/dsh-loader-smoke':
specifier: workspace:^
version: link:../../support/loader-smoke
'@deepseek-ai/dsh-session':
specifier: workspace:^
version: link:../../core/session
@@ -1352,6 +1469,9 @@ importers:
'@deepseek-ai/dsh-llm':
specifier: workspace:^
version: link:../../llm/llm
'@deepseek-ai/dsh-loader-smoke':
specifier: workspace:^
version: link:../../support/loader-smoke
'@deepseek-ai/dsh-subagent':
specifier: workspace:^
version: link:../subagent
@@ -1532,9 +1652,9 @@ importers:
'@agentclientprotocol/sdk':
specifier: 0.25.1
version: 0.25.1(zod@4.4.3)
tsx:
specifier: ^4.22.4
version: 4.22.4
'@deepseek-ai/dsh-loader-smoke':
specifier: workspace:*
version: link:../loader-smoke
vitest:
specifier: ^4.1.8
version: 4.1.8(@types/node@25.9.3)(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))

View File

@@ -2,6 +2,13 @@ packages:
- vendor/*
- packages/*/*
- website
# The runnable demo leaves join as ONE workspace member: examples/package.json
# declares the union of every leaf's cordis.yml plugins as workspace:*, so a
# plain-node (`:lib`) boot of any leaf (examples/<leaf>/cordis.yml) resolves its
# plugins through real package `exports`→lib by walking up to examples/node_modules.
# Members for DEPENDENCY RESOLUTION only — NOT build targets: tsdown's explicit
# globs (vendor/*, packages/*/*) exclude them. See the example-execute-over-tsx RFC.
- examples
# Deploy root of the single-exe build: a pure dependency manifest whose
# closure is what the exe bundles and what the Python runtime distributes.
- python/sdk-runtime

View File

@@ -4,8 +4,8 @@
"docs/architecture.md": 1790,
"docs/cordis-primer.md": 600,
"docs/defensive-patterns.md": 550,
"docs/testing.md": 800,
"examples/AGENTS.md": 200,
"docs/testing.md": 960,
"examples/AGENTS.md": 310,
"packages/AGENTS.md": 290,
"packages/README.md": 760
}

View File

@@ -163,11 +163,13 @@ function gatesForMode(selected: Mode): Gate[] {
]
case 'ci-coverage':
return [
pnpmScript('build', 'build'),
coverageGate(),
]
case 'ci-snapshot':
return [
pnpmScript('snapshot', 'test:snapshot'),
pnpmScript('build', 'build'),
snapshotGate(),
]
case 'ci-artifacts':
return ciArtifactGates()
@@ -186,7 +188,7 @@ function gatesForMode(selected: Mode): Gate[] {
pnpmScript('cordis-config', 'verify-cordis-config', { label: 'Cordis config' }),
pnpmScript('test', 'test'),
pnpmScript('duplication', 'duplication'),
pnpmScript('snapshot', 'test:snapshot'),
snapshotGate(),
pnpmScript('build', 'build'),
...hygieneLeafGates({ artifactNeeds: ['build'] }),
...docSyncLeafGates({
@@ -207,7 +209,7 @@ function ciPrimaryGates(): Gate[] {
lintGate(),
pnpmScript('duplication', 'duplication'),
coverageGate(),
pnpmScript('snapshot', 'test:snapshot'),
snapshotGate(),
demoSmokeGate({ needs: ['lint'] }),
...docSyncLeafGates(),
pnpmScript('module-graph', 'verify-module-graph', { label: 'module graph' }),
@@ -281,6 +283,18 @@ function coverageGate(): Gate {
...positiveIntArg('DSH_COVERAGE_MAX_WORKERS', '--maxWorkers'),
], {
label: 'test:coverage',
env: { DSH_EXAMPLE_MODE: 'lib' },
needs: ['build'],
})
}
// The snapshot suite boots the example bins in `lib` mode (built artifact under plain Node,
// plugins via real exports) — CI and pre-push already build, so they exercise what ships rather
// than the tsx/source path dev uses. It therefore waits on `build`.
function snapshotGate(): Gate {
return pnpmScript('snapshot', 'test:snapshot', {
env: { DSH_EXAMPLE_MODE: 'lib' },
needs: ['build'],
})
}

View File

@@ -1,18 +1,32 @@
/**
* Reject JavaScript expressions in Cordis Loader entry metadata.
* Validate Cordis Loader entry metadata and example package resolution.
*
* The Loader interpolates only a plugin entry's `config`; expression objects in
* fields such as `disabled` remain truthy data and silently change composition.
* Example configs run from built packages, so every named package must resolve
* from the examples workspace and every local package must be in the root
* TypeScript project graph.
*/
import { globSync, readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { dirname, relative, resolve } from 'node:path'
import * as yaml from 'js-yaml'
import ts from 'typescript'
interface JsExpr {
__jsExpr: string
}
interface PackageManifest {
name?: string
dependencies?: Record<string, string>
}
interface PluginReference {
file: string
name: string
}
const root = resolve(import.meta.dirname, '..')
const metadataFields = ['id', 'name', 'group', 'disabled', 'inject', 'intercept', 'isolate'] as const
const jsExprType = new yaml.Type('tag:yaml.org,2002:js', {
@@ -30,6 +44,7 @@ const files = globSync(['**/*cordis*.yml', '**/*cordis*.yaml'], {
exclude: ['.claude/**', 'node_modules/**', 'vendor/**'],
}).sort()
const errors: string[] = []
const examplePluginReferences: PluginReference[] = []
for (const file of files) {
const document: unknown = yaml.load(readFileSync(resolve(root, file), 'utf8'), { schema })
@@ -42,8 +57,10 @@ for (const file of files) {
}
}
errors.push(...validateExampleResolution())
if (errors.length > 0) {
console.error('verify-cordis-config: Loader entry metadata is static; move !!js under plugin config or select an explicit overlay.')
console.error('verify-cordis-config: invalid Loader metadata or example package resolution:')
for (const error of errors) console.error(`- ${error}`)
process.exitCode = 1
} else {
@@ -55,6 +72,7 @@ function validateEntry(value: unknown, file: string, path: string): void {
errors.push(`${file}${path}: entry must be an object`)
return
}
recordExamplePlugin(value, file)
validateMetadata(value, file, path)
if ((value.group === true || value.name === '@cordisjs/plugin-group') && isUnknownArray(value.config)) {
for (let index = 0; index < value.config.length; index++) {
@@ -68,6 +86,7 @@ function validateEntry(value: unknown, file: string, path: string): void {
const patch = config.patches[index]
const patchPath = `${path}.config.patches[${index}]`
if (!isRecord(patch)) continue
recordExamplePlugin(patch, file)
validateMetadata(patch, file, patchPath)
if (!isUnknownArray(patch.insert)) continue
for (let insertIndex = 0; insertIndex < patch.insert.length; insertIndex++) {
@@ -76,6 +95,83 @@ function validateEntry(value: unknown, file: string, path: string): void {
}
}
function recordExamplePlugin(entry: Record<string, unknown>, file: string): void {
if (file.startsWith('examples/') && typeof entry.name === 'string') {
examplePluginReferences.push({ file, name: entry.name })
}
}
function validateExampleResolution(): string[] {
const violations: string[] = []
const exampleManifest = readManifest('examples/package.json')
const dependencies = exampleManifest.dependencies ?? {}
const localPackages = localPackageDirectories()
const rootReferences = rootProjectReferences()
const requiredPackages = new Map<string, Set<string>>()
for (const reference of examplePluginReferences) {
const packageName = packageNameFromSpecifier(reference.name)
if (packageName === undefined) continue
const locations = requiredPackages.get(packageName) ?? new Set<string>()
locations.add(reference.file)
requiredPackages.set(packageName, locations)
}
for (const [packageName, locations] of requiredPackages) {
if (!(packageName in dependencies)) {
violations.push(`${[...locations].join(', ')}: ${packageName} must be declared in examples/package.json dependencies`)
}
}
const localExamplePackages = new Set([
...Object.keys(dependencies),
...requiredPackages.keys(),
])
for (const packageName of localExamplePackages) {
const packageDirectory = localPackages.get(packageName)
if (packageDirectory === undefined || rootReferences.has(packageDirectory)) continue
const repoPath = relative(root, packageDirectory).replaceAll('\\', '/')
violations.push(`tsconfig.json: missing project reference for ${packageName} (${repoPath})`)
}
return violations
}
function readManifest(path: string): PackageManifest {
return JSON.parse(readFileSync(resolve(root, path), 'utf8')) as PackageManifest
}
function localPackageDirectories(): Map<string, string> {
const manifests = globSync(['packages/*/*/package.json', 'vendor/*/package.json'], { cwd: root })
const packages = new Map<string, string>()
for (const manifestPath of manifests) {
const manifest = readManifest(manifestPath)
if (manifest.name !== undefined) packages.set(manifest.name, resolve(root, dirname(manifestPath)))
}
return packages
}
function rootProjectReferences(): Set<string> {
const config = ts.readConfigFile(resolve(root, 'tsconfig.json'), path => ts.sys.readFile(path))
if (config.error !== undefined) {
throw new Error(ts.flattenDiagnosticMessageText(config.error.messageText, '\n'))
}
const references = (config.config as { references?: Array<{ path?: unknown }> }).references ?? []
return new Set(references.flatMap((reference) => {
if (typeof reference.path !== 'string') return []
return [resolve(root, reference.path)]
}))
}
function packageNameFromSpecifier(specifier: string): string | undefined {
if (specifier.startsWith('.') || specifier.startsWith('/') || specifier.startsWith('file:')) return undefined
const segments = specifier.split('/')
if (specifier.startsWith('@')) {
return segments.length >= 2 ? `${segments[0]}/${segments[1]}` : undefined
}
return segments[0] || undefined
}
function validateMetadata(entry: Record<string, unknown>, file: string, path: string): void {
for (const field of metadataFields) {
if (!(field in entry)) continue

View File

@@ -25,20 +25,19 @@ Check token pressure and compact if the conversation is too large. Estimate the
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/compact/compact/src/index.ts#L58)
### ctx.compact.compactRegion(session, start, end, agent, signal?)
### ctx.compact.compactRegion(start, end, agent, signal?)
```ts website-api
abstract compactRegion( session: Session, start: number, end: number, agent: CompactAgentContext, signal?: AbortSignal, ): Promise<CompactionResult>
abstract compactRegion( start: number, end: number, agent: CompactAgentContext, signal?: AbortSignal, ): Promise<CompactionResult>
```
Forcibly compact a range of surface nodes into a single summary node. `start` and `end` name an inclusive span by surface position, not numeric seq order; replacements can make visible seqs non-monotonic. Both edges must be balanced so assistant tool calls remain paired with their results. A model- backed implementation forwards cancellation. The agent must own the exact target session object; implementations reject an ownership mismatch before model resolution, lock acquisition, summarization, or log mutation, and reject active, missing, reversed, or unbalanced ranges. Use toolPairingBalancedBefore and toolPairingBalancedAfter for the edge checks.
Forcibly compact a range of surface nodes into a single summary node. `start` and `end` name an inclusive span by surface position, not numeric seq order; replacements can make visible seqs non-monotonic. Both edges must be balanced so assistant tool calls remain paired with their results. A model- backed implementation forwards cancellation and rejects active, missing, reversed, or unbalanced ranges. The target session is `agent.session`. Use toolPairingBalancedBefore and toolPairingBalancedAfter for the edge checks.
- `session` — session to mutate; must be identical to `agent.session`.
- `start` — first surface seq, inclusive.
- `end` — last surface seq, inclusive.
- `agent` — owner of the target session and summarizer context.
- `agent` — context whose session is mutated and whose routing options guide summarization.
- `signal` — optional cancellation; model-backed implementations must forward it.
**Returns** the replaced range and summary.
**Returns** the appended event seqs, summary, replaced range, and token accounting.
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/compact/compact/src/index.ts#L85)
[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/compact/compact/src/index.ts#L82)