Merge branch 'codex/simp-hide-concrete-agent-loop' into codex/simp-hide-subagent-internals
This commit is contained in:
@@ -918,7 +918,7 @@ export interface Config {
|
||||
|
||||
Depends on: [`SubagentCapabilities`](../packages/subagent/subagent/src/index.ts) · [`SubagentStopReason`](../packages/subagent/subagent/src/index.ts)
|
||||
|
||||
Source: [`packages/support/subagent-mock/src/index.ts:86`](../packages/support/subagent-mock/src/index.ts)
|
||||
Source: [`packages/support/subagent-mock/src/index.ts:87`](../packages/support/subagent-mock/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-subagent-spawn`
|
||||
|
||||
|
||||
@@ -301,7 +301,7 @@ A ready child settled. Scope-filtered dispatch uses the same delegating parent c
|
||||
'subagent/end'(this: Scoped<SubagentService>, info: SubagentRunEndInfo): void
|
||||
```
|
||||
|
||||
Source: [`packages/subagent/subagent/src/index.ts:109`](../../packages/subagent/subagent/src/index.ts)
|
||||
Source: [`packages/subagent/subagent/src/index.ts:112`](../../packages/subagent/subagent/src/index.ts)
|
||||
|
||||
### `subagent/provider-added` — emit
|
||||
|
||||
@@ -311,7 +311,7 @@ A provider became resolvable in the registry.
|
||||
'subagent/provider-added'(provider: SubagentProvider): void
|
||||
```
|
||||
|
||||
Source: [`packages/subagent/subagent/src/index.ts:83`](../../packages/subagent/subagent/src/index.ts)
|
||||
Source: [`packages/subagent/subagent/src/index.ts:86`](../../packages/subagent/subagent/src/index.ts)
|
||||
|
||||
### `subagent/provider-removed` — emit
|
||||
|
||||
@@ -321,7 +321,7 @@ A provider left the registry. Accepted runs remain holder-owned.
|
||||
'subagent/provider-removed'(name: string): void
|
||||
```
|
||||
|
||||
Source: [`packages/subagent/subagent/src/index.ts:89`](../../packages/subagent/subagent/src/index.ts)
|
||||
Source: [`packages/subagent/subagent/src/index.ts:92`](../../packages/subagent/subagent/src/index.ts)
|
||||
|
||||
### `subagent/start` — emit
|
||||
|
||||
@@ -331,7 +331,7 @@ A provider established a ready child. For in-process providers, `ctx.agents.get(
|
||||
'subagent/start'(this: Scoped<SubagentService>, info: SubagentRunInfo): void
|
||||
```
|
||||
|
||||
Source: [`packages/subagent/subagent/src/index.ts:100`](../../packages/subagent/subagent/src/index.ts)
|
||||
Source: [`packages/subagent/subagent/src/index.ts:103`](../../packages/subagent/subagent/src/index.ts)
|
||||
|
||||
## `system-prompt/*`
|
||||
|
||||
|
||||
@@ -266,7 +266,7 @@ list(): string[]
|
||||
async start(name: string, request: SubagentStartRequest): Promise<SubagentRun>
|
||||
```
|
||||
|
||||
Source: [`packages/subagent/subagent/src/index.ts:142`](../../packages/subagent/subagent/src/index.ts)
|
||||
Source: [`packages/subagent/subagent/src/index.ts:153`](../../packages/subagent/subagent/src/index.ts)
|
||||
|
||||
## `ctx.systemPrompt` — `SystemPrompt`
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ interface SubagentStopReasonMap {
|
||||
```ts type-equiv
|
||||
interface SubagentRun {
|
||||
readonly id: SessionId
|
||||
readonly localAgent: Agent | undefined
|
||||
readonly result: Promise<SubagentResult>
|
||||
dispose(): Promise<void>
|
||||
sendMessage?(content: ContentBlock[]): void
|
||||
@@ -76,7 +77,7 @@ interface SubagentRun {
|
||||
}
|
||||
```
|
||||
|
||||
A local run MUST publish an ordinary child agent/session before `start()` fulfills, return that child session id as `SubagentRun.id`, and record `request.parent.session.id` in the child's `parentSession` header. Runtime ownership may place the child under the parent, provider, or root scope; `parentSession` is the durable transport-neutral lineage. A remote provider instead returns a parent-scoped lifecycle id and does not publish a local child.
|
||||
A local run MUST publish an ordinary child agent/session before `start()` fulfills, return that child session id as `SubagentRun.id`, expose the exact child as `localAgent`, and record `request.parent.session.id` in the child's `parentSession` header. Runtime ownership may place the child under the parent, provider, or root scope. A remote provider instead returns a parent-scoped lifecycle id and `localAgent: undefined`.
|
||||
|
||||
## The provider seam: `SubagentProvider`
|
||||
|
||||
@@ -91,7 +92,7 @@ interface SubagentProvider {
|
||||
}
|
||||
```
|
||||
|
||||
`start()` fulfills only with a ready run. The service observes its result, emits `subagent/start`, and returns the same run; rejection implies provider cleanup and emits no lifecycle pair. In-process children are discoverable through `ctx.agents`, while remote children need not be. `subagent/end` reports final output or infrastructure failure. Both events are observe-only and contain listener exceptions.
|
||||
`start()` fulfills only with a ready run. The service mints a unique `runId`, snapshots `local` from the provider's exact `localAgent`, observes the result, emits `subagent/start`, and returns the same run; rejection implies provider cleanup and emits no lifecycle pair. The paired `subagent/end` carries the same identity and the final output or infrastructure failure. Both events are observe-only and contain listener exceptions.
|
||||
|
||||
## In-process backends: depth and seed
|
||||
|
||||
|
||||
@@ -30,10 +30,10 @@ This matrix shows which packages dispatch each harness-owned event and which pac
|
||||
| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:56`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`session-persistence`](../packages/session-persistence/session-persistence) |
|
||||
| `session/event` | `emit` | [`packages/core/session/src/index.ts:68`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/ui/acp), [`invariants`](../packages/support/invariants), [`jsonrpc`](../packages/ui/jsonrpc), [`session-persistence`](../packages/session-persistence/session-persistence), [`stdio`](../packages/ui/stdio), [`token-meter`](../packages/llm/token-meter), [`workspace-context`](../packages/context/workspace-context) |
|
||||
| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:78`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence`](../packages/session-persistence/session-persistence) |
|
||||
| `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:109`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`jsonrpc`](../packages/ui/jsonrpc) |
|
||||
| `subagent/provider-added` | `emit` | [`packages/subagent/subagent/src/index.ts:83`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`emit`) | [`tool-subagent`](../packages/subagent/tool-subagent) |
|
||||
| `subagent/provider-removed` | `emit` | [`packages/subagent/subagent/src/index.ts:89`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`tool-subagent`](../packages/subagent/tool-subagent) |
|
||||
| `subagent/start` | `emit` | [`packages/subagent/subagent/src/index.ts:100`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`jsonrpc`](../packages/ui/jsonrpc) |
|
||||
| `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:112`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`jsonrpc`](../packages/ui/jsonrpc) |
|
||||
| `subagent/provider-added` | `emit` | [`packages/subagent/subagent/src/index.ts:86`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`emit`) | [`tool-subagent`](../packages/subagent/tool-subagent) |
|
||||
| `subagent/provider-removed` | `emit` | [`packages/subagent/subagent/src/index.ts:92`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`tool-subagent`](../packages/subagent/tool-subagent) |
|
||||
| `subagent/start` | `emit` | [`packages/subagent/subagent/src/index.ts:103`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../packages/hooks/hooks-claude) |
|
||||
| `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:27`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | [`acp`](../packages/ui/acp) |
|
||||
| `system-prompt/change` | `emit` | [`packages/core/system-prompt/src/index.ts:33`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`emit`) | - |
|
||||
| `tools/change` | `emit` | [`packages/core/tools/src/index.ts:116`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`emit`) | - |
|
||||
|
||||
@@ -279,6 +279,7 @@ flowchart TD
|
||||
pkg_tool_skill --> pkg_skill
|
||||
pkg_tool_skill --> pkg_tools
|
||||
pkg_subagent --> pkg_agent
|
||||
pkg_subagent --> pkg_brand
|
||||
pkg_subagent --> pkg_llm
|
||||
pkg_subagent --> pkg_scope
|
||||
pkg_subagent --> pkg_session
|
||||
@@ -493,7 +494,7 @@ flowchart TD
|
||||
| [`tool-fs`](../packages/fs/tool-fs) | `fs` | [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
| [`tool-fs-search`](../packages/fs/tool-fs-search) | `fs` | [`bash`](../packages/bash/bash), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
| [`tool-skill`](../packages/skill/tool-skill) | `skill` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`skill`](../packages/skill/skill), [`tools`](../packages/core/tools) |
|
||||
| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`tools`](../packages/core/tools) |
|
||||
| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`tools`](../packages/core/tools) |
|
||||
| [`tool-web`](../packages/web/tool-web) | `web` | [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`web`](../packages/web/web) |
|
||||
| [`spill-policy`](../packages/spill/spill-policy) | `spill` | [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`tools`](../packages/core/tools) |
|
||||
| [`timeout-policy`](../packages/timeout/timeout-policy) | `timeout` | [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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 与配置文件必须保持同步。
|
||||
@@ -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: a package `bin` runs built `lib/bin.js` under plain `node`, exposing failures tsx masks (settle races, module resolution, swallowed load failures). The same applies to non-index runtime entries (the worker-thread sibling `lib/worker.cjs`) and singleton modules shared across bundles (`packages/ui/jsonrpc/tests/built-scope-carrier.e2e.ts`). 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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user