mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Merge remote-tracking branch 'origin/master' into xtr/react-loop-simplification
# Conflicts: # .agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.i18n.yaml # .agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.md # .agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.zh.md # .agents/notes/implemented/feature/2026-07-21-tui-skill-slash-command.i18n.yaml # .agents/notes/implemented/feature/2026-07-21-tui-skill-slash-command.md # .agents/notes/implemented/feature/2026-07-21-tui-skill-slash-command.zh.md # .agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.i18n.yaml # .agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.md # .agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.zh.md # docs/architecture.i18n.yaml # docs/cordis-catalog/events.md # docs/cordis-catalog/services.md # docs/core-data-structures/core.i18n.yaml # docs/core-data-structures/core.md # docs/core-data-structures/core.zh.md # docs/defensive-patterns.i18n.yaml # packages/client/runtime/src/client/sessions/session.ts # packages/client/runtime/tests/queue-store.spec.ts # packages/context/time-context/tests/time-context.spec.ts # packages/context/workspace-context/tests/workspace-context.spec.ts # packages/cordis/tool-cordis/src/api-catalog.ts # packages/core/agent-loop/README.i18n.yaml # packages/core/agent-loop/README.md # packages/core/agent-loop/README.zh.md # packages/core/agent-loop/src/agent.ts # packages/core/agent/README.i18n.yaml # packages/core/agent/README.md # packages/core/agent/README.zh.md # packages/core/agent/src/types.ts # packages/core/agent/tests/agent.spec.ts # packages/core/scope/src/scoped-events.generated.ts # packages/goal/command-goal/tests/command-goal.spec.ts # packages/goal/goal-session/src/index.ts # packages/goal/goal-session/tests/goal-session.spec.ts # packages/goal/goal/tests/goal.spec.ts # packages/goal/goal/tests/projection.spec.ts # packages/goal/tool-goal/tests/tool-goal.spec.ts # packages/host/apiproxy/src/api-proxy.ts # packages/host/apiproxy/src/api/events.schema.ts # packages/host/apiproxy/src/api/events.ts # packages/host/apiproxy/tests/api-proxy-workspace.spec.ts # packages/llm/llm/README.i18n.yaml # packages/llm/llm/README.zh.md # packages/llm/llm/src/index.ts # packages/pty/pty-local/tests/index.spec.ts # packages/pty/pty-local/tests/local.spec.ts # packages/pty/pty/tests/service.spec.ts # packages/pty/tool-pty/tests/loader-composition.spec.ts # packages/pty/tool-pty/tests/tools.spec.ts # packages/skill/tool-skill/tests/tool-skill.spec.ts # packages/tasks/tasks-local/tests/tasks.spec.ts # packages/ui/tui/src/index.ts # packages/ui/tui/tests/harness.ts # packages/ui/tui/tests/tui.spec.ts # scripts/gen-cordis-catalog.ts # scripts/type-equiv.manifest.json
This commit is contained in:
@@ -1,6 +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
|
||||
# pnpm run verify-translation-pairing --write packages/support/README.md
|
||||
README.md: b9550fd54feb36448227faae8485fe8b6dbf4fb0
|
||||
README.zh.md: dc017c5f6544e29533f14600deb5b9261e351536
|
||||
README.zh.md: 8eec9b96f621724e1ddc9a70abed069f11cb7fdc
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
这些包用于开发、测试和示例,而非作为产品 API 发布。它们是真实工作区包(有类型、经过测试、受覆盖率门禁约束),但具有**较低的兼容性预期**:当其背后的开发需求变化时,它们可以改变或被移除,无需像产品包那样谨慎执行弃用流程。
|
||||
这些包(package)用于开发、测试和示例,而非作为产品 API 发布。它们是实际的工作区包(具备类型、经过测试,并受覆盖率门禁约束),但具有**较低的兼容性预期**:当其背后的开发需求变化时,它们可以改变或被移除,无需像产品包那样谨慎执行弃用流程。
|
||||
|
||||
| 包 | 职责 | ctx 键 |
|
||||
|---|---|---|
|
||||
| `acp-snapshot/` | ACP 测试工具包:共享子进程/客户端启动器、快照 harness、规范化器和套件工厂 | (库:由 ACP e2e 和 `*.snapshot.ts` 套件导入) |
|
||||
| `agent-loop-testkit/` | 为测试具体 agent loop 的测试挂载共享先决条件 | (库:由 AgentLoop 集成测试导入) |
|
||||
| `acp-snapshot/` | ACP(Agent Client Protocol)测试工具包:共享子进程/客户端启动器、快照 harness、规范化器和套件工厂 | (库:由 ACP e2e 和 `*.snapshot.ts` 套件导入) |
|
||||
| `agent-loop-testkit/` | 为验证具体 agent loop(智能体循环)的测试挂载共享先决条件 | (库:由 AgentLoop 集成测试导入) |
|
||||
| `invariants/` | 用于开发诊断的运行时事件契约断言 | (监听 `session/*`、`agent/*`) |
|
||||
| `loader-smoke/` | 共享的真实 Loader 子进程 harness,用于无密钥示例冒烟测试 | (库:由示例 e2e 套件导入) |
|
||||
| `llm-mock-server/` | 可编脚本的 OpenAI 兼容 HTTP/SSE 故障服务器与 CLI,用于 LLM 恢复测试 | (独立服务器和测试库) |
|
||||
| `llm-replay/` | 录制/回放适配器:从已记录的会话 JSONL 短路 `llm/stream`(无密钥快照测试) | (监听 `llm/stream`) |
|
||||
| `llm-mock-server/` | 可编程的 OpenAI 兼容 HTTP/SSE(Server-Sent Events)故障服务器与 CLI(命令行界面),用于 LLM(大语言模型)恢复测试 | (独立服务器和测试库) |
|
||||
| `llm-replay/` | 录制/回放适配器:通过已记录的会话 JSONL 对 `llm/stream` 进行短路处理(无密钥快照测试) | (监听 `llm/stream`) |
|
||||
|
||||
`invariants` 是开发支持,但没有环境保护:无论在何处注册,它都会运行;默认 `dsh-agent-spine-demo` bundle 无条件挂载它。`agent-loop-testkit` 为手工构建的 AgentLoop 测试集中管理必需服务主干,而不负责其 loop 或场景。`llm-replay` 支撑演示和受每文件覆盖率门禁约束的快照测试层,`llm-mock-server` 则通过确定性 HTTP/SSE 故障驱动真实提供方适配器。`acp-snapshot` 包含 ACP 子进程/客户端边界以及快照 harness、规范化器和套件机制,`loader-smoke` 负责无密钥示例 e2e 套件使用的并列真实 Loader 启动边界。只有当某个包获得已记录的产品消费方时,它才会从 `support/` 升级到产品分组。
|
||||
`invariants` 是开发支持,但没有环境条件限制:无论在何处注册,它都会运行;默认 `dsh-agent-spine-demo` bundle 无条件挂载它。`agent-loop-testkit` 为手工构建的 AgentLoop 测试集中管理必需服务主干,而不负责其 agent loop 或场景。`llm-replay` 支撑演示和受逐文件覆盖率门禁约束的快照测试层,`llm-mock-server` 则通过确定性 HTTP/SSE 故障驱动真实提供方适配器。`acp-snapshot` 包含 ACP 子进程/客户端边界以及快照 harness、规范化器和套件机制,`loader-smoke` 负责无密钥示例 e2e 套件使用的并列真实 Loader 启动边界。只有当某个包获得文档记载的产品消费方时,它才会从 `support/` 转入产品分组。
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# 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 packages/support/acp-snapshot/README.md
|
||||
README.md: 43666d2d117170f9d7f73737fb1704efc2a55f27
|
||||
README.zh.md: 608b2490f5de7dc7ec4ecd863f41df7f609451ac
|
||||
README.md: 948c33a91977f078d16842c285011bf8f83623bd
|
||||
README.zh.md: fb86bd4e236be1c79f66dc46fbaac4d7dfbf9977
|
||||
|
||||
@@ -51,9 +51,11 @@ defineAcpSnapshotSuite({
|
||||
})
|
||||
```
|
||||
|
||||
A scenario booting a differently-composed tree sets its own `configPath` (an overlay whose basename still ends in `cordis.yml`, so the bin's replay swap finds the sibling `*cordis.snapshot.yml`) and, when that composition changes the request header, its own `headerClass` with its own pinning scenario — the acp-agent example's Code Mode and filesystem scenarios are templates. Default generated workspaces are stored in session fixtures as `{{cwd}}` so platform temp roots and random basenames do not affect recordings; `workspaceParent` moves the generated cwd outside the platform temp area when temporary-directory grants are themselves under test, keeps that explicit path in the fixture, and remains parent-owned while the harness removes only the generated child. A pin owns its generated `system-prompt.expected.md` or `tool-schemas.expected.json` by default; `systemPromptSource` and `toolSchemasSource` name another pin when the complete corresponding sequence is identical, so each distinct version is committed once. The pin's `session.jsonl` stores `"system":"{{system}}","tools":"{{tools}}"` while retaining config, reason, and any model-visible prefix. A pin with legitimate mid-run header changes declares `expectedHeaderChanges`; a shared source must declare the same count, and record/refresh rejects claimants that generate different bytes.
|
||||
A scenario booting a differently-composed tree sets its own `configPath` (an overlay whose basename still ends in `cordis.yml`, so the bin's replay swap finds the sibling `*cordis.snapshot.yml`) and, when that composition changes the request header, its own `headerClass` with its own pinning scenario — the acp-agent example's Code Mode and filesystem scenarios are templates. Default generated workspaces are stored in session fixtures as `{{cwd}}` so platform temp roots and random basenames do not affect recordings; `workspaceParent` moves the generated cwd outside the platform temp area when temporary-directory grants are themselves under test, keeps that explicit path in the fixture, and remains parent-owned while the harness removes only the generated child. A scenario's committed `workspace/` is copied into that child first, then `prepareWorkspace` runs against the generated cwd before the agent starts. Reserve this hook for fixtures Git cannot represent portably, keep ordinary seeds in `workspace/`, and pair it with `posixOnly` when the generated paths are invalid on Windows.
|
||||
|
||||
Every scenario compares `stdout.expected.jsonl` with cwd-rooted separators canonicalized to `/`. On Windows, `pinsNativeWindowsStdout` additionally compares the complete `stdout.expected.windows.jsonl` after the shared expected output and requires that sidecar exactly when enabled. A scenario whose driven behavior needs POSIX process semantics (e.g. cancelling a live bash call kills a detached process group) declares `posixOnly`, which skips its run test on Windows while the fixture guards keep covering its committed files everywhere.
|
||||
A pin owns its generated `system-prompt.expected.md` or `tool-schemas.expected.json` by default; `systemPromptSource` and `toolSchemasSource` name another pin when the complete corresponding sequence is identical, so each distinct version is committed once. The pin's `session.jsonl` stores `"system":"{{system}}","tools":"{{tools}}"` while retaining config, reason, and any model-visible prefix. A pin with legitimate mid-run header changes declares `expectedHeaderChanges`; a shared source must declare the same count, and record/refresh rejects claimants that generate different bytes.
|
||||
|
||||
Every scenario compares `stdout.expected.jsonl` with cwd-rooted separators canonicalized to `/`. On Windows, `pinsNativeWindowsStdout` additionally compares the complete `stdout.expected.windows.jsonl` after the shared expected output and requires that sidecar exactly when enabled. A scenario requiring a non-Windows host declares `posixOnly`, which skips its run test on Windows while the fixture guards keep covering its committed files everywhere; examples include POSIX process semantics (e.g. cancelling a live bash call kills a detached process group) and generated paths Windows cannot represent.
|
||||
|
||||
The example also ships a `cordis.snapshot.yml` replay overlay next to its `cordis.yml` (the bin swaps them under `DSH_SNAPSHOT=replay` — [single-source replay config Agent Note](../../../.agents/notes/archived/testing/2026-07-04-single-source-acp-replay-config.md)); replay fixtures are served by [`dsh-llm-replay`](../llm-replay/README.md), which this package points at via the `DSH_SNAPSHOT_*` env vars it sets on the child. `pnpm run test:snapshot:record` calls the live LLM and rewrites the recorded scenarios' model fixtures; `pnpm run test:snapshot:refresh` stays keyless, runs the replay overlay, and rewrites stdout, comparable session-log expected outputs, and owned prompt and tool-schema sidecars from the committed model scripts. Fixture roles, record/replay/refresh semantics, and scenario-table fields are documented on `Scenario` and in the [snapshot Agent Note](../../../.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md).
|
||||
|
||||
|
||||
@@ -51,9 +51,11 @@ defineAcpSnapshotSuite({
|
||||
})
|
||||
```
|
||||
|
||||
启动不同组合树的场景会设置自己的 `configPath`(一个 basename 仍以 `cordis.yml` 结尾的 overlay,使 bin 的回放交换可找到同级 `*cordis.snapshot.yml`);当该组合改变请求 header 时,还会设置自己的 `headerClass` 和 pin 场景,acp-agent 示例的 Code Mode 与文件系统场景是模板。默认生成的 workspace 在会话 fixture 中存储为 `{{cwd}}`,使平台临时根目录和随机 basename 不影响录制结果;当临时目录授权自身待测时,`workspaceParent` 将生成 cwd 移出平台临时区域,在 fixture 中保留该显式路径,并仍归父级所有,而 harness 只移除生成的子级。每个 pin 默认拥有其生成的 `system-prompt.expected.md` 或 `tool-schemas.expected.json`;当完整的对应序列相同时,`systemPromptSource` 和 `toolSchemasSource` 指定另一个 pin 作为来源,因此每个不同版本只提交一次。该 pin 的 `session.jsonl` 存储 `"system":"{{system}}","tools":"{{tools}}"`,同时保留配置、原因和任何模型可见前缀。具有合法运行中 header 变更的 pin 声明 `expectedHeaderChanges`;共享来源必须声明相同的 header 变更数量,录制/刷新会拒绝生成不同字节的共享引用方。
|
||||
启动不同组合树的场景会设置自己的 `configPath`(一个 basename 仍以 `cordis.yml` 结尾的 overlay,使 bin 的回放交换可找到同级 `*cordis.snapshot.yml`);当该组合改变请求 header 时,还会设置自己的 `headerClass` 和 pin 场景,acp-agent 示例的 Code Mode 与文件系统场景是模板。默认生成的 workspace 在会话 fixture 中存储为 `{{cwd}}`,使平台临时根目录和随机 basename 不影响录制结果;当临时目录授权自身待测时,`workspaceParent` 将生成 cwd 移出平台临时区域,在 fixture 中保留该显式路径,并仍归父级所有,而 harness 只移除生成的子级。场景签入的 `workspace/` 会先复制到该子级,随后 `prepareWorkspace` 在 agent 启动前针对生成 cwd 运行。此 hook 仅用于 Git 无法跨平台表示的 fixture;普通种子应留在 `workspace/` 中,而生成路径在 Windows 上无效时还必须搭配 `posixOnly`。
|
||||
|
||||
每个场景都比较 `stdout.expected.jsonl`,其中以 cwd 为根的分隔符规范化为 `/`。在 Windows 上,`pinsNativeWindowsStdout` 还会在共享预期输出之后比较完整 `stdout.expected.windows.jsonl`,并在启用时精确要求该 sidecar。驱动行为需要 POSIX 进程语义的场景(例如取消实时 bash 调用会终止脱离进程组)声明 `posixOnly`,在 Windows 上跳过运行测试,但 fixture 保护仍在所有平台覆盖其已提交文件。
|
||||
每个 pin 默认拥有其生成的 `system-prompt.expected.md` 或 `tool-schemas.expected.json`;当完整的对应序列相同时,`systemPromptSource` 和 `toolSchemasSource` 指定另一个 pin 作为来源,因此每个不同版本只提交一次。该 pin 的 `session.jsonl` 存储 `"system":"{{system}}","tools":"{{tools}}"`,同时保留配置、原因和任何模型可见前缀。具有合法运行中 header 变更的 pin 声明 `expectedHeaderChanges`;共享来源必须声明相同的 header 变更数量,录制/刷新会拒绝生成不同字节的共享引用方。
|
||||
|
||||
每个场景都比较 `stdout.expected.jsonl`,其中以 cwd 为根的分隔符规范化为 `/`。在 Windows 上,`pinsNativeWindowsStdout` 还会在共享预期输出之后比较完整 `stdout.expected.windows.jsonl`,并在启用时精确要求该 sidecar。需要非 Windows 主机的场景声明 `posixOnly`,在 Windows 上跳过运行测试,但 fixture 保护仍在所有平台覆盖其已提交文件;示例包括 POSIX 进程语义(例如取消实时 bash 调用会终止脱离进程组)和 Windows 无法表示的生成路径。
|
||||
|
||||
示例还发布 `cordis.snapshot.yml` 回放 overlay,位于 `cordis.yml` 旁边(bin 在 `DSH_SNAPSHOT=replay` 下交换它们,见[单源回放配置 Agent Note](../../../.agents/notes/archived/testing/2026-07-04-single-source-acp-replay-config.md));回放 fixture 由 [`dsh-llm-replay`](../llm-replay/README.md) 提供,该包通过对子级设置的 `DSH_SNAPSHOT_*` env var 指向它。`pnpm run test:snapshot:record` 调用实时 LLM,并重写已记录场景的模型 fixture;`pnpm run test:snapshot:refresh` 保持无密钥,运行回放 overlay,并从已提交模型脚本重写 stdout、可比较会话日志预期输出,以及各 pin 自有的提示词与工具 schema sidecar。Fixture 角色、录制/回放/刷新语义和场景表字段记录在 `Scenario` 以及[快照 Agent Note](../../../.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md) 中。
|
||||
|
||||
|
||||
@@ -155,6 +155,13 @@ export interface RunOptions {
|
||||
* start from an empty workspace.
|
||||
*/
|
||||
workspaceDir?: string
|
||||
/**
|
||||
* Optional final workspace preparation, run after {@link workspaceDir} is
|
||||
* copied and before the agent starts. This is for fixtures that cannot be
|
||||
* represented portably in Git (for example, a POSIX-only filename that is
|
||||
* invalid on Windows); ordinary seeded files belong in `workspaceDir`.
|
||||
*/
|
||||
prepareWorkspace?: (cwd: string) => void | Promise<void>
|
||||
/**
|
||||
* Parent directory for the generated session cwd. Defaults to
|
||||
* `os.tmpdir()`. A scenario that must distinguish its workspace from the
|
||||
@@ -221,6 +228,7 @@ export async function runScenario(input: InputScript, opts: RunOptions): Promise
|
||||
if (opts.workspaceDir !== undefined && existsSync(opts.workspaceDir)) {
|
||||
await cp(opts.workspaceDir, cwd, { recursive: true })
|
||||
}
|
||||
await opts.prepareWorkspace?.(cwd)
|
||||
const env: NodeJS.ProcessEnv = {
|
||||
...opts.env,
|
||||
DSH_SNAPSHOT: opts.mode,
|
||||
|
||||
@@ -130,6 +130,12 @@ export interface Scenario {
|
||||
* test and the scenario needs an independent project location.
|
||||
*/
|
||||
workspaceParent?: string
|
||||
/**
|
||||
* Optional final workspace preparation after the committed fixture is
|
||||
* copied. Reserve this for paths that Git cannot represent portably; normal
|
||||
* scenario files belong under the scenario's `workspace/` directory.
|
||||
*/
|
||||
prepareWorkspace?: (cwd: string) => void | Promise<void>
|
||||
/**
|
||||
* Whether Windows additionally compares stdout with native separators against
|
||||
* `stdout.expected.windows.jsonl`. The shared canonical stdout expected output is still
|
||||
@@ -138,10 +144,9 @@ export interface Scenario {
|
||||
*/
|
||||
pinsNativeWindowsStdout?: boolean
|
||||
/**
|
||||
* Whether the driven behavior needs POSIX process semantics the harness
|
||||
* cannot exercise on Windows (e.g. cancelling a live bash tool call kills a
|
||||
* detached process group). The scenario's run test is skipped on Windows;
|
||||
* its fixtures stay guarded on every platform.
|
||||
* Whether the scenario requires a non-Windows host, such as for POSIX process
|
||||
* semantics or generated paths Windows cannot represent. The scenario's run
|
||||
* test is skipped on Windows; its fixtures stay guarded on every platform.
|
||||
*/
|
||||
posixOnly?: boolean
|
||||
}
|
||||
@@ -956,8 +961,7 @@ export function defineAcpSnapshotSuite(options: SnapshotSuiteOptions): void {
|
||||
scenarioSuite('snapshot scenarios', () => {
|
||||
for (const scenario of scenarios) {
|
||||
// In RECORD mode, only re-run the `recorded` (live-API) scenarios; the `authored` ones
|
||||
// (sidecar-driven errors/cancel) are never re-recorded. `posixOnly` scenarios skip on
|
||||
// Windows, where their process semantics cannot be driven.
|
||||
// (sidecar-driven errors/cancel) are never re-recorded. `posixOnly` scenarios skip on Windows.
|
||||
it.skipIf(scenarioSkipped(scenario, RECORDING))(`snapshot: ${scenario.name} matches the expected outputs`, async ({ expect }) => {
|
||||
const dir = join(snapshotsDir, scenario.name)
|
||||
const input = JSON.parse(await readFile(join(dir, 'input.json'), 'utf8')) as InputScript
|
||||
@@ -980,6 +984,7 @@ export function defineAcpSnapshotSuite(options: SnapshotSuiteOptions): void {
|
||||
// replays from its own script. In RECORD they are harvested, not read.
|
||||
...!RECORDING && childFixtureFiles.length > 0 ? { childFiles: childFixtureFiles.map(file => join(dir, file)) } : {},
|
||||
...existsSync(workspaceDir) ? { workspaceDir } : {},
|
||||
...scenario.prepareWorkspace !== undefined ? { prepareWorkspace: scenario.prepareWorkspace } : {},
|
||||
...scenario.workspaceParent !== undefined ? { workspaceParent: scenario.workspaceParent } : {},
|
||||
// A scenario booting an overlay tree passes its own live config; the
|
||||
// bin's replay swap derives the sibling `*cordis.snapshot.yml` from it.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
|
||||
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
|
||||
import { once } from 'node:events'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { delimiter, join, relative, sep } from 'node:path'
|
||||
@@ -468,6 +468,30 @@ describe('runScenario', () => {
|
||||
expect(result.rawStdout).toContain('workspace:seeded.txt')
|
||||
})
|
||||
|
||||
it('prepares the generated workspace after copying committed fixtures', { timeout: 20_000 }, async () => {
|
||||
const { dir, fixtureFile } = await scenario({ echoWorkspace: true })
|
||||
const workspaceDir = join(dir, 'workspace')
|
||||
const { mkdir } = await import('node:fs/promises')
|
||||
await mkdir(workspaceDir, { recursive: true })
|
||||
await writeFile(join(workspaceDir, 'committed.txt'), 'committed')
|
||||
|
||||
const result = await runScenario(
|
||||
{ steps: [...boot, { op: 'prompt', text: 'ls' }] },
|
||||
{
|
||||
agent: AGENT,
|
||||
mode: 'replay',
|
||||
fixtureFile,
|
||||
workspaceDir,
|
||||
prepareWorkspace: async (cwd) => {
|
||||
expect(await readFile(join(cwd, 'committed.txt'), 'utf8')).toBe('committed')
|
||||
await writeFile(join(cwd, 'runtime.txt'), 'runtime')
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
expect(result.rawStdout).toContain('workspace:committed.txt,runtime.txt')
|
||||
})
|
||||
|
||||
it('creates the generated workspace under an explicit parent', { timeout: 20_000 }, async () => {
|
||||
const { fixtureFile } = await scenario({})
|
||||
const workspaceParent = await mkdtemp(join(tmpdir(), 'acp-snap-parent-'))
|
||||
|
||||
@@ -78,6 +78,9 @@ const REPLAY_SCENARIOS: Scenario[] = [
|
||||
env: { DSH_PERMISSION_MODE: 'never' },
|
||||
configPath: AGENT.configPath,
|
||||
workspaceParent: tmpdir(),
|
||||
prepareWorkspace: (cwd) => {
|
||||
writeFileSync(join(cwd, 'seed.txt'), 'prepared at runtime')
|
||||
},
|
||||
},
|
||||
{ name: 'no-model', hasModelTurn: false, recorded: false, headerClass: 'main' },
|
||||
{ name: 'blocked-log', hasModelTurn: false, comparesLog: true, recorded: false, headerClass: 'main' },
|
||||
|
||||
@@ -1,6 +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
|
||||
# pnpm run verify-translation-pairing --write packages/support/agent-loop-testkit/README.md
|
||||
README.md: 18c46069d3cfd402c83b5ecab68458667738163b
|
||||
README.zh.md: 93af58a279558007c252ef0734cc95b2ad79a5a5
|
||||
README.zh.md: 2151591154899094fe33f9c25c9b9144e6d02a22
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
为测试具体 `AgentLoop` 的测试挂载共享先决条件。`mountAgentLoopTestDependencies(ctx, options?)` 按依赖顺序安装 LLM、会话、系统提示词、工具和 agent 服务,然后在 loop 挂载前返回。
|
||||
为运行具体 `AgentLoop` 的测试共享挂载先决依赖。`mountAgentLoopTestDependencies(ctx, options?)` 按依赖顺序安装 LLM(大语言模型)、会话、系统提示词、工具和 agent(智能体)服务,然后在 agent loop 挂载前返回。
|
||||
|
||||
调用方注册适配器和可选插件,使用待测配置挂载 `AgentLoop`,并 dispose 自己的 Context。系统提示词和工具注册表配置可通过 `options` 转发;辅助工具不提供超出服务自有默认值的测试默认值。插件加载失败会拒绝辅助工具调用,而顺序中较早激活的服务仍归调用方的 Context 所有。
|
||||
调用方注册适配器和可选插件,使用待测配置挂载 `AgentLoop`,并 dispose(资源释放)自己的 Context。系统提示词和工具注册表配置可通过 `options` 转发;该辅助函数不提供超出服务自有默认值的测试默认值。插件加载失败会使辅助函数调用被拒绝,而顺序中较早激活的服务仍归调用方的 Context 所有。
|
||||
|
||||
```ts
|
||||
import { Context } from 'cordis'
|
||||
@@ -18,16 +18,16 @@ await mountAgentLoopTestDependencies(ctx)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
```
|
||||
|
||||
针对注入失败、部分拓扑、服务加载顺序或服务拆卸的测试会直接挂载其依赖,而不使用此辅助工具。
|
||||
针对注入失败、部分拓扑、服务加载顺序或服务清理的测试会直接挂载其依赖,而不使用此辅助函数。
|
||||
|
||||
## 模型体验
|
||||
|
||||
无。该测试专用组合辅助工具既不驱动也不修改模型请求。
|
||||
|
||||
#### KV 缓存影响
|
||||
#### KV Cache 影响
|
||||
|
||||
无;该包既不组装也不发送提供方请求。
|
||||
|
||||
## 已知限制与待完成工作
|
||||
## 已知限制与暂缓事项
|
||||
|
||||
- **只共享必需的先决主干**:适配器、可选插件、`AgentLoop`、agent 和 Context 拆卸仍由调用方负责,以使场景专用顺序保持可见。
|
||||
- **只共享必需的先决主干**:适配器、可选插件、`AgentLoop`、agent 和 Context 清理仍由调用方负责,以使特定场景的挂载顺序清晰可见。
|
||||
|
||||
@@ -1,6 +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
|
||||
# pnpm run verify-translation-pairing --write packages/support/invariants/README.md
|
||||
README.md: 203dbd5ad09f5b1378061fbf9adcff885889eae2
|
||||
README.zh.md: e101a30046c6b1ac18870c268ea6a2c960cb5d90
|
||||
README.zh.md: 841e7a32f8e2605c25aadef35862a0223046933f
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
用于包自有运行时不变量检查的可配置注册表服务。根插件注册 `ctx.invariants`;它不包含产品检查或产品包导入。每个工作区包都发布一个 `./invariant` 配套入口,用于注册其精确 NPM 包名。
|
||||
用于包(package)自有运行时不变量检查的可配置注册表服务。根插件注册 `ctx.invariants`;它不包含产品检查或产品包导入。每个工作区包都发布一个 `./invariant` 配套入口,用于注册其精确 NPM 包名。
|
||||
|
||||
## 服务:`InvariantService`(`ctx.invariants`)
|
||||
|
||||
@@ -16,19 +16,19 @@ interface Config {
|
||||
|
||||
默认值为 `enabled: true`、`package_allowlist: []` 和 `package_blocklist: []`。只有在服务启用、allowlist 为空或至少一个 allowlist pattern 匹配完整 NPM 名称,且没有 blocklist pattern 匹配时,包才被选中。因此,blocklist 匹配优先于 allowlist 匹配。
|
||||
|
||||
每个条目都是区分大小写的 JavaScript 正则表达式源,使用 `new RegExp(pattern)` 编译。除非源提供 `^` 和 `$`,否则匹配不锚定;不解析 `/pattern/flags` 语法。同一列表中的空白、带前后空白、无效或重复条目会使服务启动失败。有效 pattern 可以不匹配任何当前已加载包,以使后续加载和 HMR 保持确定性。
|
||||
每个条目都是区分大小写的 JavaScript 正则表达式源,使用 `new RegExp(pattern)` 编译。除非源提供 `^` 和 `$`,否则匹配不锚定;不解析 `/pattern/flags` 语法。同一列表中的空白、带前后空白、无效或重复条目会使服务启动失败。有效 pattern 可以不匹配任何当前已加载包,以使后续加载和 HMR(热模块替换)保持确定性。
|
||||
|
||||
`ctx.invariants.register(packageName, installer)` 为完整 NPM 包名保留一个活动注册,即使过滤器使其 installer 保持非活动,并返回 disposer。已启用贡献在专用子 Cordis fiber 中运行。Installer 可以通过 `installer.inject` 声明所需服务接口,并收到 `fail(message)`;后者抛出绑定到注册包的 `InvariantError`。在注册成功前,系统会等待同步或异步 installer 完成;失败会 dispose 子级,并原子释放归属。
|
||||
`ctx.invariants.register(packageName, installer)` 为完整 NPM 包名保留一个活动注册,即使过滤器使其 installer 保持非活动,并返回 disposer。已启用贡献在专用子 Cordis fiber 中运行。installer 可以通过 `installer.inject` 声明所需服务接口,并收到 `fail(message)`;后者抛出绑定到注册包的 `InvariantError`。在注册成功前,系统会等待同步或异步 installer 完成;失败会原子地 dispose(资源释放)子级并释放归属。
|
||||
|
||||
服务拥有每个注册 fiber,返回的 disposer 同时属于配套 fiber。卸载任一侧都会移除监听器、跟踪状态和保留。因此,配套入口可以重新加载并注册同一包名,而不保留旧状态。由会话支撑的配套入口从持久事件重建 baseline;仅实时配套入口观察重新加载后开始的操作。
|
||||
|
||||
`InvariantError` 扩展 `Error`,携带稳定 `code: 'INVARIANT'`,并公开所属 `packageName`,而不向服务添加产品依赖。
|
||||
|
||||
在每个组合中,Session 自身负责不可变且接口有效的日志存储:它对每个候选项制作一份无损 JSON 快照,验证完整来源和位置替换,将 `tool/result` 替换限制为一个当前结果的 `content`,深度冻结已接受记录,并通过不可变数组快照公开日志。`dsh-session` 不变量配套入口检查 Session 不负责的其余跨记录规则。
|
||||
在每个组合中,Session 自身负责不可变且通过对外接口校验的日志存储:它对每个候选项制作一份无损 JSON 快照,验证完整来源和位置替换,将 `tool/result` 替换限制为一个当前结果的 `content`,深度冻结已接受记录,并通过不可变数组快照公开日志。`dsh-session` 不变量配套入口检查 Session 不负责的其余跨记录规则。
|
||||
|
||||
## 包配套入口
|
||||
|
||||
发布和注册覆盖全部包;运行时断言刻意不使用合成内容。只有当包拥有可观察事件关系或相关可变数据关系时,配套入口才安装检查。确认必需方法、插件名称、注入、effect 或固定纯函数结果属于类型、加载或单元测试关注点,而非运行时不变量。
|
||||
发布和注册覆盖全部包;但不会为了覆盖全部包而人为编造运行时断言。只有当包拥有可观察事件关系或相关可变数据关系时,配套入口才安装检查。确认必需方法、插件名称、注入、effect 或固定纯函数结果属于类型、加载或单元测试关注点,而非运行时不变量。
|
||||
|
||||
如果不存在合理的运行时关系,配套入口使用空 installer,并以包专用的前置 `No runtime invariant:` 注释说明原因。纯工具、行为已通过 seam 观察的薄实现、仅组合包、二进制程序、契约需要崩溃/往返测试的持久化适配器和测试支持包通常属于此类。当 owner 获得可变状态或事件协议时,必须重新审视该说明。
|
||||
|
||||
@@ -36,18 +36,18 @@ interface Config {
|
||||
|
||||
| 配套入口 | 检查 |
|
||||
|---|---|
|
||||
| `dsh-session`, `dsh-agent`, `dsh-scope`, `dsh-agent-loop` | 会话包含关系和调用/结果跟踪、agent 状态转换、inbox FIFO 守恒、作用域 subject 和模型请求重建。 |
|
||||
| `dsh-session`, `dsh-agent`, `dsh-scope`, `dsh-agent-loop` | 会话包含关系和调用/结果跟踪、agent(智能体)状态转换、inbox FIFO 守恒、作用域 subject 和模型请求重建。 |
|
||||
| `dsh-llm`, `dsh-llm-retry`, `dsh-tools`, `dsh-system-prompt` | 流语法、持久重试位置和边界、工具流水线阶段与冻结结果,以及权威提示词组装数据。 |
|
||||
| `dsh-compact`, `dsh-hook-protocol`, `dsh-sandbox-policy` | 持久压缩与钩子配对、压缩元数据和沙箱 mode 词汇。 |
|
||||
| `dsh-compact`, `dsh-hook-protocol`, `dsh-sandbox-policy` | 持久压缩(compaction)与钩子配对、压缩元数据和沙箱 mode 词汇。 |
|
||||
| `dsh-fs`, `dsh-subagent`, `dsh-workflow` | 文件系统事件身份、提供方/子级配对和工作流/agent 生命周期身份。 |
|
||||
| `dsh-goal`, `dsh-goal-session` | 持久 goal 来源/内容一致性、修订和生命周期转换、时间戳、顺序接纳 Round 和重建的继续提示词。 |
|
||||
| `dsh-goal`, `dsh-goal-session` | 持久 goal 来源/内容一致性、修订和生命周期转换、时间戳、依次获准的 Round 和重建的继续提示词。 |
|
||||
| `dsh-permission`, `dsh-user-approval` | 活动 preset 引用和审批询问/决定审计配对。 |
|
||||
| `dsh-tasks`, `dsh-tool-todo` | 任务快照生命周期/归属字段和持久整表 todo 结构。 |
|
||||
| `dsh-time-context` | 持久时钟读数与会话开放轮次、下一个步骤前位置和已用 baseline 一致;渲染时间可解析,且不晚于其事件。 |
|
||||
| `dsh-time-context` | 持久化时钟读数与会话中正在进行的轮次、下一步骤开始前的位置及已用时间 baseline 一致;渲染时间可解析,且不晚于其事件。 |
|
||||
|
||||
每个 owner 的根入口仍与诊断独立。单独加载服务不会安装产品检查;在没有服务时加载配套入口,会等待其声明的 `invariants` 注入。
|
||||
每个 owner 的根入口仍独立于诊断。单独加载服务不会安装产品检查;在没有服务时加载配套入口,会等待其声明的 `invariants` 注入。
|
||||
|
||||
`pnpm run verify-package-invariants` 发现全部工作区包。它拒绝生成标记、未说明的空 installer、省略或忽略 reporter 的非空 installer、错误注册名称,以及不完整的导出、发布、依赖、TypeScript 引用或 bundle 接线。该源规则是最低归属检查;聚焦测试证明每个可执行配套入口的语义。
|
||||
`pnpm run verify-package-invariants` 发现全部工作区包。它拒绝生成标记、未说明的空 installer、省略或忽略 reporter 的非空 installer、错误注册名称,以及不完整的导出、发布、依赖、TypeScript 引用或 bundle 接线。该源码规则是最低归属检查;聚焦测试证明每个可执行配套入口的语义。
|
||||
|
||||
## 组合
|
||||
|
||||
@@ -68,18 +68,18 @@ ctx.plugin(SessionInvariant)
|
||||
|
||||
标准 agent 主干挂载服务和 4 个核心有状态配套入口。自定义组合为希望检查其契约的其他已加载包显式添加配套入口;过滤器可以在不改变包入口的情况下禁用或选择注册。
|
||||
|
||||
每个普通 Vitest 拓扑都挂载显式启用的服务和当前测试包的配套入口。聚焦套件覆盖可执行配套入口的有效和无效观察,一个穷尽拓扑则挂载全部配套入口,以证明注册和 dispose 接线。
|
||||
每个普通 Vitest 拓扑都挂载显式启用的服务和当前测试包的配套入口。聚焦套件覆盖可执行配套入口的合法与违规观测,一个穷尽拓扑则挂载全部配套入口,以证明注册和 dispose 接线。
|
||||
|
||||
## 模型体验
|
||||
|
||||
无。服务和配套入口观察运行时事件和可变快照,不会更改提示词、消息、schema、流或工具结果。
|
||||
|
||||
#### KV 缓存影响
|
||||
#### KV Cache 影响
|
||||
|
||||
无;不变量检查不组装或发送提供方请求。
|
||||
|
||||
## 已知限制与待完成工作
|
||||
## 已知限制与暂缓事项
|
||||
|
||||
- 请求重建覆盖 loop 在冻结前显式标记的请求;直接一次性 LLM 调用即使由调用方冻结或附加会话 id,仍不在该标记契约内。
|
||||
- 请求重建覆盖 loop 在冻结前显式标记的请求;直接一次性 LLM(大语言模型)调用即使由调用方冻结或附加会话 id,仍不在该标记契约内。
|
||||
- 仅实时生命周期配套入口无法重建自身重新加载前开始的操作。标准组合和测试组合会在相应操作开始前挂载它们。
|
||||
- 正则表达式过滤器在服务生命周期内固定;更改它们需要执行普通 Cordis 插件重新加载。
|
||||
|
||||
@@ -192,7 +192,7 @@ export class InvariantService extends Service {
|
||||
}
|
||||
// Cordis attaches setup thenability and async teardown to this callable;
|
||||
// the service seam intentionally exposes only the conventional disposer.
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises -- the extra runtime shape stays private.
|
||||
// oxlint-disable-next-line typescript/no-misused-promises -- the extra runtime shape stays private.
|
||||
return registration
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/support/llm-replay/README.md
|
||||
README.md: 6c934e01a5f13b94724d0e5435524d9d463adb2f
|
||||
README.zh.md: 03309931d01957ef60aa65d5d9ba3c2a844b0a16
|
||||
README.zh.md: d13d84ad95a6b63177ee0e2aa83965be19fdeeb8
|
||||
|
||||
@@ -2,21 +2,21 @@
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
用于无密钥快照测试的回放 LLM 插件。它从已记录的**会话 JSONL** fixture 重建模型流,使测试可以在无 API 密钥的情况下使用固定模型 transcript 启动真实 agent。配置 `providers` 后,它会注册仅回放适配器,其目录可供测试模型发现的场景使用;没有 `providers` 时,它会安装不需要发现的测试所用 catch-all `llm/stream` waterfall。
|
||||
用于无密钥快照测试的大语言模型(LLM)回放插件。它根据已记录的**会话 JSONL** fixture(测试前置数据)重建模型流,使测试无需 API 密钥即可针对固定的模型 transcript(文本记录)启动真实 agent(智能体)。配置 `providers` 后,它会注册仅用于回放的适配器,其模型目录可供测试模型发现功能的场景使用;未配置 `providers` 时,它会安装无需模型发现功能的测试所用 catch-all `llm/stream` waterfall(瀑布式事件)。
|
||||
|
||||
其消费方是 ACP、headless `stream-json` 和 TUI 快照套件,以及 web 浏览器 e2e lane。Loader 驱动套件使用此插件替换真实 LLM 适配器;web lane 直接安装它,以保留拆卸消费句柄。将派生和回放逻辑保留在此处,可使其受 `packages/*/src` 上每文件 100% 覆盖率门禁约束。
|
||||
其消费方包括 ACP(Agent Client Protocol)、headless `stream-json` 和 TUI 快照套件,以及 Web 浏览器 e2e 流水线。Loader 驱动的套件使用此插件替代真实 LLM 适配器;Web 流水线直接安装它,以保留清理阶段的消费检查句柄。将派生和回放逻辑放在此处,可使其受 `packages/*/src` 的逐文件 100% 覆盖率门禁约束。
|
||||
|
||||
## Fixture 的工作方式
|
||||
## fixture 的工作方式
|
||||
|
||||
Fixture 就是持久化会话日志(`<scenario>/session.jsonl`)。其 `assistant/chunk` 事件携带每个 `StreamChunk`,因此按 `(turn, step)` 对其分组可重建每次 `stream()` 调用的分片序列(每个 loop 步骤一次模型调用)。因此,录制操作是「运行一次真实 agent 并收集 `.jsonl`」,由快照 harness 完成;该插件不执行录制。Fixture 的 `request/header` 内容可能被 token 化为 `{{system}}`/`{{tools}}`(harness 在一个场景中固定该内容,并擦除其余场景);回放对此并不关心,因为派生只读取 `assistant/chunk` 事件和第 0 行会话 header。
|
||||
fixture 就是持久化的会话日志(`<scenario>/session.jsonl`)。其 `assistant/chunk` 事件包含每个 `StreamChunk`,因此按 `(turn, step)` 分组即可重建每次 `stream()` 调用的分片序列(每个循环步骤调用一次模型)。因此,录制就是「运行一次真实 agent 并收集 `.jsonl`」,由快照 harness 完成;该插件本身不录制。fixture 的 `request/header` 内容可能被标记化为 `{{system}}`/`{{tools}}`(harness 会在一个场景中固定该内容,并清除其余场景中的内容);回放不受影响,因为派生过程只读取 `assistant/chunk` 事件和第 0 行的会话 header。
|
||||
|
||||
有两种失败 mode 无法仅从 `assistant/chunk` 重建:在任何分片前纯抛出(例如 HTTP 401,日志只包含 `turn/end {error}` 而没有分片),以及 cancel/hang(是时序,而非分片内容)。需要这些的场景提供可选 sidecar(`<scenario>/replay.override.json`),它要么替换派生脚本(裸 `ReplayEntry[]`),要么增补派生脚本(`{ patches: [{ at, entry }] }`:保留全部由 JSONL 派生的调用,仅在点名的调用索引处换入,索引从 0 计;`at` 等于派生长度时为追加,正是注入的瞬态抛出之后那次重试尝试所占的槽位)。Patch 索引必须互不重复。覆写文档、每个 patch 与每个条目,以及每个分片的判别字段都会在文件加载时接受校验。`hang` 条目可以指定 `readyFile`;在其前缀分片到达 loop 后、等待取消前,回放会写入该空标记,使外部驱动器可以在不观察展示更新的情况下确定性取消。
|
||||
有两种失败模式无法仅根据 `assistant/chunk` 重建:在产生任何分片前直接抛出异常(例如 HTTP 401,此时日志只有 `turn/end {error}` 而没有分片),以及取消或挂起(差异在时序,而非分片内容)。需要这些行为的场景可提供伴随文件(`<scenario>/replay.override.json`):它可以替换派生脚本(裸 `ReplayEntry[]`),也可以增补派生脚本(`{ patches: [{ at, entry }] }`:保留所有从 JSONL 派生的调用,只替换指定的从 0 开始计数的调用索引;当 `at` 等于派生长度时,则在注入瞬态异常后的重试位置追加一次调用)。补丁索引不得重复。文件加载时会校验覆写文档、每个补丁和条目,以及每个分片的判别标签。`hang` 条目可以指定 `readyFile`;当前缀分片到达循环后、开始等待取消前,回放会写入这个空标记,使外部驱动程序无需观察展示层更新即可确定性地取消。
|
||||
|
||||
## 嵌套 agent:每会话键控
|
||||
|
||||
父 agent 委托给进程内 subagent 的场景会记录多个日志:父级(`session.jsonl`)和每个子级各一个(`session.1.jsonl`等)。每个 agent 在同一上下文中作为自己的 `Session` 运行,因此回放必须为每个 agent 提供自己的脚本。
|
||||
父 agent 委托给进程内 subagent(子 agent)的场景会记录多个日志:父会话使用 `session.jsonl`,每个子会话各使用一个日志(`session.1.jsonl` 等)。每个 agent 都在同一上下文中作为独立的 `Session` 运行,因此回放必须为每个 agent 提供各自的脚本。
|
||||
|
||||
回放按调用会话 id 为每次调用建键(由 agent loop 标记的 `GenerateOptions.sessionId`)。实时会话 id 在每次运行中都是新的随机值,绝不等于已记录值,因此实时会话通过**首次调用顺序** 绑定到已记录脚本:脚本按 header `createdAt` 排序(父级在前,因为它必须先进行流式输出才能委托);第一个进行任何调用的实时会话领取第一个脚本,下一个新会话领取下一个,以此类推。然后,每个会话推进自己的游标。没有 `sessionId` 的调用是绑定到主脚本的单一匿名会话,因此单会话场景与以前完全相同。实时会话数超过已记录脚本数时快速失败。
|
||||
回放根据发起调用的会话 id 为每次调用建立键(`GenerateOptions.sessionId` 由 agent loop 写入)。实时会话 id 每次运行时都会重新随机生成,绝不会等于记录中的 id,因此实时会话按**首次调用顺序**绑定到已记录脚本:脚本按 header 中的 `createdAt` 排序(父会话在前,因为它必须先开始流式输出才能委托);第一个发起调用的实时会话取得第一个脚本,下一个新会话取得下一个脚本,以此类推。此后每个会话分别推进自己的游标。没有 `sessionId` 的调用视为一个绑定主脚本的匿名会话,因此单会话场景的行为与以前完全相同。不同实时会话的数量超过已记录脚本数时会明确报错。
|
||||
|
||||
## 配置
|
||||
|
||||
@@ -24,9 +24,9 @@ Fixture 就是持久化会话日志(`<scenario>/session.jsonl`)。其 `assis
|
||||
|---|---|---|---|
|
||||
| `file` | string | `$DSH_SNAPSHOT_FILE` | 主(父)`session.jsonl` fixture 的路径。必需(配置或 env)。 |
|
||||
| `overrideFile` | string | `$DSH_SNAPSHOT_OVERRIDE` | 主会话的可选 `ReplayOverrideDoc` sidecar:裸 `ReplayEntry[]` 替换其派生脚本,`{ patches }` 则按调用索引增补该脚本。 |
|
||||
| `childFiles` | string[] | `$DSH_SNAPSHOT_CHILD_FILES` (path-delimited) | 嵌套场景中已记录的 subagent 子会话日志;单会话场景为空。 |
|
||||
| `childFiles` | string[] | `$DSH_SNAPSHOT_CHILD_FILES`(以路径分隔符分隔) | 嵌套场景中已记录的 subagent 子会话日志;单会话场景为空。 |
|
||||
| `providers` | `ReplayProviderConfig[]` | 无 | 可选的仅回放提供方和模型目录。每个提供方可以设置 `retryPolicy`,每个模型可以发布 `contextWindow`;已配置路由通过回放适配器分派,绝不执行提供方 I/O。 |
|
||||
| `paceMs` | number | 无(突发) | 可选的每分片毫秒延迟,使下游传输(例如真实浏览器观察的 web SSE mux)看到真正的增量传递。它只是仿真开关,测试不得依赖它保证正确性。值必须是非负整数;pace 等待期间中止会迅速取消流。 |
|
||||
| `paceMs` | number | 无(突发) | 可选的每分片毫秒延迟,使下游传输(例如真实浏览器观察到的 Web SSE(Server-Sent Events)多路复用器)看到真正的增量传递。它只是仿真开关,测试不得依赖它保证正确性。值必须是非负整数;pace 等待期间中止会迅速取消流。 |
|
||||
|
||||
```yaml
|
||||
- id: llm-replay
|
||||
@@ -52,10 +52,10 @@ Fixture 就是持久化会话日志(`<scenario>/session.jsonl`)。其 `assis
|
||||
|
||||
## 导出项
|
||||
|
||||
- `installLlmReplay(ctx, config)`:安装已配置回放适配器或 catch-all `llm/stream` 监听器;返回 `ReplayHandle`(包含用于 HMR 安全的 `dispose()`,以及 `assertConsumed()` 拆卸检查;后者确保每个已记录脚本都绑定到实时会话,且每个已绑定游标都已耗尽,从而将场景静默驱动的模型调用少于记录数转换为明确诊断)。在测试中使用它,可以不通过 Loader 或 env var 驱动回放。
|
||||
- `loadSessionScripts(config)`:解析场景的有序 `SessionScript[]` (主级 + 子级),准备按首次调用顺序绑定到实时会话。
|
||||
- `loadReplayScript(config)`:只解析主会话的 `ReplayEntry[]` (如果存在则使用经校验的 sidecar 替换或 patch,否则从 JSONL 派生;fixture 缺失时快速失败)。
|
||||
- `deriveReplayScript(events)` / `parseSessionLog(text)` / `parseSessionHeader(text)`:将已记录会话日志转换为脚本并读取其 header `id`/`createdAt` 的纯辅助工具。派生分组必须以 `finish` 分片结束;没有该分片的分组是已抛出 `stream()` 的指纹,必须改用 override sidecar 表达。
|
||||
- `installLlmReplay(ctx, config)`:安装已配置回放适配器或 catch-all `llm/stream` 监听器;返回 `ReplayHandle`(包含用于保证 HMR(热模块替换)安全的 `dispose()`,以及清理阶段执行的 `assertConsumed()` 检查;后者确保每个已记录脚本都绑定到实时会话,且每个已绑定游标都已耗尽,从而将场景静默驱动的模型调用少于记录数转换为明确诊断)。在测试中使用它,可以不通过 Loader 或 env var 驱动回放。
|
||||
- `loadSessionScripts(config)`:解析场景的有序的 `SessionScript[]`(主会话 + 子会话),准备按首次调用顺序绑定到实时会话。
|
||||
- `loadReplayScript(config)`:只解析主会话的 `ReplayEntry[]`(如果伴随文件存在,则使用经校验的替换或补丁;否则从 JSONL 派生;fixture 缺失时明确报错)。
|
||||
- `deriveReplayScript(events)` / `parseSessionLog(text)` / `parseSessionHeader(text)`:将已记录会话日志转换为脚本并读取其 header `id`/`createdAt` 的纯辅助工具。派生分组必须以 `finish` 分片结束;没有该分片的分组是 `stream()` 抛出异常的指纹,必须改用 override sidecar 表达。
|
||||
- 类型 `ReplayEntry` / `ReplayOverrideDoc` / `ReplayOverridePatch` / `SessionScript` / `ReplayConfig` / `ReplayProviderConfig` / `ReplayModelConfig` / `ReplayHandle` / `Config`。
|
||||
|
||||
## 插件导出形态
|
||||
@@ -66,11 +66,11 @@ Fixture 就是持久化会话日志(`<scenario>/session.jsonl`)。其 `assis
|
||||
|
||||
无。该无密钥测试适配器不向提供方模型发送请求,只将已记录 assistant 分片回放到测试 loop 中。
|
||||
|
||||
#### KV 缓存影响
|
||||
#### KV Cache 影响
|
||||
|
||||
无;该包既不组装也不发送提供方请求。
|
||||
无;该包(package)既不组装也不发送提供方请求。
|
||||
|
||||
## 已知限制与待完成工作
|
||||
## 已知限制与暂缓事项
|
||||
|
||||
- **首次调用顺序脚本绑定假设串行委托**:并发运行同级 subagent 的 cut(或运行中落地的压缩摘要调用)会非确定性地将实时会话绑定到已记录脚本;在这种场景出现前暂不实现更强的键控(`XXX(concurrent-subagents)`)。
|
||||
- **只有生产分片的调用可派生**:纯分片前抛出或 cancel/hang 场景需要 `replay.override.json` sidecar。替换和 patch 两种形式都只影响主会话;子会话脚本仍从各自日志派生。
|
||||
- **首次调用顺序脚本绑定假设串行委托**:并发运行同级 subagent 的 cut(或运行中发生的上下文压缩(context compaction)摘要调用)会非确定性地将实时会话绑定到已记录脚本;在这种场景出现前暂不实现更强的键控(`XXX(concurrent-subagents)`)。
|
||||
- **只有会产生分片的调用才能派生**:在产生分片前直接抛出异常或取消/挂起的场景需要 `replay.override.json` 伴随文件。替换和补丁两种形式都只影响主会话;子会话脚本仍从各自日志派生。
|
||||
|
||||
@@ -1,6 +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
|
||||
# pnpm run verify-translation-pairing --write packages/support/loader-smoke/README.md
|
||||
README.md: 73610ce50ebac4c6fc7bb9135f7b41b347c60685
|
||||
README.zh.md: 17f8481220136e8edf9fccd23fabfca5ccf41dfc
|
||||
README.zh.md: 9254cd1592d4bf3f119d350f04165228db87b1cf
|
||||
|
||||
@@ -4,20 +4,20 @@
|
||||
|
||||
用于测试通过 Cordis Loader 启动应用和 `cordis.yml` 的共享子进程 harness。`resolveExampleLaunch` 选择本地 `src` mode(tsx 和根 tsconfig 路径)或 CI `lib` mode(普通 Node 和包导出);选择依据为显式 mode 或 `DSH_EXAMPLE_MODE`。
|
||||
|
||||
`runLoaderSmoke` 接受 bin 和配置路径、可选的完整 bin 参数、环境覆盖、stdin、运行前设置和清理前检查。它负责隔离 cwd、DSH 主目录、诊断、deadline、终止、EOF 和清理;在零退出后返回两个流,失败时拒绝并携带两个流。
|
||||
`runLoaderSmoke` 接受可执行文件路径和配置路径、可选的完整可执行文件参数、环境变量覆盖、标准输入、运行前准备和清理前检查。它负责隔离工作目录、DSH 主目录、诊断、截止时间、终止、EOF 和清理;进程以零状态退出后返回两个流,失败时则返回拒绝并附带两个流。
|
||||
|
||||
这是支持层测试基础设施,而非产品 API。
|
||||
|
||||
## 模型体验
|
||||
|
||||
无。该测试专用 harness 启动示例进程并检查它们的流,不会改变已组装模型请求。
|
||||
无。该测试专用 harness 启动示例进程并检查它们的流,不会改变组装后的模型请求。
|
||||
|
||||
#### KV 缓存影响
|
||||
#### KV Cache 影响
|
||||
|
||||
无;该包既不组装也不发送提供方请求。
|
||||
|
||||
## 已知限制与待完成工作
|
||||
## 已知限制与暂缓事项
|
||||
|
||||
- **构建 mode 需要事先构建**:配置还必须能够通过 `examples/node_modules` 向上解析每个命名包。
|
||||
- **构建模式需要事先构建**:配置还必须能够通过 `examples/node_modules` 向上解析每个命名包。
|
||||
- **捕获的 stdout 和 stderr 仅受 execa 默认 100 MB `maxBuffer` 约束**:失控子进程会在该上限处被终止,而不是在冒烟测试自选的预算处。
|
||||
- **超时只终止直接子进程**:故障 fixture 生成的进程树可以比冒烟测试存活更久,需要外部清理。
|
||||
- **超时只终止直接子进程**:有故障的 fixture(测试前置数据)spawn 的进程树可能比冒烟测试存活更久,需要外部清理。
|
||||
|
||||
Reference in New Issue
Block a user