docs(notes): rewrite the three delivery notes to the shipped mechanisms
The timeout-promotion, workflow run_in_background, and human-job-kill notes kept their bodies in the present tense over ctx.activities, RunningJob/updateDetail, and the registry's reported bit, with Update headers doing the correcting. Their mechanism sentences now state the shipped facts (observed streams as pull sources, JobHandle/updateProgress, the tool-jobs delivery ledger), the Update headers shrink to cross-links, and the testing paragraph names the current specs.
This commit is contained in:
1 parent
9fe106693c
commit
15cbcad1eb
9 files changed
+24
-24
No files matched your search
@@ -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/feature/2026-08-26-human-job-kill.md
|
||||
2026-08-26-human-job-kill.md: 35f0c256ce1debe6fdc86f23d7e07fcf5a0f6580
|
||||
2026-08-26-human-job-kill.zh.md: b641c80eaf1b515835e8980174f42846374d186f
|
||||
2026-08-26-human-job-kill.md: b82a2f2ad1d79cf070a09809138019a30e06d6c2
|
||||
2026-08-26-human-job-kill.zh.md: 7efd504f7eccf1923ebebcef4c22510ac8bc3826
|
||||
@@ -12,25 +12,25 @@ The [web job display note](2026-08-08-web-background-job-display.md) shipped the
|
||||
|
||||
## Decision
|
||||
|
||||
The `reported` bit means "the terminal state has a committed delivery path to the model", so the fix is to stop conflating cancellation with claiming that delivery.
|
||||
A delivery claim means "the terminal state has a committed delivery path to the model", so the fix is to stop conflating cancellation with claiming that delivery.
|
||||
|
||||
- **`kill(id, caller?, options?)` takes `{ reason?, reported? }`.** `reported` defaults to `true` — the exact previous semantics for the model's `job_kill`. `reported: false` withholds this kill's claim, so the settlement flows through the existing completion reporter (wakeup/inject, wake budget, truncation all unchanged). It never clears an existing claim: a job the model already killed stays reported, and a terminal record stays exactly as settlement left it.
|
||||
- **A human kill claims no delivery.** `VisibleJobs.kill(id, { reason? })` records the reason only; the delivery ledger lives in `dsh-tool-jobs`, which claims a job for the model's own `job_kill` and for its waits, so a `job.kill` from the task list leaves the settlement to the existing completion reporter (wakeup/inject, wake budget, truncation all unchanged). It never clears a claim the model already holds, and a terminal record stays exactly as settlement left it.
|
||||
- **A recorded kill reason merges into a `killed` settlement's detail** — producer facts first (`signal: SIGTERM; cancelled by the user`) — so the completion notice and the web row both say who stopped the work without a new snapshot field or notice template. A job that outruns its kill (settles `completed`/`failed`) keeps the producer detail alone. This is the reconciliation Codex spells out in its `<turn_aborted>` guidance: when the user stops something, the model is told rather than left to infer.
|
||||
- **`job.kill` is a Job Controller Remote** (`@deepseek-ai/dsh-api-job-controller`, beside `job.observe`; it began life as `session.killJob` on the Session Controller and moved with the observation stream when that package split out) with the same live-only Agent lookup (`ctx.agents.get`) as `session.cancel`: a running job's owner is alive by the registry's ownership contract, and killing from a list must not revive a Session any more than listing does. Unknown and foreign jobs collapse to one `job/not-found` rejection scoped to the lookup — a producer-cancel throw propagates per the registry contract instead of masquerading as a lookup failure; the controller requires `ctx.jobs` to load, so a composition without a registry has no kill Remote at all, and the Session Controller's subagent ownership fence applies exactly as it does to `cancel`. No approval interaction: this single-user local BFF treats it as the same class of action as the turn-cancel button.
|
||||
- **The task list's stop control is two-press** (arm, then confirm within 3s, matching the Kimi `s`+`y` and OpenCode double-Esc shape), rendered only on running job rows — rows with a `jobId`. Standalone activity rows (workflow runs) have no kill handle and no button. The pending press disables the control and stays pending on an admitted kill — the unary response and the jobs frames have no cross-carrier ordering, so only the authoritative frame (the row leaving the killable set) releases it; a rejected kill shows a brief hint. Because the kill addresses `ctx.jobs` by id, every job kind gains the control at once — bash, pwsh, pty, and one-shot background subagents.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **A `notify: boolean` option** — rejected because the registry cannot promise a notice (quiet delivery, unowned jobs, owner teardown all legitimately drop one); `reported` names the fact the registry actually controls, in the vocabulary `JobSnapshot.reported` already documents.
|
||||
- **A `notify: boolean` option** — rejected because the registry cannot promise a notice (quiet delivery, unowned jobs, owner teardown all legitimately drop one); the ledger's claim names the fact the tool actually controls: whether one of its own tool results already delivered the terminal state.
|
||||
- **A separate `interrupt()`/`stopByUser()` method** — one cancellation path with an explicit claim beats two methods whose only difference is a boolean, and Kimi's split (`stop` vs `stopByUser`) shipped with its TUI calling the wrong one.
|
||||
- **Saying nothing to the model (Claude Code's shape)** — rejected; their own source comments question it, and the DSH blocker note recorded the stale-belief failure as the reason the control did not ship earlier.
|
||||
|
||||
## Testing
|
||||
|
||||
`jobs-local` pins the unreporting kill (notice stays due, existing claims survive, terminal records untouched), the detail merge on `killed`, and the outran-kill case; `tool-jobs` pins the delivered notice text verbatim, reason included. `kill-job.host.spec.ts` pins the Remote command: admission, unowned-job kill from an agentless session, `job-not-found` for unknown/foreign, `jobs-unavailable` without the registry. The client suites pin the RPC passthrough and the two-press control (arm, confirm, disarm timer, failure hint, single armed row, stale-phase cleanup); the keyless web e2e drives the button end to end.
|
||||
`tool-jobs` pins the ledger (a kill the model did not request leaves the notice due; its own `job_kill` and waits claim it) and the delivered notice text verbatim, reason included; `jobs-local` pins the detail merge on `killed` and the outran-kill case. `kill.host.spec.ts` pins the Remote command: admission, unowned-job kill from an agentless session, `job/not-found` for unknown/foreign. The client suites pin the RPC passthrough and the two-press control (arm, confirm, disarm timer, failure hint, single armed row, stale-phase cleanup); the keyless web e2e drives the button end to end.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The completion notice for a human kill wakes an idle owner (default `wakeup` delivery) — a deliberate cost: an unclaimed completion the model never learns about is the failure this note exists to fix.
|
||||
- `kill`'s positional `reason` parameter is gone (pre-release, no shim); the options object is the only form.
|
||||
- A killed job's `detail` may now carry two clauses joined by `; `. Anything parsing `detail` as a single producer fact must treat it as opaque text, which `JobSnapshot.detail` always declared it to be.
|
||||
- A killed job's `detail` may now carry two clauses joined by `; `. Anything parsing `detail` as a single producer fact must treat it as opaque text, which `JobView.detail` always declared it to be.
|
||||
@@ -12,25 +12,25 @@ Update:本文引入的 `reported` 选项已随 [jobs seam 收敛](../architect
|
||||
|
||||
## 决策
|
||||
|
||||
`reported` 位的本义是「终态已有一条向模型送达的承诺路径」,所以修法是不再把「取消」与「认领这次送达」混为一谈。
|
||||
送达认领的本义是「终态已有一条向模型送达的承诺路径」,所以修法是不再把「取消」与「认领这次送达」混为一谈。
|
||||
|
||||
- **`kill(id, caller?, options?)` 接受 `{ reason?, reported? }`。** `reported` 默认 `true`——即模型 `job_kill` 的原有语义。`reported: false` 只是不提出本次 kill 的认领,结算随即照常走完成播报员(唤醒/注入、唤醒预算、截断全部不变)。它绝不清除已有认领:模型已 kill 过的任务保持 reported,终态记录也保持结算时的原样。
|
||||
- **人类的 kill 不认领送达。** `VisibleJobs.kill(id, { reason? })` 只记录原因;送达台账在 `dsh-tool-jobs` 里,它只为模型自己的 `job_kill` 与等待认领任务,因此来自任务列表的 `job.kill` 让结算照常走完成播报员(唤醒/注入、唤醒预算、截断全部不变)。它绝不清除模型已持有的认领,终态记录也保持结算时的原样。
|
||||
- **记录下来的 kill reason 合入 `killed` 结算的 detail**——生产者事实在前(`signal: SIGTERM; cancelled by the user`)——完成通知与 Web 行因此都能说清是谁停的工作,无需新快照字段或新通知模板。跑赢 kill 的任务(结算为 `completed`/`failed`)只保留生产者 detail。这正是 Codex 在 `<turn_aborted>` 指引里写明的对账原则:用户停掉了什么,要告诉模型,而不是让它去猜。
|
||||
- **`job.kill` 是 Job Controller 的 Remote**(`@deepseek-ai/dsh-api-job-controller`,与 `job.observe` 并列;它最初是 Session Controller 上的 `session.killJob`,该包拆出时随观测流一起迁走),与 `session.cancel` 一样只查活体 Agent(`ctx.agents.get`):按注册表的所有权契约,在跑任务的 owner 必然活着;从列表里 kill 与列表本身一样绝不复活 Session。未知与他人任务合并为一个只作用于查找的 `job/not-found` 拒绝——生产者 cancel 抛错按注册表契约原样传播,绝不伪装成查找失败;控制器加载就要求 `ctx.jobs`,没有注册表的组合根本没有 kill Remote,Session Controller 的 subagent 所有权栅栏与 `cancel` 完全一致地适用。不加 approval 交互:这个单用户本地 BFF 把它视为与停轮按钮同级的操作。
|
||||
- **任务列表的停止控件为两击式**(先武装、3 秒内确认,对齐 Kimi 的 `s`+`y` 与 OpenCode 的双 Esc),只渲染在运行中的 job 行——即带 `jobId` 的行。独立 activity 行(workflow 运行)没有 kill 句柄也就没有按钮。请求进行中按钮禁用,且受理成功后保持 pending——unary 响应与 jobs 帧没有跨载体顺序保证,只有权威帧(行离开可杀集合)才释放控件;被拒绝的 kill 短暂提示。由于 kill 按 id 寻址 `ctx.jobs`,所有任务 kind 一次性获得该控件——bash、pwsh、pty 与一次性后台 subagent。
|
||||
|
||||
## 备选方案
|
||||
|
||||
- **`notify: boolean` 选项**——否决:注册表承诺不了通知(quiet 投递、无主任务、owner 拆除都会正当地丢弃一条);`reported` 命名的是注册表真正控制的事实,用的是 `JobSnapshot.reported` 已有的词汇。
|
||||
- **`notify: boolean` 选项**——否决:注册表承诺不了通知(quiet 投递、无主任务、owner 拆除都会正当地丢弃一条);台账的认领命名的是工具真正控制的事实:它自己的某个 tool result 是否已经送达了终态。
|
||||
- **独立的 `interrupt()`/`stopByUser()` 方法**——一条取消路径带显式认领胜过两个只差一个布尔的方法;Kimi 的拆分(`stop` 与 `stopByUser`)上线时 TUI 就调错了那个。
|
||||
- **对模型保持沉默(Claude Code 的形状)**——否决;他们自己的源码注释都在质疑它,而 DSH 的阻塞 Note 正是把过期认知失败记为该控件此前不能上线的原因。
|
||||
|
||||
## 测试
|
||||
|
||||
`jobs-local` 钉住不认领 kill(通知仍欠、既有认领存续、终态记录不动)、`killed` 的 detail 合并与跑赢 kill 的情形;`tool-jobs` 逐字钉住带原因的投递通知文本。`kill-job.host.spec.ts` 钉住 Remote 命令:受理、无 agent 会话 kill 无主任务、未知/他人的 `job-not-found`、无注册表的 `jobs-unavailable`。客户端套件钉住 RPC 透传与两击控件(武装、确认、解除计时、失败提示、单一武装行、过期相位清理);无密钥 web e2e 端到端驱动按钮。
|
||||
`tool-jobs` 钉住台账(不是模型请求的 kill 让通知保持待送达;它自己的 `job_kill` 与等待认领它)与逐字的带原因投递通知文本;`jobs-local` 钉住 `killed` 的 detail 合并与跑赢 kill 的情形。`kill.host.spec.ts` 钉住 Remote 命令:受理、无 agent 会话 kill 无主任务、未知/他人的 `job/not-found`。客户端套件钉住 RPC 透传与两击控件(武装、确认、解除计时、失败提示、单一武装行、过期相位清理);无密钥 web e2e 端到端驱动按钮。
|
||||
|
||||
## 后果
|
||||
|
||||
- 人工 kill 的完成通知会唤醒空闲 owner(默认 `wakeup` 投递)——这是刻意的代价:模型永远学不到的未认领完成,正是本 Note 要修的失败。
|
||||
- `kill` 的位置参数 `reason` 已删除(pre-release,无垫片);options 对象是唯一形式。
|
||||
- 被 kill 任务的 `detail` 可能携带以 `; ` 连接的两个子句。任何把 `detail` 当单一生产者事实解析的代码都必须把它当不透明文本——`JobSnapshot.detail` 一直如此声明。
|
||||
- 被 kill 任务的 `detail` 可能携带以 `; ` 连接的两个子句。任何把 `detail` 当单一生产者事实解析的代码都必须把它当不透明文本——`JobView.detail` 一直如此声明。
|
||||
+2
-2
@@ -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/feature/2026-08-26-shell-execute-timeout-promotion.md
|
||||
2026-08-26-shell-execute-timeout-promotion.md: 594a9c8e1babdadd0a78b0c61c009c4a3d486bce
|
||||
2026-08-26-shell-execute-timeout-promotion.zh.md: 048f82d5a3b2e5a49edd0f8ac2849236c3273b41
|
||||
2026-08-26-shell-execute-timeout-promotion.md: 07bb9bf581ae21f7543dc66d541fbc4a3bd34c8a
|
||||
2026-08-26-shell-execute-timeout-promotion.zh.md: b3435d6caabc7aa7a97606e52b7c5a1b0b517469
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Status: implemented
|
||||
|
||||
Update: with the [jobs seam consolidation](../architecture/2026-09-03-jobs-seam-consolidation.md) the promoted job feeds the registry's output ring through pull sources that start at the observed streams' current ends. The promoted result still carries the output so far through one consuming read; the ring holds only bytes produced after the promotion, so neither the model's next `job_output` nor the Web panel repeats that output.
|
||||
Update: the [jobs seam consolidation](../architecture/2026-09-03-jobs-seam-consolidation.md) owns the output ring, the cursors, and the pull sources the promoted job now feeds.
|
||||
|
||||
English | [中文](2026-08-26-shell-execute-timeout-promotion.zh.md)
|
||||
|
||||
@@ -20,7 +20,7 @@ Peer evidence pointed one way. Kimi promotes on timeout by default (`bashAutoBac
|
||||
|
||||
**Spawn failures are contained uniformly**: a synchronous spawn throw and an asynchronous rejection both settle the handle as `killed` with the note on the read path, while `result()` rejects with the original error (identity preserved — the sandbox executors map runner-attributed failures to `SANDBOX_UNAVAILABLE` in their `result()` decoration and stamp handle facts in `onProcessDone`, both keyed on the one handle instance).
|
||||
|
||||
**`tool-bash`/`tool-pwsh` promote on timeout by default** (`promoteOnTimeout: true`, requiring `enableRunInBackground` and a live `ctx.jobs`). The threshold is the existing `timeoutMs` — expiry becomes the trigger, exactly as Kimi and Claude Code redefined it; no second knob exists. At the offer the tool registers the running handle as a job with the same three hooks as `run_in_background`, mirrors it into `ctx.activities` (so the Web task list streams it, stop control included), takes one consuming read for the result, and returns `{ kind: 'promoted', jobId, timeoutMs, output }` rendered as `[still running after <ms>; moved to background job <id>]` plus hand-off guidance. The cursor continues exactly after the embedded output — Kimi's shape; Claude Code returns only a file pointer because its output identity is a file, DSH's is the job cursor. Any promotion failure (admission, controller preflight) declines the offer and falls back to the plain timeout kill, logged. Completion later flows through the existing tool-jobs notice, so the model is told without polling.
|
||||
**`tool-bash`/`tool-pwsh` promote on timeout by default** (`promoteOnTimeout: true`, requiring `enableRunInBackground` and a live `ctx.jobs`). The threshold is the existing `timeoutMs` — expiry becomes the trigger, exactly as Kimi and Claude Code redefined it; no second knob exists. At the offer the tool registers the running handle as a job with the same three hooks as `run_in_background`, hands the registry the handle's observed streams as pull sources starting at their current offsets (so the Web task list streams it through `job.rows` and `job.observe`, stop control included, and the ring holds only bytes produced after the promotion), takes one consuming read for the result, and returns `{ kind: 'promoted', jobId, timeoutMs, output }` rendered as `[still running after <ms>; moved to background job <id>]` plus hand-off guidance. The cursor continues exactly after the embedded output — Kimi's shape; Claude Code returns only a file pointer because its output identity is a file, DSH's is the job cursor. Any promotion failure (admission, controller preflight) declines the offer and falls back to the plain timeout kill, logged. Completion later flows through the existing tool-jobs notice, so the model is told without polling.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -31,7 +31,7 @@ Peer evidence pointed one way. Kimi promotes on timeout by default (`bashAutoBac
|
||||
|
||||
## Testing
|
||||
|
||||
Executor level (real processes): offer at deadline; accept detaches timer and caller signal (an abort after accept does not kill; `kill()` does); decline classifies `timedOut`; settle-first resolves `undefined`; unanswered auto-declines; pre-offer abort classifies `aborted`; `'none'`-policy classification; sync-throw containment with error identity through `result()` (sandbox suites). Tool level: a real `printf …; sleep 30` with `timeoutMs: 250` promotes end to end — job registered, activity mirrored with the call correlation, cursor continues past the embedded output, `job_kill` works; admission saturation falls back with the warn; `promoteOnTimeout: false` keeps the kill and drops the description sentence; pwsh mirrors via scripted offers. Render texts are pinned verbatim.
|
||||
Executor level (real processes): offer at deadline; accept detaches timer and caller signal (an abort after accept does not kill; `kill()` does); decline classifies `timedOut`; settle-first resolves `undefined`; unanswered auto-declines; pre-offer abort classifies `aborted`; `'none'`-policy classification; sync-throw containment with error identity through `result()` (sandbox suites). Tool level: a real `printf …; sleep 30` with `timeoutMs: 250` promotes end to end — job registered, ring fed from the promotion offsets, cursor continues past the embedded output, `job_kill` works; admission saturation falls back with the warn; `promoteOnTimeout: false` keeps the kill and drops the description sentence; pwsh mirrors via scripted offers. Render texts are pinned verbatim.
|
||||
|
||||
## Consequences
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
状态:已实现
|
||||
|
||||
Update:随 [jobs seam 收敛](../architecture/2026-09-03-jobs-seam-consolidation.zh.md),被提升的 job 通过从观测流当前末尾开始的拉取源填充注册表的输出环。提升结果仍通过一次消耗式读取携带迄今为止的输出;环只保有提升之后产生的字节,因此模型的下一次 `job_output` 与 Web 面板都不会重复这段输出。
|
||||
Update:[jobs seam 收敛](../architecture/2026-09-03-jobs-seam-consolidation.zh.md)拥有被提升的 job 现在填充的输出环、游标与拉取源。
|
||||
|
||||
[English](2026-08-26-shell-execute-timeout-promotion.md) | 中文
|
||||
|
||||
@@ -20,7 +20,7 @@ Update:随 [jobs seam 收敛](../architecture/2026-09-03-jobs-seam-consolidati
|
||||
|
||||
**spawn 失败统一收容**:同步抛错与异步 rejection 都把句柄结算为 `killed`、说明进入读路径,而 `result()` 以原始错误 reject(保留同一性——沙箱执行器在其 `result()` 装饰里把可归因于 runner 的失败映射为 `SANDBOX_UNAVAILABLE`,并在 `onProcessDone` 里盖章句柄事实,两者都以同一个句柄实例为键)。
|
||||
|
||||
**`tool-bash`/`tool-pwsh` 默认超时即转移**(`promoteOnTimeout: true`,要求 `enableRunInBackground` 与活的 `ctx.jobs`)。阈值就是现有的 `timeoutMs`——到期成为触发器,与 Kimi 和 Claude Code 的重定义完全一致;不存在第二个旋钮。收到 offer 时,工具用与 `run_in_background` 相同的三个钩子把在跑句柄注册为任务、镜像进 `ctx.activities`(Web 任务列表随即可流式观看,含停止控件)、做一次消费读嵌入结果,返回 `{ kind: 'promoted', jobId, timeoutMs, output }`,渲染为 `[still running after <ms>; moved to background job <id>]` 加交接指引。游标恰好从嵌入输出之后接续——Kimi 的形状;Claude Code 只回文件指针是因为其输出身份是文件,DSH 的是任务游标。任何转移失败(准入、控制器预检)都 decline 并回落到普通超时杀,且有日志。之后的完成经既有 tool-jobs 通知流动,模型无需轮询即被告知。
|
||||
**`tool-bash`/`tool-pwsh` 默认超时即转移**(`promoteOnTimeout: true`,要求 `enableRunInBackground` 与活的 `ctx.jobs`)。阈值就是现有的 `timeoutMs`——到期成为触发器,与 Kimi 和 Claude Code 的重定义完全一致;不存在第二个旋钮。收到 offer 时,工具用与 `run_in_background` 相同的三个钩子把在跑句柄注册为任务、把句柄的 observed 流从当前偏移起作为拉取源交给注册表(Web 任务列表经 `job.rows` 与 `job.observe` 随即可流式观看,含停止控件,且环只保有提升之后产生的字节)、做一次消费读嵌入结果,返回 `{ kind: 'promoted', jobId, timeoutMs, output }`,渲染为 `[still running after <ms>; moved to background job <id>]` 加交接指引。游标恰好从嵌入输出之后接续——Kimi 的形状;Claude Code 只回文件指针是因为其输出身份是文件,DSH 的是任务游标。任何转移失败(准入、控制器预检)都 decline 并回落到普通超时杀,且有日志。之后的完成经既有 tool-jobs 通知流动,模型无需轮询即被告知。
|
||||
|
||||
## 备选方案
|
||||
|
||||
@@ -31,7 +31,7 @@ Update:随 [jobs seam 收敛](../architecture/2026-09-03-jobs-seam-consolidati
|
||||
|
||||
## 测试
|
||||
|
||||
执行器层(真实进程):deadline 处给出 offer;accept 解绑计时器与调用方信号(accept 后 abort 不杀、`kill()` 杀);decline 归类 `timedOut`;先结束则 resolve `undefined`;未应答自动 decline;offer 前 abort 归类 `aborted`;`'none'` 策略归类;同步抛错收容且错误同一性穿过 `result()`(沙箱套件)。工具层:真实的 `printf …; sleep 30` 配 `timeoutMs: 250` 端到端转移——任务注册、activity 带调用关联镜像、游标越过嵌入输出接续、`job_kill` 有效;准入饱和回落并告警;`promoteOnTimeout: false` 保持杀并撤掉描述句;pwsh 以脚本化 offer 镜像。渲染文本逐字钉死。
|
||||
执行器层(真实进程):deadline 处给出 offer;accept 解绑计时器与调用方信号(accept 后 abort 不杀、`kill()` 杀);decline 归类 `timedOut`;先结束则 resolve `undefined`;未应答自动 decline;offer 前 abort 归类 `aborted`;`'none'` 策略归类;同步抛错收容且错误同一性穿过 `result()`(沙箱套件)。工具层:真实的 `printf …; sleep 30` 配 `timeoutMs: 250` 端到端转移——任务注册、环从提升偏移起填充、游标越过嵌入输出接续、`job_kill` 有效;准入饱和回落并告警;`promoteOnTimeout: false` 保持杀并撤掉描述句;pwsh 以脚本化 offer 镜像。渲染文本逐字钉死。
|
||||
|
||||
## 后果
|
||||
|
||||
|
||||
@@ -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/feature/2026-09-01-workflow-run-in-background.md
|
||||
2026-09-01-workflow-run-in-background.md: be4162cf384dd7a6907c2c4eddbf447464acf66e
|
||||
2026-09-01-workflow-run-in-background.zh.md: 2c71ca6476e301e6be686c954937d94095f19796
|
||||
2026-09-01-workflow-run-in-background.md: abc7158f935e7624fc6b69e202e1bf3c4693da91
|
||||
2026-09-01-workflow-run-in-background.zh.md: b626f1c57f9c27779242793d604d4d4b490ce08c
|
||||
@@ -16,7 +16,7 @@ The `workflow` tool gains `run_in_background: true` (exposed and accepted while
|
||||
|
||||
- **The run belongs to the job, not the tool step.** The engine run starts inside the job starter with no `exec.signal`; `job_kill`, the job list's stop control, and owner teardown are the cancellation paths, each forwarding its reason into `run.cancel`. A synchronous engine rejection (meta/parse failure) propagates out of the starter, so nothing registers and the model sees the ordinary correctable error.
|
||||
- **Settlement is the job's settlement.** `done` chains from `run.result`: dispose (a disposal failure warns and never rejects into the registry), stop the mirrors, then map the stop reason — `completed` carries the same rendered return value as the foreground path in `JobOutcome.output` (so the completion notice and `job_output` deliver it), `cancelled` settles `killed` and leaves the detail to the registry's kill-reason merge (the forwarded cancel reason is the same string), `error` settles `failed` with the script's failure message.
|
||||
- **The record mirror replaces the deleted activity mirror.** `src/record.ts` subscribes `workflow/phase`, `workflow/log`, and member lifecycle events once per plugin and routes them into tracked runs' `RunningJob` faces as the same text lines the activity mirror wrote; `updateDetail` tracks the current phase. There is no contained-error wrapper: `append`/`updateDetail` are non-throwing surfaces (post-settlement appends drop inside the registry), and a straggling event finds no tracked run.
|
||||
- **The record mirror replaces the deleted activity mirror.** `src/record.ts` subscribes `workflow/phase`, `workflow/log`, and member lifecycle events once per plugin and routes them into tracked runs' `JobHandle` faces as the same text lines the activity mirror wrote; `updateProgress` tracks the current phase. There is no contained-error wrapper: `append`/`updateProgress` are non-throwing surfaces (post-settlement appends drop inside the registry), and a straggling event finds no tracked run.
|
||||
- **The output schema becomes a `kind`-discriminated union** (`background` | `foreground`), matching bash's shape; the foreground envelope gains `kind: 'foreground'` for symmetry. The durable run-start/member/run-end session recording keeps its `exec.parent === undefined` gate on both paths, with the background run-end written from the job's `done` chain.
|
||||
- **`workflow` joins `JobKindMap`** by declaration merging from the tool package, like `pwsh` and `pty`.
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ Update:随 [jobs seam 收敛](../architecture/2026-09-03-jobs-seam-consolidati
|
||||
|
||||
- **运行属于任务,不属于工具步骤。** 引擎运行在任务 starter 内启动且不带 `exec.signal`;取消路径是 `job_kill`、任务列表的停止控件与 owner 释放,各自把原因转发进 `run.cancel`。引擎的同步拒绝(meta/解析失败)从 starter 传播出去,因此什么都不注册,模型看到的是普通的可修正错误。
|
||||
- **结算即任务结算。** `done` 从 `run.result` 链起:先 dispose(释放失败只告警,绝不 reject 进注册表),再停掉镜像,最后映射结束原因——`completed` 在 `JobOutcome.output` 里携带与前台路径相同的渲染返回值(完成播报与 `job_output` 由此送达它),`cancelled` 结算为 `killed` 并把 detail 留给注册表的 kill 原因合并(转发的取消原因就是同一字符串),`error` 以脚本失败消息结算为 `failed`。
|
||||
- **record 镜像顶替被删的 activity 镜像。** `src/record.ts` 按插件订阅一次 `workflow/phase`、`workflow/log` 与成员生命周期事件,并把它们以 activity 镜像当年写下的同样文本行路由进被跟踪运行的 `RunningJob` 面;`updateDetail` 跟随当前 phase。这里没有收容错误的包装层:`append`/`updateDetail` 是不抛出的表面(结算后的 append 在注册表内丢弃),迟到的事件找不到被跟踪的运行。
|
||||
- **record 镜像顶替被删的 activity 镜像。** `src/record.ts` 按插件订阅一次 `workflow/phase`、`workflow/log` 与成员生命周期事件,并把它们以 activity 镜像当年写下的同样文本行路由进被跟踪运行的 `JobHandle` 面;`updateProgress` 跟随当前 phase。这里没有收容错误的包装层:`append`/`updateProgress` 是不抛出的表面(结算后的 append 在注册表内丢弃),迟到的事件找不到被跟踪的运行。
|
||||
- **输出 schema 变为 `kind` 判别联合**(`background` | `foreground`),与 bash 的形态一致;前台包络为对称获得 `kind: 'foreground'`。持久的 run-start/成员/run-end 会话记录在两条路径上都保留 `exec.parent === undefined` 门槛,后台的 run-end 从任务的 `done` 链写出。
|
||||
- **`workflow` 进入 `JobKindMap`**,由工具包声明合并,与 `pwsh`、`pty` 相同。
|
||||
|
||||
|
||||
Reference in new issue
Block a user