From 5d86a284e548ccfac0557cd2ea4ff106ac9e1306 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Mon, 10 Aug 2026 14:18:41 +0800 Subject: [PATCH 1/9] fix(web-app,agent-presets): keep the task registry on the host plane MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `tool-bash` resolves the background-task registry with `ctx.get('tasks')`, and it sits at the preset's top level. The registry sat inside an entry-local `isolate: { tasks: true }` realm, which is invisible to every sibling row outside it, while the Web surface disabled the host row — so both lookups missed and every `run_in_background` call answered "background tasks unavailable" with `task_output`, `task_list`, and `task_kill` still listed in the catalog. `task_list` returning "(no background tasks)" is what made the outage read as an empty queue rather than a severed producer. That is the `goals` criterion read from inside the preset: a Service a row outside its realm READS belongs to the plane both can see. `tasks` already keys access by owning agent (`assertAccess` compares `task.owner.id`) and mints an independent token per `attachSurface` call, so one host instance serves every session exactly as before presets — the per-preset-standing-mounts note records that sharing `tasks-local` is a return to its design. `minimal` mounts no `tool-tasks`, and the `start()` control-surface gate is a service-wide set that another preset's controls would open for it, so its `tool-bash` disables `run_in_background` and drops the parameter from the schema. Fixes #2141 --- .../agent-presets/code/agent.cordis.yml | 20 +++--- .../agent-presets/cordis/agent.cordis.yml | 20 +++--- .../agent-presets/minimal/agent.cordis.yml | 8 +++ .../agent-presets/standard/agent.cordis.yml | 20 +++--- apps/web/tests/shipped-composition.e2e.ts | 62 +++++++++++++++++++ packages/bundle/web-app/cordis.patch.yml | 13 +++- 6 files changed, 107 insertions(+), 36 deletions(-) diff --git a/apps/cli/config/agent-presets/code/agent.cordis.yml b/apps/cli/config/agent-presets/code/agent.cordis.yml index 65d2716458..d068e00dea 100644 --- a/apps/cli/config/agent-presets/code/agent.cordis.yml +++ b/apps/cli/config/agent-presets/code/agent.cordis.yml @@ -70,17 +70,15 @@ # ── background tasks ──────────────────────────────────────────────────────── -- id: tasks - name: cordis:group - group: true - isolate: - tasks: true - config: - - id: tasks-local - name: '@deepseek-ai/dsh-tasks-local' - - - id: tool-tasks - name: '@deepseek-ai/dsh-tool-tasks' +# Only the model-facing controls. The task REGISTRY stays on the host plane: +# its producers sit outside any realm this file could put it in — `tool-bash` +# above resolves it with `ctx.get`, and an entry-local realm here is invisible +# to every sibling row, so `run_in_background` would answer "background tasks +# unavailable" while these controls sat in the catalog. The registry is keyed by +# owning agent anyway, so one host instance serves every session. What a preset +# chooses is whether its agent can collect and stop background work at all. +- id: tool-tasks + name: '@deepseek-ai/dsh-tool-tasks' # ── skills ────────────────────────────────────────────────────────────────── diff --git a/apps/cli/config/agent-presets/cordis/agent.cordis.yml b/apps/cli/config/agent-presets/cordis/agent.cordis.yml index f2cdeea159..91fa28a31d 100644 --- a/apps/cli/config/agent-presets/cordis/agent.cordis.yml +++ b/apps/cli/config/agent-presets/cordis/agent.cordis.yml @@ -64,17 +64,15 @@ # ── background tasks ──────────────────────────────────────────────────────── -- id: tasks - name: cordis:group - group: true - isolate: - tasks: true - config: - - id: tasks-local - name: '@deepseek-ai/dsh-tasks-local' - - - id: tool-tasks - name: '@deepseek-ai/dsh-tool-tasks' +# Only the model-facing controls. The task REGISTRY stays on the host plane: +# its producers sit outside any realm this file could put it in — `tool-bash` +# above resolves it with `ctx.get`, and an entry-local realm here is invisible +# to every sibling row, so `run_in_background` would answer "background tasks +# unavailable" while these controls sat in the catalog. The registry is keyed by +# owning agent anyway, so one host instance serves every session. What a preset +# chooses is whether its agent can collect and stop background work at all. +- id: tool-tasks + name: '@deepseek-ai/dsh-tool-tasks' # ── goals ─────────────────────────────────────────────────────────────────── diff --git a/apps/cli/config/agent-presets/minimal/agent.cordis.yml b/apps/cli/config/agent-presets/minimal/agent.cordis.yml index 8ca6f0dcdf..cbccafe160 100644 --- a/apps/cli/config/agent-presets/minimal/agent.cordis.yml +++ b/apps/cli/config/agent-presets/minimal/agent.cordis.yml @@ -22,8 +22,16 @@ # never reached the model's shell at all. `tool-bash` consumes the host registry # from here; the executor behind it (`bash-sandbox`) is host-plane too, where the # sandbox policy owns it. +# +# `run_in_background` is off because this preset mounts no `tool-tasks`: the +# host task registry gates starts on SOME control surface being attached, and +# that set is process-wide, so another preset's controls would let this agent +# start work it has no `task_output` to collect. Disabling drops the parameter +# from the schema too, which is the honest surface for a two-tool benchmark. - id: tool-bash name: '@deepseek-ai/dsh-tool-bash' + config: + enableRunInBackground: false - id: tool-str-replace-editor name: '@deepseek-ai/dsh-tool-str-replace-editor' diff --git a/apps/cli/config/agent-presets/standard/agent.cordis.yml b/apps/cli/config/agent-presets/standard/agent.cordis.yml index 66407faf1d..f73f4b3fba 100644 --- a/apps/cli/config/agent-presets/standard/agent.cordis.yml +++ b/apps/cli/config/agent-presets/standard/agent.cordis.yml @@ -63,17 +63,15 @@ # ── background tasks ──────────────────────────────────────────────────────── -- id: tasks - name: cordis:group - group: true - isolate: - tasks: true - config: - - id: tasks-local - name: '@deepseek-ai/dsh-tasks-local' - - - id: tool-tasks - name: '@deepseek-ai/dsh-tool-tasks' +# Only the model-facing controls. The task REGISTRY stays on the host plane: +# its producers sit outside any realm this file could put it in — `tool-bash` +# above resolves it with `ctx.get`, and an entry-local realm here is invisible +# to every sibling row, so `run_in_background` would answer "background tasks +# unavailable" while these controls sat in the catalog. The registry is keyed by +# owning agent anyway, so one host instance serves every session. What a preset +# chooses is whether its agent can collect and stop background work at all. +- id: tool-tasks + name: '@deepseek-ai/dsh-tool-tasks' # ── skills ────────────────────────────────────────────────────────────────── diff --git a/apps/web/tests/shipped-composition.e2e.ts b/apps/web/tests/shipped-composition.e2e.ts index 5d929044b3..8241dfc0bc 100644 --- a/apps/web/tests/shipped-composition.e2e.ts +++ b/apps/web/tests/shipped-composition.e2e.ts @@ -5,6 +5,7 @@ // surface itself. import { tmpdir } from 'node:os' import { afterEach, expect, it } from 'vitest' +import { CallId } from '@deepseek-ai/dsh-llm' import { canonicalPath, writableRoots } from '@deepseek-ai/dsh-sandbox' import { SessionId } from '@deepseek-ai/dsh-session' // Empty type imports carry the tools/sandboxPolicy/approval Context merges. @@ -114,3 +115,64 @@ it('assembles the shipped Web catalog with the confined access default', async ( await commandHandle.dispose() } }, 120_000) + +it('lets a preset producer reach the background-task registry', async () => { + scaffold = await launchWebScaffold() + const ctx = scaffold.ctx + const handle = await ctx.agents.create({ + sessionId: SessionId('shipped-background-task'), + meta: { cwd: scaffold.workspaceCwd }, + setup: agentCtx => ctx.agentPresets.mount(agentCtx).then(() => undefined), + }) + try { + const signal = new AbortController().signal + // `tool-bash` is a preset row and `tasks` is a host registry; the producer + // resolves it with `ctx.get`, so a registry hidden behind a preset realm + // fails here — with every task control still listed in the catalog above. + const started = await ctx.tools.execute({ + signal, + callId: CallId('shipped-bash-background'), + name: 'bash', + arguments: { + command: 'printf SHIPPED_BACKGROUND_OK', + description: 'shipped background probe', + run_in_background: true, + }, + agent: handle.agent, + }) + expect({ isError: started.isError, content: started.content }).toEqual({ + isError: false, + content: [{ type: 'text', text: 'started background task bash-1' }], + }) + + // The control surface reads what the producer started: same registry, one + // owner. A per-preset registry would list nothing here even on success. + const listed = await ctx.tools.execute({ + signal, + callId: CallId('shipped-task-list'), + name: 'task_list', + arguments: {}, + agent: handle.agent, + }) + expect(listed.isError).toBe(false) + expect(listed.content).toEqual([ + { type: 'text', text: expect.stringContaining('bash-1 [bash]') as unknown as string }, + ]) + + // The full round trip: the output a host-plane producer wrote is collected + // through a preset-plane control, which is the linkage the realm severed. + const collected = await ctx.tools.execute({ + signal, + callId: CallId('shipped-task-output'), + name: 'task_output', + arguments: { task_id: 'bash-1', wait: true }, + agent: handle.agent, + }) + expect(collected.isError).toBe(false) + expect(collected.content).toEqual([ + { type: 'text', text: expect.stringContaining('SHIPPED_BACKGROUND_OK') as unknown as string }, + ]) + } finally { + await handle.dispose() + } +}, 120_000) diff --git a/packages/bundle/web-app/cordis.patch.yml b/packages/bundle/web-app/cordis.patch.yml index e4c4935a2a..ed08451f1b 100644 --- a/packages/bundle/web-app/cordis.patch.yml +++ b/packages/bundle/web-app/cordis.patch.yml @@ -218,10 +218,17 @@ - id: tool-bash disabled: true -- id: tool-tasks - disabled: true +# The background-task REGISTRY stays on the host plane; only the model-facing +# `task_*` controls move. Its producers — `tool-bash` here, `tool-pty` and a +# non-continuable `tool-subagent` elsewhere — are preset rows that resolve it +# with `ctx.get`, and an entry-local realm around the registry is invisible to +# every sibling row outside that realm, so `run_in_background` answered +# "background tasks unavailable" while the controls sat in the catalog. That is +# the `goals` criterion read from inside the preset: a Service a row outside its +# realm READS belongs to the plane both can see. The registry is keyed by owning +# agent, so one host instance serves every session exactly as before presets. -- id: tasks +- id: tool-tasks disabled: true - id: tool-fs From e56c6234d2f4cdaa517f875fbddbc3b7c36181d3 Mon Sep 17 00:00:00 2001 From: Turtle Date: Mon, 10 Aug 2026 14:49:30 +0800 Subject: [PATCH 2/9] chore: exclude archived Agent Notes from rg --- .../process/2026-07-26-frozen-agent-note-archive.i18n.yaml | 4 ++-- .../process/2026-07-26-frozen-agent-note-archive.md | 6 +++++- .../process/2026-07-26-frozen-agent-note-archive.zh.md | 6 +++++- .rgignore | 2 ++ 4 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 .rgignore diff --git a/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.i18n.yaml b/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.i18n.yaml index 1ae66cc1b7..df1a615d35 100644 --- a/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.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/process/2026-07-26-frozen-agent-note-archive.md -2026-07-26-frozen-agent-note-archive.md: 52b43088b276c0c8e263fc8a81a2df1408cc8059 -2026-07-26-frozen-agent-note-archive.zh.md: a37e06e7cbc19b5000d4849cc3ee2ddc9b451ee3 +2026-07-26-frozen-agent-note-archive.md: 0c139c4a5d892de5bdace76b4935edf32586c4c1 +2026-07-26-frozen-agent-note-archive.zh.md: e67f981e5d7ae9d800b725e5ecbaf15dd7fef46b diff --git a/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.md b/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.md index 52b43088b2..0c139c4a5d 100644 --- a/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.md +++ b/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.md @@ -14,6 +14,8 @@ Only implemented Agent Notes can be archived. An implemented note moves when its The archive uses `.agents/notes/archived/{kind}/yyyy-mm-dd-topic.md`; the redundant `implemented` segment is absent. The archival change moves the complete English, Chinese, and consistency-sidecar triplet, leaves `Status: implemented` intact, and inserts `Archived: YYYY-MM-DD` immediately below it in both language files. Relocation, that metadata line, the corresponding sidecar re-record, and mechanical inbound-link repair are the only permitted archival edits. +The root `.rgignore` excludes the archive from searches that traverse a parent directory. Historical queries name the archive directory explicitly, so intentional access remains available without mixing frozen facts into active decision discovery. + After archival, the triplet is permanently frozen and is historical context rather than current authority. It is not updated for renamed packages, changed behavior, translation standards, formatting rules, broken outbound links, or later documentation contracts. Active prose may intentionally link into an archived note, redirect that link to current authority, or delete it. Repository gates therefore validate links into archived files but never treat archived files as link sources. [`verify-archived-agent-notes`](../../../../scripts/verify-archived-agent-notes.ts) owns the frozen boundary. It accepts only the closed set of Agent Note kinds, requires a complete triplet with implemented status and matching valid archive dates, verifies the sidecar against both current Git blob hashes, and seals every artifact by path and SHA-256 content hash in an append-only manifest. Its `--write` mode first proves every existing seal unchanged and then appends only newly archived artifacts. Pull-request CI supplies the trusted base SHA and checks out complete history before running the verifier, so a reused runner's shallow checkout cannot omit the baseline manifest. The ordinary Agent Note format, translation-pairing, wrapping, Markdown-link, package-path, Mermaid, documentation-TypeScript, and type-equivalence gates exclude archive sources; their evolving standards cannot create pressure to edit history. @@ -28,6 +30,8 @@ Supersession is checked while a new Agent Note is being written, not deferred to **Keep every implemented and rejected note active.** Rejected because maintenance effort and search noise grow with records that no longer help a future decision. Rejected notes in particular earn retention only by preventing a plausible fallacy. +**Leave archived notes in default repository search results.** Rejected because archived facts may be stale by design and can outrank current results by lexical match. Historical work can search the archive directory explicitly. + **Defer supersession cleanup to periodic corpus audits.** Rejected because the author of a replacement note has the freshest evidence about ownership and overlap. Postponement leaves redundant active authorities and makes later classification more expensive. **Archive rejected or proposed notes too.** Rejected because archive status means “implemented historical decision.” An obsolete proposal needs an explicit rejection, while a rejection with no guardrail value needs deletion rather than a second low-value holding area. @@ -38,4 +42,4 @@ Supersession is checked while a new Agent Note is being written, not deferred to ## Consequences -The active corpus becomes a set of decisions expected to influence future work, while low-value implemented history remains searchable and linkable without consuming maintenance attention. Writing a new note includes a scoped supersession check, so replacement decisions cannot silently leave redundant active records behind. Rejected clutter can disappear when it no longer protects a meaningful choice, and proposed work cannot quietly evade a verdict through archival. The archive adds a manifest, a dedicated verifier, and an explicit one-time metadata step. Archived facts and outbound links can become stale by design, so readers and agents must treat active code and documentation as authority and cite an archived note only as history. +The active corpus becomes a set of decisions expected to influence future work, while low-value implemented history remains explicitly searchable and linkable without consuming maintenance attention or appearing in parent-directory searches. Writing a new note includes a scoped supersession check, so replacement decisions cannot silently leave redundant active records behind. Rejected clutter can disappear when it no longer protects a meaningful choice, and proposed work cannot quietly evade a verdict through archival. The archive adds a manifest, a dedicated verifier, and an explicit one-time metadata step. Archived facts and outbound links can become stale by design, so readers and agents must treat active code and documentation as authority and cite an archived note only as history. diff --git a/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.zh.md b/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.zh.md index a37e06e7cb..e67f981e5d 100644 --- a/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.zh.md +++ b/.agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.zh.md @@ -14,6 +14,8 @@ implemented Agent Note 作为当前决策记录持续维护,因此活跃记录 归档路径为 `.agents/notes/archived/{kind}/yyyy-mm-dd-topic.md`,其中省略了冗余的 `implemented` 层级。归档变更会移动完整的英文、中文和一致性伴随记录三个文件,保留 `Status: implemented`,并在两种语言的文件中紧接该状态行插入 `Archived: YYYY-MM-DD`。归档时只允许做文件迁移、添加该元数据行、相应地重新记录伴随记录,以及机械修复入站链接。 +根目录的 `.rgignore` 会将归档目录排除在从上层目录开始的搜索之外。查找历史内容时会显式指定归档目录,因此仍可按需访问,同时不会把冻结事实混入对活跃决策的检索。 + 归档后,这三个文件永久冻结,只作为历史背景,不再是当前权威依据。不得因为包重命名、行为变化、翻译标准、格式规则、出站链接失效或后续文档约定而更新归档文件。活跃文档可以有意链接到归档 Agent Note,也可以把该链接重定向到当前权威依据,或直接删除。仓库门禁因此会校验指向归档文件的链接,但绝不把归档文件作为链接源来校验。 [`verify-archived-agent-notes`](../../../../scripts/verify-archived-agent-notes.ts) 负责维护冻结边界。它只接受封闭集合中的 Agent Note 类别,要求三个配对文件完整、状态为 implemented,且归档日期有效并互相匹配;它还会用双方当前的 Git blob hash 校验伴随记录,并在仅追加的 manifest(元数据清单) 中按路径和 SHA-256 内容 hash 封存每项产物。其 `--write` 模式会先证明每条现有封存记录对应的内容都未改变,再仅追加新归档的产物。拉取请求 CI 会提供可信的基准 SHA,并在运行校验器前检出完整历史,因此复用运行器上的浅克隆检出无法漏掉基线 manifest。普通的 Agent Note 格式、翻译配对、换行、Markdown 链接、包路径、Mermaid、文档 TypeScript 和类型等价门禁都排除归档源文件,因此这些门禁持续演进的标准不会产生修改历史记录的压力。 @@ -28,6 +30,8 @@ implemented Agent Note 作为当前决策记录持续维护,因此活跃记录 **继续将每一份 implemented 和 rejected Agent Note 作为活跃记录保留。** 不予采纳,因为不再帮助未来决策的记录会不断增加维护成本和搜索噪声。尤其是 rejected Agent Note,只有能避免一种可能发生的谬误时,才值得保留。 +**让归档 Agent Note 继续出现在默认的仓库搜索结果中。** 不予采纳,因为归档事实按设计可能已经陈旧,并可能仅凭字面匹配就排在当前结果之前。需要查找历史内容时,可以显式搜索归档目录。 + **把取代关系清理留到定期审计记录集合时再做。** 不予采纳,因为替代记录的作者掌握着关于归属和重叠的最新证据。推迟处理会留下冗余的活跃权威依据,并增加日后分类的成本。 **同时归档 rejected 或 proposed Agent Note。** 不予采纳,因为归档状态表达的是「已经实施的历史决策」。过时的提案需要明确转为 rejected;无法提供防错价值的 rejected Agent Note 则应删除,而不是再放入第二个低价值存放区。 @@ -38,4 +42,4 @@ implemented Agent Note 作为当前决策记录持续维护,因此活跃记录 ## 后果 -活跃记录集合由预计仍会影响未来工作的决策组成;未来指导价值较低的实施历史仍可搜索和链接,却不再消耗维护精力。编写新记录时会包含一项范围明确的取代关系检查,因此取代既有决策的新决策无法悄然留下冗余的活跃记录。当被否决的记录不再保护有意义的选择时,可以清除这类杂项;提案也无法通过归档悄悄逃避明确结论。归档机制增加一份 manifest、一个专用校验器和一个显式的一次性元数据步骤。归档中的事实和出站链接可以按设计逐渐陈旧,因此读者和 agent 必须以活跃代码与文档为权威依据,并且仅将归档 Agent Note 作为历史引用。 +活跃记录集合由预计仍会影响未来工作的决策组成;未来指导价值较低的实施历史仍可显式搜索和链接,却不再消耗维护精力,也不会出现在从上层目录开始的搜索中。编写新记录时会包含一项范围明确的取代关系检查,因此取代既有决策的新决策无法悄然留下冗余的活跃记录。当被否决的记录不再保护有意义的选择时,可以清除这类杂项;提案也无法通过归档悄悄逃避明确结论。归档机制增加一份 manifest、一个专用校验器和一个显式的一次性元数据步骤。归档中的事实和出站链接可以按设计逐渐陈旧,因此读者和 agent 必须以活跃代码与文档为权威依据,并且仅将归档 Agent Note 作为历史引用。 diff --git a/.rgignore b/.rgignore new file mode 100644 index 0000000000..6bffdc1726 --- /dev/null +++ b/.rgignore @@ -0,0 +1,2 @@ +# Frozen Agent Notes are historical snapshots, not current search authority. +/.agents/notes/archived/ From 37ebe87087c75d628e551775d90247ae211dbe23 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Mon, 10 Aug 2026 15:27:10 +0800 Subject: [PATCH 3/9] fix(tool-tasks): claim completion notices only for the mount's own scope MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moving the task registry to the host plane put every preset's `tool-tasks` listener on ONE `LocalTaskService`. `settle()` computes a single snapshot and walks every registered listener with no scope filter, and it marks `reported` only when a waiter is present — so a task settling without a waiter reached each mount's listener with `reported` false and every one of them injected the same completion into the same owner. Three shipped presets carry `tool-tasks`, and a preset file edit adds a second generation of the same mount, so an agent read N copies of one notice as model-visible durable context. A mount now claims an owner only when the owner's scope chain reaches the mount's own scope. An unscoped mount is the host-plane instance that serves every agent, which keeps the TUI composition and every existing test intact. Registry-side ownership was the alternative: mark `reported` once the first listener claims it. It is wrong because `onTaskDone` is not a notice-only seam — the `dsh-tasks` invariant companion registers a validating listener — so first-claim-wins would silence observers that are not delivering anything. The regression test mounts two scoped `tool-tasks` over one registry and settles an unowned-wait task, which is the only path that reaches the notice listeners at all: the shipped-composition e2e uses `wait: true`, and a waiter marks `reported` before settlement, so that test structurally cannot cover it. Also corrects the standing-mounts Agent Note, which still listed `tasks-local` among the stateful PRESET plugins. Refs #2141 --- ...08-08-per-preset-standing-mounts.i18n.yaml | 4 +- .../2026-08-08-per-preset-standing-mounts.md | 2 +- ...026-08-08-per-preset-standing-mounts.zh.md | 2 +- docs/config-catalog.i18n.yaml | 4 +- docs/config-catalog.md | 2 +- docs/config-catalog.zh.md | 2 +- docs/module-graph.i18n.yaml | 4 +- docs/module-graph.md | 3 +- docs/module-graph.zh.md | 3 +- packages/tasks/tool-tasks/README.i18n.yaml | 4 +- packages/tasks/tool-tasks/README.md | 2 + packages/tasks/tool-tasks/README.zh.md | 2 + packages/tasks/tool-tasks/package.json | 2 + packages/tasks/tool-tasks/src/index.ts | 12 ++++- .../tasks/tool-tasks/tests/tool-tasks.spec.ts | 49 +++++++++++++++++++ packages/tasks/tool-tasks/tsconfig.json | 3 ++ pnpm-lock.yaml | 3 ++ 17 files changed, 88 insertions(+), 15 deletions(-) diff --git a/.agents/notes/implemented/architecture/2026-08-08-per-preset-standing-mounts.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-08-per-preset-standing-mounts.i18n.yaml index d8c55c9f0a..410e209e28 100644 --- a/.agents/notes/implemented/architecture/2026-08-08-per-preset-standing-mounts.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-08-per-preset-standing-mounts.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/architecture/2026-08-08-per-preset-standing-mounts.md -2026-08-08-per-preset-standing-mounts.md: 834d645f5f293a2e137b8faf662e301f1e8bb971 -2026-08-08-per-preset-standing-mounts.zh.md: 45ce0f4e7dec28e5bf807898dc9cdbf32b8e4eb5 +2026-08-08-per-preset-standing-mounts.md: c2792454f90a88cd6fba36eed8e36104e5fffea4 +2026-08-08-per-preset-standing-mounts.zh.md: 47668c8c2c424eb188aa14bf55986d27bcfb8ee0 diff --git a/.agents/notes/implemented/architecture/2026-08-08-per-preset-standing-mounts.md b/.agents/notes/implemented/architecture/2026-08-08-per-preset-standing-mounts.md index 834d645f5f..c2792454f9 100644 --- a/.agents/notes/implemented/architecture/2026-08-08-per-preset-standing-mounts.md +++ b/.agents/notes/implemented/architecture/2026-08-08-per-preset-standing-mounts.md @@ -16,7 +16,7 @@ A preset is one composition per PROCESS, not one per session. The roster mounts Standing mounts fix the class, not the instances: the registrations a reader needs exist for the process lifetime, keyed by preset id, no agent required. What made it cheap -- The stateful preset plugins (`plan-mode`, `token-meter`, `compact-basic`, `tasks-local`) already key state by `Session`/`Agent` — they predate presets. Sharing one instance is a return to their design, not a rewrite. +- The stateful preset plugins (`plan-mode`, `token-meter`, `compact-basic`) already key state by `Session`/`Agent` — they predate presets. Sharing one instance is a return to their design, not a rewrite. `tasks-local` shared that property and has since left the preset plane entirely: producers outside its realm (`tool-bash`, `tool-pty`, a non-continuable `tool-subagent`) resolve the registry with `ctx.get`, which an entry-local realm hides from them, so it is composed on the host plane and only the model-facing `tool-tasks` row stays per preset. - Preset ymls are unchanged: one mount per preset = one Entry per preset, whose entry-local realms (`isolate: : true`) keep two presets' same-named services apart exactly as they kept two sessions' apart. - A shared realm label was NOT an option: `provide()` throws on a second registration under the same realm symbol, so labels pool the REALM, never the instance — a per-session world sharing a label crashes the second mount. diff --git a/.agents/notes/implemented/architecture/2026-08-08-per-preset-standing-mounts.zh.md b/.agents/notes/implemented/architecture/2026-08-08-per-preset-standing-mounts.zh.md index 45ce0f4e7d..47668c8c2c 100644 --- a/.agents/notes/implemented/architecture/2026-08-08-per-preset-standing-mounts.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-08-per-preset-standing-mounts.zh.md @@ -16,7 +16,7 @@ Status: implemented 常驻挂载修的是这一类问题而非其中的个例:读取方需要的注册在进程生命周期内始终存在,按 preset id 索引,不需要任何 agent。让它便宜的原因: -- 有状态的 preset 插件(`plan-mode`、`token-meter`、`compact-basic`、`tasks-local`)本就按 `Session`/`Agent` 分键存状态——它们早于 preset 存在。共享一份实例是回归其设计,不是改写。 +- 有状态的 preset 插件(`plan-mode`、`token-meter`、`compact-basic`)本就按 `Session`/`Agent` 分键存状态——它们早于 preset 存在。共享一份实例是回归其设计,不是改写。`tasks-local` 同样具备该性质,且此后已完全离开 preset 平面:realm 之外的生产方(`tool-bash`、`tool-pty`、非 continuable 的 `tool-subagent`)以 `ctx.get` 解析该注册表,而 entry-local realm 对它们不可见,因此它组合在宿主平面,只有面向模型的 `tool-tasks` 行仍留在各 preset 中。 - preset 的 yml 不变:每 preset 挂一次 = 每 preset 一个 Entry,其 entry 本地 realm(`isolate: : true`)让两个 preset 的同名服务互不相干,正如它从前隔开两个会话。 - 共享 realm label **不是**选项:`provide()` 对同一 realm 符号下的第二次注册直接抛错,label 池化的是 REALM 而非实例——按会话挂载的世界里共享 label 会让第二次挂载崩溃。 diff --git a/docs/config-catalog.i18n.yaml b/docs/config-catalog.i18n.yaml index 60c6e85cca..680f6afa42 100644 --- a/docs/config-catalog.i18n.yaml +++ b/docs/config-catalog.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 docs/config-catalog.md -config-catalog.md: 18980d22c694647374b9fa4e6dfbf245ff2416c4 -config-catalog.zh.md: a43c561806498ca53a95af815d0cd7686a0100ca +config-catalog.md: f4d275393dd918f1391d33db19222e4e62e80b96 +config-catalog.zh.md: 9b3cf689c8d4c23d1cca1260b7e74c911558d678 diff --git a/docs/config-catalog.md b/docs/config-catalog.md index 18980d22c6..f4d275393d 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -2332,7 +2332,7 @@ export interface Config { } ``` -Source: [`packages/tasks/tool-tasks/src/index.ts:23`](../packages/tasks/tool-tasks/src/index.ts) +Source: [`packages/tasks/tool-tasks/src/index.ts:24`](../packages/tasks/tool-tasks/src/index.ts) ## `@deepseek-ai/dsh-tool-todo` diff --git a/docs/config-catalog.zh.md b/docs/config-catalog.zh.md index a43c561806..9b3cf689c8 100644 --- a/docs/config-catalog.zh.md +++ b/docs/config-catalog.zh.md @@ -2333,7 +2333,7 @@ export interface Config { } ``` -来源:[`packages/tasks/tool-tasks/src/index.ts:23`](../packages/tasks/tool-tasks/src/index.ts) +来源:[`packages/tasks/tool-tasks/src/index.ts:24`](../packages/tasks/tool-tasks/src/index.ts) ## `@deepseek-ai/dsh-tool-todo` diff --git a/docs/module-graph.i18n.yaml b/docs/module-graph.i18n.yaml index 54453b3411..229c8d1cd6 100644 --- a/docs/module-graph.i18n.yaml +++ b/docs/module-graph.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 docs/module-graph.md -module-graph.md: c41db02165740b19a9ef751e6f50316a76df28c3 -module-graph.zh.md: 9071dbc0f2e6df8ec7edd1f3e14cd7ff063123fa +module-graph.md: 0f76bfd2dd700d81e2c6fb6faec3d2c0c9655e98 +module-graph.zh.md: 7fa0eb72666e63e72109a272cdc9ce323c60d2fd diff --git a/docs/module-graph.md b/docs/module-graph.md index c41db02165..0f76bfd2dd 100644 --- a/docs/module-graph.md +++ b/docs/module-graph.md @@ -947,6 +947,7 @@ flowchart TD pkg_tool_tasks --> pkg_invariants pkg_tool_tasks --> pkg_llm pkg_tool_tasks --> pkg_retention + pkg_tool_tasks --> pkg_scope pkg_tool_tasks --> pkg_system_prompt pkg_tool_tasks --> pkg_tasks pkg_tool_tasks --> pkg_tools @@ -1390,7 +1391,7 @@ flowchart TD | [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | `session` | [`brand`](../packages/util/brand), [`command-feedback`](../packages/feedback/command-feedback), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`paths`](../packages/util/paths), [`session`](../packages/core/session), [`session-telemetry`](../packages/session/session-telemetry) | | [`session-title-all-messages-llm`](../packages/session/session-title-all-messages-llm) | `session` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) | | [`session-title-first-message-llm`](../packages/session/session-title-first-message-llm) | `session` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) | -| [`tool-tasks`](../packages/tasks/tool-tasks) | `tasks` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) | +| [`tool-tasks`](../packages/tasks/tool-tasks) | `tasks` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`scope`](../packages/core/scope), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) | | [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | | [`tool-bash`](../packages/bash/tool-bash) | `bash` | [`agent`](../packages/core/agent), [`bash`](../packages/bash/bash), [`bash-env`](../packages/bash/bash-env), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | | [`tool-pwsh`](../packages/bash/tool-pwsh) | `bash` | [`agent`](../packages/core/agent), [`bash`](../packages/bash/bash), [`bash-env`](../packages/bash/bash-env), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | diff --git a/docs/module-graph.zh.md b/docs/module-graph.zh.md index 9071dbc0f2..7fa0eb7266 100644 --- a/docs/module-graph.zh.md +++ b/docs/module-graph.zh.md @@ -949,6 +949,7 @@ flowchart TD pkg_tool_tasks --> pkg_invariants pkg_tool_tasks --> pkg_llm pkg_tool_tasks --> pkg_retention + pkg_tool_tasks --> pkg_scope pkg_tool_tasks --> pkg_system_prompt pkg_tool_tasks --> pkg_tasks pkg_tool_tasks --> pkg_tools @@ -1392,7 +1393,7 @@ flowchart TD | [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | `session` | [`brand`](../packages/util/brand), [`command-feedback`](../packages/feedback/command-feedback), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`paths`](../packages/util/paths), [`session`](../packages/core/session), [`session-telemetry`](../packages/session/session-telemetry) | | [`session-title-all-messages-llm`](../packages/session/session-title-all-messages-llm) | `session` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) | | [`session-title-first-message-llm`](../packages/session/session-title-first-message-llm) | `session` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) | -| [`tool-tasks`](../packages/tasks/tool-tasks) | `tasks` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) | +| [`tool-tasks`](../packages/tasks/tool-tasks) | `tasks` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`scope`](../packages/core/scope), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) | | [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | | [`tool-bash`](../packages/bash/tool-bash) | `bash` | [`agent`](../packages/core/agent), [`bash`](../packages/bash/bash), [`bash-env`](../packages/bash/bash-env), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | | [`tool-pwsh`](../packages/bash/tool-pwsh) | `bash` | [`agent`](../packages/core/agent), [`bash`](../packages/bash/bash), [`bash-env`](../packages/bash/bash-env), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | diff --git a/packages/tasks/tool-tasks/README.i18n.yaml b/packages/tasks/tool-tasks/README.i18n.yaml index 24e6b6892a..8c97357246 100644 --- a/packages/tasks/tool-tasks/README.i18n.yaml +++ b/packages/tasks/tool-tasks/README.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 packages/tasks/tool-tasks/README.md -README.md: 6e8e889c2330d6991cb384674b011e4d2e988268 -README.zh.md: 355b6736b476fb2434f17ea3857544f32c40adb3 +README.md: 1b63ba7124e9bdbfbf64d70e36e90d1ff13a27c8 +README.zh.md: 946ba9156c4a9d8902f8c47deb6056b2f6525f86 diff --git a/packages/tasks/tool-tasks/README.md b/packages/tasks/tool-tasks/README.md index 6e8e889c23..1b63ba7124 100644 --- a/packages/tasks/tool-tasks/README.md +++ b/packages/tasks/tool-tasks/README.md @@ -20,6 +20,8 @@ When a producer supplies `outputLimitBytes`, `task_output`, terminal `task_kill` An unreported completion injects `background task (: