diff --git a/.agents/notes/implemented/bug-fix/2026-08-11-preset-authoring-agent-validates-its-own-composition.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-11-preset-authoring-agent-validates-its-own-composition.i18n.yaml index d932d7f6f4..9c173b1cd7 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-11-preset-authoring-agent-validates-its-own-composition.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-08-11-preset-authoring-agent-validates-its-own-composition.i18n.yaml @@ -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 .agents/notes/implemented/bug-fix/2026-08-11-preset-authoring-agent-validates-its-own-composition.md -2026-08-11-preset-authoring-agent-validates-its-own-composition.md: 77bd30d4c8f6599ccde50b4d814f55d065266763 -2026-08-11-preset-authoring-agent-validates-its-own-composition.zh.md: cdc0d30bcd769e1a17ddcce364002f0136a26242 +2026-08-11-preset-authoring-agent-validates-its-own-composition.md: b7d20ef20c611a0ea0c694bac6bd3948bd194b2b +2026-08-11-preset-authoring-agent-validates-its-own-composition.zh.md: 512ac5ea0f47f4a796170b070c140f14b0072879 diff --git a/.agents/notes/implemented/bug-fix/2026-08-11-preset-authoring-agent-validates-its-own-composition.md b/.agents/notes/implemented/bug-fix/2026-08-11-preset-authoring-agent-validates-its-own-composition.md index 77bd30d4c8..b7d20ef20c 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-11-preset-authoring-agent-validates-its-own-composition.md +++ b/.agents/notes/implemented/bug-fix/2026-08-11-preset-authoring-agent-validates-its-own-composition.md @@ -14,7 +14,7 @@ Its `isolate` example composed `tasks-local` with `tool-tasks`. `tasks-local` is It described a string realm label as pooling one instance across subtrees. Labels join realms; `provide()` still throws on the second registration under the same realm symbol, which `standard`'s header comment already stated. -It sent the agent to a package's README to learn whether a row publishes a service. Outside `apps/cli` (`files: ["lib/*.js", "config"]`), every harness package publishes only `lib/index.js`, `lib/invariant.js`, and `lib/types/**/*.d.ts` — no README, no `src/`, no `docs/`. In an installed deployment that instruction cannot be followed at all. +It sent the agent to a package's README to learn whether a row publishes a service. Every harness package declares `files`, and no declaration includes its README, so an installed deployment carries none. There that instruction cannot be followed at all. Underneath all four sat a capability claim: the agent "cannot start one \[a session\] yourself", so verification was hand-reading YAML fields and handing the result to the user through the settings page's red marking. That marking is discovery's shape check, which is far weaker than the sentence implied. @@ -41,7 +41,8 @@ Each row was produced by booting the shipped Web composition and calling the too | Composition under test | `list()` `broken` | `standingKeyFor()` | |---|---|---| | row names an absent package | empty | `Cannot find package '@deepseek-ai/dsh-does-not-exist'` | -| service row with no realm | empty | `service "tasks" has been registered at ` | +| service row with no realm, name the host supplies | empty | `service "tasks" has been registered at ` | +| service row with no realm, name the host does not supply | empty | `row(s) published process-global service(s) [workflows]; …` | | same row inside `isolate` | empty | mounts | | consumer row with no provider | empty | `1 row(s) did not activate: … waiting for workflows` | | row missing a required config field | empty | `invalid config: $.allowParallelInProgress missing required value` | diff --git a/.agents/notes/implemented/bug-fix/2026-08-11-preset-authoring-agent-validates-its-own-composition.zh.md b/.agents/notes/implemented/bug-fix/2026-08-11-preset-authoring-agent-validates-its-own-composition.zh.md index cdc0d30bcd..512ac5ea0f 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-11-preset-authoring-agent-validates-its-own-composition.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-08-11-preset-authoring-agent-validates-its-own-composition.zh.md @@ -14,7 +14,7 @@ Status: implemented 它把字符串 realm label 描述为跨子树共享一个实例。label 只是加入同一 realm,`provide()` 在同一 realm symbol 下第二次注册仍然抛错——`standard` 的头部注释早已如此说明。 -它让 agent 去读包的 README 判断某行是否发布服务。除 `apps/cli`(`files: ["lib/*.js", "config"]`)外,所有 harness 包只发布 `lib/index.js`、`lib/invariant.js` 与 `lib/types/**/*.d.ts`,没有 README、没有 `src/`、没有 `docs/`。在装机部署中该指令根本无法执行。 +它让 agent 去读包的 README 判断某行是否发布服务。每个 harness 包都声明了 `files`,且没有任何一份声明包含自己的 README,因此装机部署中一份也没有。在那里该指令根本无法执行。 四条之下还压着一个能力断言:agent「自己起不了会话」,于是校验退化成肉眼核对 YAML 字段,再把结果经设置页的红色标记交给用户。那个标记是发现阶段的结构检查,远弱于这句话给人的印象。 @@ -41,7 +41,8 @@ agent 按 `cordis_mount` 自身文档所述的方式够到 roster 服务:挂 | 被测组装 | `list()` 的 `broken` | `standingKeyFor()` | |---|---|---| | 行指向不存在的包 | 空 | `Cannot find package '@deepseek-ai/dsh-does-not-exist'` | -| 服务行未套 realm | 空 | `service "tasks" has been registered at ` | +| 服务行未套 realm,名字宿主已提供 | 空 | `service "tasks" has been registered at ` | +| 服务行未套 realm,名字宿主未提供 | 空 | `row(s) published process-global service(s) [workflows]; …` | | 同一行置于 `isolate` 内 | 空 | 挂载成功 | | 消费者行无人提供服务 | 空 | `1 row(s) did not activate: … waiting for workflows` | | 行缺少必填配置字段 | 空 | `invalid config: $.allowParallelInProgress missing required value` | diff --git a/apps/cli/config/agent-presets/cordis/skills/editing-cordis-compositions/SKILL.md b/apps/cli/config/agent-presets/cordis/skills/editing-cordis-compositions/SKILL.md index 7a37a61e0c..32a59dbb1b 100644 --- a/apps/cli/config/agent-presets/cordis/skills/editing-cordis-compositions/SKILL.md +++ b/apps/cli/config/agent-presets/cordis/skills/editing-cordis-compositions/SKILL.md @@ -63,9 +63,9 @@ Unmount the plugin with `cordis_unmount` when you are done; it is a probe, not a ## Authoring a preset -1. **Start from a copy.** `copy(from, id, name)` copies a whole preset directory into the user root — composition, metadata, skill directories, assets. It validates the id (lowercase letters, digits, and hyphens, because it becomes the directory name), refuses an id any root already supplies, rolls a failed copy back, and rewrites the copy's `preset.yml` to keep the source's description while dropping its name and roster `order`. Prefer it over a shell copy: it needs no sandbox escalation, and the copy is exactly as loadable as its source. `standard` is the full coding agent and the usual source. -2. **Expect the file sandbox on every edit after the copy.** The user preset root lies outside the session workspace, so under the default `workspace-write` policy the first write there is denied. Retry that exact command once with `sandbox_permissions` escalation and a short justification — the user sees and approves it. Batch your writes (one heredoc per file) rather than escalating many small commands. `copy()` itself runs host-side and needs none of this; the edits do. -3. **Give the copy its own `name` and `description`** in `preset.yml`. +1. **Start from a copy.** `copy(from, id, name)` copies a whole preset directory into the user root — composition, metadata, skill directories, assets. It validates the id against `[a-z0-9][a-z0-9-]*` (it becomes the directory name, so no leading hyphen), refuses an id any root already supplies, rolls a failed copy back, and rewrites the copy's `preset.yml` to keep the source's description while dropping its name and roster `order`. Prefer it over a shell copy: it needs no sandbox escalation, and the copy is exactly as loadable as its source. `standard` is the full coding agent and the usual source. +2. **Expect the file sandbox on every edit after the copy.** The user preset root lies outside the session workspace, so under the default `workspace-write` policy the first write there is denied. Only writes are: reading any composition by absolute path needs no escalation. Retry that exact command once with `sandbox_permissions` escalation and a short justification — the user sees and approves it. Batch your writes (one heredoc per file) rather than escalating many small commands. `copy()` itself runs host-side and needs none of this; the edits do. +3. **Write the copy's `description`** in `preset.yml`, and its `name` if you passed none to `copy()`. 4. **Edit `agent.cordis.yml`** row by row, keeping the plane rule and the realm rule. 5. **Mount-validate the result**, then hand off to the user for a real session — both under *Verifying a change*. @@ -106,8 +106,8 @@ Realms are for services a preset owns, not for every group. A host capability th - a row whose package does not resolve (`Cannot find package …`); - a row whose config is invalid (`invalid config: $. missing required value`); -- a service published into the root realm (`service "" has been registered at `); -- a row that never activated (`N row(s) did not activate: : waiting for `). +- a row that never activated (`N row(s) did not activate: : waiting for `); +- a service published into the root realm, which arrives as one of two messages. A name the host does not supply lands in the root realm and the mount audit rejects it: `row(s) published process-global service(s) []; a preset service must sit behind an isolate realm or move to the host composition` — this is the shape a preset's own forgotten realm takes. A name the host already supplies collides before the audit: `service "" has been registered at `. Both name the offending service. It returns normally when the composition mounts. Run it as the final check on a finished edit rather than after every line: a successful mount installs a standing generation that lives until the process exits, while a failed one disposes its subtree and leaves nothing behind.