@akagilnc/pi-workflow-roles 0.1.2336 → 0.1.2348

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @akagilnc/pi-workflow-roles
2
2
 
3
- Packaged workflow roles for [Pi](https://pi.dev): `judge`, `fixer`, `coder`, `reviewer`, `collector`, `doctor`, `merger`. 中文说明见 [README.zh-CN.md](https://github.com/Akagilnc/ak-pi-workflow-roles/blob/main/README.zh-CN.md)。
3
+ Packaged workflow roles for [Pi](https://pi.dev): `judge`, `fixer`, `coder`, `reviewer`, `collector`, `doctor`, `merger`, `notary`, `analyst`. 中文说明见 [README.zh-CN.md](https://github.com/Akagilnc/ak-pi-workflow-roles/blob/main/README.zh-CN.md)。
4
4
 
5
5
  ## Install
6
6
 
@@ -11,7 +11,7 @@ pi install npm:@akagilnc/pi-workflow-roles
11
11
  export PATH="$HOME/.pi/agent/npm/node_modules/.bin:$PATH"
12
12
  ```
13
13
 
14
- Update with `pi update npm:@akagilnc/pi-workflow-roles`—never a second global `npm install -g`. Inspect with `ak-role roles` and `ak-role help <role>`; set per-seat model defaults with `ak-role config set judge openai-codex/gpt-5.6-sol:high`; set or clear a persistent labor engine (callable roles) with `ak-role config set-engine <seat> <name>` / `ak-role config unset-engine <seat>`.
14
+ Update with `pi update npm:@akagilnc/pi-workflow-roles`—never a second global `npm install -g`. Inspect with `ak-role roles` and `ak-role help <role>`; set per-seat model defaults with `ak-role config set <seat> <provider/model[:thinking]>` (callable seats plus automatic `gatekeeper` / `inspector` / `navigator`); clear a Menxia officer override with `ak-role config unset <gatekeeper|inspector|notary>`; set or clear a persistent labor engine (callable roles) with `ak-role config set-engine <seat> <name>` / `ak-role config unset-engine <seat>`.
15
15
 
16
16
  ## Reading results
17
17
 
@@ -23,31 +23,41 @@ ak-role judge --attach ./plan.md "Review this plan." > result.txt
23
23
 
24
24
  Exit status reports lifecycle honesty, not business success: every lawful typed result (including `audit_escalation`) exits zero; a failure without a lawful result exits nonzero, and its Terminal carries the Error Artifact ref and original cause instead of a fabricated receipt.
25
25
 
26
- `ak-role resume <runId>` reopens that run's exact Pi session. Whether to resume is the caller's decision: the command does not require a typed HTTP 429 or a `resumable` state. Unknown run IDs and missing session principals are rejected. Collector and Doctor remain one-shot. The package never auto-switches providers; override the model for one run with the global flags.
26
+ `ak-role resume <runId> [message]` reopens that run's exact Pi session. Standard chain after a role `escalate`s: take the owner ruling and feed it back with `ak-role resume <runId> "<ruling>"` so the same session continues to a terminal. The optional `message` after `runId` is passed through unchanged as the continuation prompt (opaque: not parsed as flags); omit it to use the package resume envelope. Whether to resume is the caller's decision: the command does not require a typed HTTP 429 or a `resumable` state. Unknown run IDs and missing session principals are rejected. Collector, Doctor, and Notary remain one-shot. The package never auto-switches providers; override the model for one run with the global flags.
27
27
 
28
28
  Judge, coder, fixer, reviewer, and merger also retry a non-lawful LLM call in place (same `runId` and session) up to `autoResumeLimit` times. Unset defaults to 2; `ak-role config set-auto-resume-limit <N>` writes the ceiling (`0` disables). Lawful typed terminals (`accepted`, `audit_escalation`, `no_receipt`) stop immediately. Manual `ak-role resume` stays available.
29
29
 
30
- Global overrides work before or after the role: `ak-role --model xai/grok-4.5:high resume <runId>`.
30
+ Global overrides work before the opaque message segment: `ak-role --model <provider/model[:thinking]> resume <runId>` or `ak-role resume --model <provider/model[:thinking]> <runId>`.
31
31
 
32
- Every run also prepares Navigator advice in the same Terminal. Configure it like any other seat:
32
+ Every run also prepares Navigator advice in the same Terminal. Configure seats like this:
33
33
 
34
34
  ```bash
35
- ak-role config set navigator openai-codex/gpt-5.6-luna:medium
35
+ ak-role config set judge <provider/model[:thinking]>
36
+ ak-role config set navigator <provider/model[:thinking]>
37
+ # Menxia officers (automatic on submission; not caller commands except direct notary)
38
+ ak-role config set gatekeeper <provider/model[:thinking]>
39
+ ak-role config set inspector <provider/model[:thinking]>
40
+ ak-role config set notary <provider/model[:thinking]>
41
+ ak-role config unset gatekeeper
36
42
  # persistent labor engine (callable roles; not navigator); one-shot override remains --engine
37
43
  ak-role config set-engine judge opus
38
44
  ak-role config unset-engine judge
39
45
  ak-role config set-auto-resume-limit 3
40
46
  ```
41
47
 
42
- `config set` stores the seat model default; `config set-engine` / `unset-engine` store or clear the persistent labor-engine name on callable roles (same seats as `--engine`; navigator refused — no independent activation). `config set-auto-resume-limit` stores the single-call auto-resume ceiling. Usage and refusal text are owned by `ak-role config` in the public CLI.
48
+ `config set` stores the seat model default. For Menxia officers (`gatekeeper` / `inspector` / `notary`) resolution is officer pin → province (`gatekeeper`) pin → inherit parent session; an explicit selection that fails is loud and does not fall back. `config unset` clears only those officer overrides. `config set-engine` / `unset-engine` store or clear the persistent labor-engine name on callable roles (same seats as `--engine`; navigator refused — no independent activation). `config set-auto-resume-limit` stores the single-call auto-resume ceiling. Usage and refusal text are owned by `ak-role config` / `ak-role help config`.
43
49
 
44
50
  Receipts are typed, so callers compose roles without parsing prose; ordering and stopping stay caller-owned. Programmatic consumers derive contracts from the exported schemas in `src/package-contracts/`, not from this guide.
45
51
 
52
+ ### Menxia submission gate
53
+
54
+ On completing-side submissions the package may spawn the Menxia province before the run settles: `gatekeeper` reads the subject and dispatches an officer (`inspector` or `notary`); existing auditor hooks stay where already wired. The gate runs inside the submission session; bounce means rewrite-and-resubmit in that same session — not role failure; the final receipt is the post-gate product. `planned` / `refused` / `unfinished` skip the province. Pointers only: [ADR 0067](docs/adr/0067-menxia-province-founding-jishizhong-fubaolang.md), [ADR 0072](docs/adr/0072-menxia-pre-pr-submission-hooks.md). Do not scrape session prose for gate status.
55
+
46
56
  When a labor-engine detour fails and the seat continues the labor on the main road, the typed receipt may carry a mechanical `engineLaborFallback` field: `{ engine, failure, laborBy: "seat" }`. It appears only after a real detour failure that fell back to seat labor—not on detour success or caller cancel. First failure wins for the activation; model-forged `engineLaborFallback` keys are stripped unless the package latch recorded one. Sole producer: `src/engine-labor-fallback.ts`; decision record: [ADR 0071](docs/adr/0071-engine-detour-failure-seat-fallback-declaration.md). This README only projects that contract.
47
57
 
48
58
  ## Call the roles
49
59
 
50
- Public option identity, aliases, requiredness, and mode faces live in the generated [Public CLI options](#public-cli-options-generated) table and in `ak-role help <command>` — both project the same typed source. The examples below are usage sketches, not a second flag contract. An instruction is optional for judge, collector, and doctor, and required nonblank for coder, fixer, reviewer, and merger.
60
+ Public option identity, aliases, requiredness, and mode faces live in the generated [Public CLI options](#public-cli-options-generated) table and in `ak-role help <command>` — both project the same typed source. The examples below are usage sketches, not a second flag contract. An instruction is optional for judge, collector, and doctor; notary admits no caller prompt or attachment; analyst is deterministic (see help). Required nonblank for coder, fixer, reviewer, and merger.
51
61
 
52
62
  ```bash
53
63
  # judge — adjudicate the supplied materials; infers its burden, no burden flag
@@ -79,6 +89,20 @@ ak-role doctor --issue 115 "Diagnose this retained case."
79
89
  # merger — resolve one merge already in conflict (start it first with Git’s ort)
80
90
  ak-role merger --project /path/to/worktree "Reconcile the active merge."
81
91
  # hands new intent/authority questions back instead of inventing authority
92
+
93
+ # notary — document-fidelity check on one retained source run; zero prompt/attachment; one-shot
94
+ ak-role notary --source-run <runId@role|path>
95
+
96
+ # analyst — deterministic metrics over the book (cwd git common-dir); bare = whole book
97
+ ak-role analyst
98
+ ak-role analyst --ticket <N>
99
+ ak-role analyst sweep --attach ./payload.md
100
+ ak-role analyst --cohort \
101
+ --group-a-label A --group-a-issues 1,2 \
102
+ --group-b-label B --group-b-issues 3,4
103
+
104
+ # after escalate: feed the owner ruling into the same session (standard chain; see resume under Reading results)
105
+ ak-role resume <runId> "<ruling>"
82
106
  ```
83
107
 
84
108
  ## Names
package/README.zh-CN.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @akagilnc/pi-workflow-roles
2
2
 
3
- 为 [Pi](https://pi.dev) 打包的工作流角色:大理寺(judge)、修内司(fixer)、将作监(coder)、御史台(reviewer)、门下省(collector)、太医署(doctor)、校书郎(merger)。English: [README.md](https://github.com/Akagilnc/ak-pi-workflow-roles/blob/main/README.md)。
3
+ 为 [Pi](https://pi.dev) 打包的工作流角色:大理寺(judge)、修内司(fixer)、将作监(coder)、御史台(reviewer)、通进司(collector)、太医署(doctor)、校书郎(merger)、符宝郎(notary)、太史(analyst)。English: [README.md](https://github.com/Akagilnc/ak-pi-workflow-roles/blob/main/README.md)。
4
4
 
5
5
  ## 安装
6
6
 
@@ -11,7 +11,7 @@ pi install npm:@akagilnc/pi-workflow-roles
11
11
  export PATH="$HOME/.pi/agent/npm/node_modules/.bin:$PATH"
12
12
  ```
13
13
 
14
- 更新用 `pi update npm:@akagilnc/pi-workflow-roles`——勿另起全局 `npm install -g`。查看能力:`ak-role roles`、`ak-role help <role>`;设席位模型默认:`ak-role config set judge openai-codex/gpt-5.6-sol:high`;设或清持久劳务引擎(可调用角色):`ak-role config set-engine <seat> <name>` / `ak-role config unset-engine <seat>`。
14
+ 更新用 `pi update npm:@akagilnc/pi-workflow-roles`——勿另起全局 `npm install -g`。查看能力:`ak-role roles`、`ak-role help <role>`;设席位模型默认:`ak-role config set <seat> <provider/model[:thinking]>`(可调用席位,以及自动出席的 `gatekeeper`/`inspector`/`navigator`);清除门下省官钉:`ak-role config unset <gatekeeper|inspector|notary>`;设或清持久劳务引擎(可调用角色):`ak-role config set-engine <seat> <name>` / `ak-role config unset-engine <seat>`。
15
15
 
16
16
  ## 读结果
17
17
 
@@ -23,31 +23,41 @@ ak-role judge --attach ./plan.md "Review this plan." > result.txt
23
23
 
24
24
  退出码报的是生命周期诚实,不是业务成败:一切合法 typed 终态(含 `audit_escalation`)退出零;无合法终态的失败退出非零,其 Terminal 携带 Error Artifact 引用与原始原因,不伪造回执。
25
25
 
26
- `ak-role resume <runId>` 重开该次运行的同一 Pi session。要不要续跑由调用者决定:不再要求 typed HTTP 429,也不要求 `resumable` 状态。未知 run ID、session 主体不在则拒绝。门下省、太医署仍为一次性,无 resume。包绝不自动换 provider;临时换模型用全局旗标。
26
+ `ak-role resume <runId> [message]` 重开该次运行的同一 Pi session。角色 `escalate`(直通御前)后拿到 owner 裁定,标准续跑是 `ak-role resume <runId> "<裁定>"`——把裁定喂回同一 session,角色继续走到终局。`runId` 后可选的 `message` 原样作为续跑 prompt(opaque:不进全局旗标语法);省略则用包自带 resume envelope。要不要续跑由调用者决定:不再要求 typed HTTP 429,也不要求 `resumable` 状态。未知 run ID、session 主体不在则拒绝。通进司、太医署、符宝郎仍为一次性,无 resume。包绝不自动换 provider;临时换模型用全局旗标。
27
27
 
28
28
  大理寺、将作监、修内司、御史台、校书郎在单次调用内对非 lawful LLM 终态原地续跑(同一 `runId` 与 session),次数上限为 `autoResumeLimit`。缺键默认 2;`ak-role config set-auto-resume-limit <N>` 写入(`0` 关闭自动续)。lawful typed 终态(`accepted` / `audit_escalation` / `no_receipt`)立即停止。手动 `ak-role resume` 仍可用。
29
29
 
30
- 全局覆盖前后皆可:`ak-role --model xai/grok-4.5:high resume <runId>`。
30
+ 全局覆盖须在 opaque message 段之前:`ak-role --model <provider/model[:thinking]> resume <runId>` 或 `ak-role resume --model <provider/model[:thinking]> <runId>`。
31
31
 
32
32
  每次运行游奕使自动出席,建议随同一 Terminal 给出。配置:
33
33
 
34
34
  ```bash
35
- ak-role config set navigator openai-codex/gpt-5.6-luna:medium
35
+ ak-role config set judge <provider/model[:thinking]>
36
+ ak-role config set navigator <provider/model[:thinking]>
37
+ # 门下省官席(交卷自动出席;除直调符宝郎外无独立命令)
38
+ ak-role config set gatekeeper <provider/model[:thinking]>
39
+ ak-role config set inspector <provider/model[:thinking]>
40
+ ak-role config set notary <provider/model[:thinking]>
41
+ ak-role config unset gatekeeper
36
42
  # 持久劳务引擎(可调用角色;不含 navigator);一次性覆盖仍用 --engine
37
43
  ak-role config set-engine judge opus
38
44
  ak-role config unset-engine judge
39
45
  ak-role config set-auto-resume-limit 3
40
46
  ```
41
47
 
42
- `config set` 存席位模型默认;`config set-engine` / `unset-engine` 在可调用角色上写入或清除持久劳务引擎名(与 `--engine` 同轴;拒收 navigator——无独立 activation)。`config set-auto-resume-limit` 写入单次调用自动续跑上限。用法与拒绝文案以公开 CLI 的 `ak-role config` 为准。
48
+ `config set` 存席位模型默认。门下省官席(`gatekeeper`/`inspector`/`notary`)解析顺序:官自钉 → 省钉(`gatekeeper`)→ 继承父 session;显式指定失败响亮、不回退。`config unset` 只清这三官的覆盖。`config set-engine`/`unset-engine` 在可调用角色上写入或清除持久劳务引擎名(与 `--engine` 同轴;拒收 navigator——无独立 activation)。`config set-auto-resume-limit` 写入单次调用自动续跑上限。用法与拒绝文案以 `ak-role config`/`ak-role help config` 为准。
43
49
 
44
50
  回执是 typed 的,调用者不必解析散文即可组合角色;顺序与停止归调用者。编程消费者从 `src/package-contracts/` 导出推导契约,不从本文。
45
51
 
52
+ ### 门下省交卷闸
53
+
54
+ 完成侧交卷时,包可能在本局结算前起门下省:`gatekeeper` 读受审物并派官(`inspector` 给事中或 `notary` 符宝郎);既有审刑院挂钩仍在原位。闸在交卷 session 内运行;封驳=当场重写重交,不是角色失败;最终回执即过闸产物。`planned`/`refused`/`unfinished` 不调省。指针:[ADR 0067](docs/adr/0067-menxia-province-founding-jishizhong-fubaolang.md)、[ADR 0072](docs/adr/0072-menxia-pre-pr-submission-hooks.md)。勿刮 session 散文当闸状态。
55
+
46
56
  当劳务引擎绕行失败、座席回到主路继续劳务时,typed 回执可带机械字段 `engineLaborFallback`:`{ engine, failure, laborBy: "seat" }`。仅在真实绕行失败并座席顶班后出现——成功绕行或调用方 cancel 不出现。同一次 activation 内先到先得;无包内 latch 时剥离模型伪造的 `engineLaborFallback` 键。唯一构造点:`src/engine-labor-fallback.ts`;决策记录:[ADR 0071](docs/adr/0071-engine-detour-failure-seat-fallback-declaration.md)。本文只投影该契约。
47
57
 
48
58
  ## 调用百官
49
59
 
50
- 公开 option 身份、别名、必填性与 mode 面以生成区 [公开 CLI 选项(生成)](#公开-cli-选项生成) 与 `ak-role help <command>` 为准——二者同源。下例只是用法速写,不是第二份旗标合同。指令对大理寺、门下省、太医署可省略,对将作监、修内司、御史台、校书郎必须非空。
60
+ 公开 option 身份、别名、必填性与 mode 面以生成区 [公开 CLI 选项(生成)](#公开-cli-选项生成) 与 `ak-role help <command>` 为准——二者同源。下例只是用法速写,不是第二份旗标合同。指令对大理寺、通进司、太医署可省略;符宝郎零 prompt/附件;太史为确定性命令(见 help)。对将作监、修内司、御史台、校书郎必须非空。
51
61
 
52
62
  ```bash
53
63
  # 大理寺——审断所供材料;自行推断举证责任,无 burden 旗标
@@ -63,7 +73,7 @@ ak-role reviewer --base main "Review the branch."
63
73
  # --base 为必填并钉住 fixed point;御史台不接受 --attach
64
74
  # completed ≠ 准行——findings 在 Terminal 里
65
75
 
66
- # 门下省——GitHub PR 收证;仅 github.com,需 gh 已认证;一次性
76
+ # 通进司——GitHub PR 收证;仅 github.com,需 gh 已认证;一次性
67
77
  ak-role collector --pr 42 --repo owner/repository
68
78
  ak-role collector --pr 42 --request-manifest ./requests.json
69
79
  # 无配置时仅观察;可选 request manifest 为 {requests:[{id,body}]};repo 默认取 origin
@@ -79,6 +89,20 @@ ak-role doctor --issue 115 "Diagnose this retained case."
79
89
  # 校书郎——雠校一个已在冲突的 merge(先用 Git ort 起动)
80
90
  ak-role merger --project /path/to/worktree "Reconcile the active merge."
81
91
  # 遇新意图/权限问题交回调用者,不捏造 authority
92
+
93
+ # 符宝郎——对一份留存 source run 做文书核验;零 prompt/附件;一次性
94
+ ak-role notary --source-run <runId@role|path>
95
+
96
+ # 太史——确定性指标(cwd 候簿=git common-dir);裸调=整簿
97
+ ak-role analyst
98
+ ak-role analyst --ticket <N>
99
+ ak-role analyst sweep --attach ./payload.md
100
+ ak-role analyst --cohort \
101
+ --group-a-label A --group-a-issues 1,2 \
102
+ --group-b-label B --group-b-issues 3,4
103
+
104
+ # escalate 后:把 owner 裁定喂回同一 session(标准链;细则见上方「读结果」resume 段)
105
+ ak-role resume <runId> "<裁定>"
82
106
  ```
83
107
 
84
108
  ## 班子(唐宋官署命名)
@@ -93,8 +117,11 @@ ak-role merger --project /path/to/worktree "Reconcile the active merge."
93
117
  | **修内司** | fixer | **缮修旧物。** 面对已有问题,不急于表面修补,而是追寻问题根源,找到真正需要修整之处。既要修复眼前缺漏,也要防止同类问题再次出现。 |
94
118
  | **御史台** | reviewer | **察举百弊。** 置身事外,以旁观之眼审视成果,寻找其中的不妥、遗漏与隐患。只负责指出问题、陈明依据,不参与修改,也不替人作最终判断。 |
95
119
  | **大理寺** | judge | **审理定谳。** 承接各方意见与材料,依照既定规则逐项判断,辨明是非曲直。可以准行、退回或请示更高决定,但自身不参与建设与修改。 |
96
- | **审刑院** | judge-auditor/reviewer-auditor(无 CLI,共享内部接缝) | **复核成案。** 不重新争论事情本身,而是检查整个办理过程是否合乎规矩。关注是否有人越过职责、是否遗漏必要步骤、是否以错误方式得出正确结果。 |
97
- | **门下省** | collector | **承接百议。** 位于决策之前,收集各方反馈与意见,确认事情是否已经具备继续推进的条件。它不替人裁决,只负责让信息完整、状态清楚。 |
120
+ | **审刑院** | judge-auditor/reviewer-auditor(无 CLI,共享内部接缝) | **复核成案。** 不重新争论事情本身,而是检查整个办理过程是否合乎规矩。关注是否有人越过职责、是否遗漏必要步骤、是否以错误方式得出正确结果。直属陛下,不入门下省编制。 |
121
+ | **门下省** | gatekeeper(无独立 CLI;交卷自动出席) | **质量保证省。** 交卷时判断受审物、够不够审、该谁审,派给事中或符宝郎;省内政,不是外层编排器。规范见 [ADR 0067](docs/adr/0067-menxia-province-founding-jishizhong-fubaolang.md)、[ADR 0072](docs/adr/0072-menxia-pre-pr-submission-hooks.md)。 |
122
+ | **给事中** | inspector(无独立 CLI;可由门下省派发) | **复杂度与测试质量两轴质检。** 受审物是将作监/修内司完成侧交卷;封驳=当场打回重写,不是本局失败。 |
123
+ | **符宝郎** | notary | **引语真伪与票面对齐。** 受审物是大理寺拟判等文书;可被门下省派发,也可 `ak-role notary` 单独调。 |
124
+ | **通进司** | collector | **承接百议/收证。** 门下省下的收证衙门:收集外部 GitHub PR 材料与意见,只收不审、不替人裁决。canonical 键仍为 `collector`。 |
98
125
  | **校书郎** | merger | **雠校异文。** 面对不同来源的修改,负责整理、校合与调和。保留双方有价值的部分,解决彼此冲突;遇到无法自行决定之处,则留待重新裁量。 |
99
126
  | **游奕使** | navigator(无 CLI,自动出席) | **巡行问路。** 不掌具体事务,而是观察全局变化,结合当前局面提醒下一步方向。它提供建议与路径参考,但最终选择仍由执掌之人决定。 |
100
127
 
@@ -103,14 +130,15 @@ ak-role merger --project /path/to/worktree "Reconcile the active merge."
103
130
  | 席位 | 名 | 职掌 | 状态 |
104
131
  | --- | --- | --- | --- |
105
132
  | doctor | **太医署** | 单案诊断工厂机制,开 `keep|thin|delete` 方 | 已建 |
106
- | | **司天台** | 记候簿——只打点、只指针,不分析不执法 | **一期不是角色**([ADR 0047](docs/adr/0047-sitian-phase-one-mechanism-not-role.md):零 LLM 双面对账);席位形态属 [#67](https://github.com/Akagilnc/ak-pi-workflow-roles/issues/67) 素材,未定 |
107
- | — | **兰台** | 读档议制——耗时/缺口/冗余三条,上奏不执法 | 未建([#67](https://github.com/Akagilnc/ak-pi-workflow-roles/issues/67) 两席之一) |
108
- | — | **考功司** | 考具体效率——角色与档位的升档率、一次通过率、每票成本 | 留档,不属 #67,需要时另立票 |
133
+ | analyst | **太史** | 司天台分析席:只读司天记录、出高阶指标;确定性机制,非 LLM,可单独调用 | 已建([ADR 0068](docs/adr/0068-taishi-analysis-seat-reads-records-writes-sibling-home.md);机器面键 `analyst`,[#445](https://github.com/Akagilnc/ak-pi-workflow-roles/issues/445) 拼音清零) |
134
+ | — | **司天台** | 记候簿——只打点、只指针,不分析不执法 | **一期不是角色**([ADR 0047](docs/adr/0047-sitian-phase-one-mechanism-not-role.md):零 LLM 双面对账);分析席已由太史承担;机器面键 `archivist`([#445](https://github.com/Akagilnc/ak-pi-workflow-roles/issues/445) |
135
+ | — | **兰台** | 读档议制——耗时/缺口/冗余三条,上奏不执法 | 未建 |
136
+ | — | **考功司** | 考具体效率——角色与档位的升档率、一次通过率、每票成本 | 留档,需要时另立票 |
109
137
  | — | **主簿** | 合并后勾稽销案:核实确已合上、清理残留、报到达 | 未建 |
110
138
 
111
- **merge 按钮归调用者**,没有任何角色握不可逆权限:门下省把收证这件苦活做完并报收集终态,人(或 AI)自己判断、自己点,点完想调主簿就调、不调也可以。
139
+ **merge 按钮归调用者**,没有任何角色握不可逆权限:通进司把收证这件苦活做完并报收集终态,人(或 AI)自己判断、自己点,点完想调主簿就调、不调也可以。
112
140
 
113
- 上表**不规定调用顺序**——组合、顺序、重复次数归调用者([ADR 0010](docs/adr/0010-callers-own-role-composition-and-repetition.md))。御史台/大理寺/审刑院是**职责分立的类比,不是必经链**;审刑院也并非只跟在大理寺之后,大理寺、御史台、太医署各自都有一次。
141
+ 上表**不规定调用顺序**——组合、顺序、重复次数归调用者([ADR 0010](docs/adr/0010-callers-own-role-composition-and-repetition.md))。御史台/大理寺/审刑院是**职责分立的类比,不是必经链**;审刑院也并非只跟在大理寺之后,大理寺、御史台、太医署各自都有一次。门下省交卷闸是完成侧挂钩,不是调用者必经编排链。
114
142
 
115
143
  `拾遗补阙` 成对留档,待将来出现第二个进言席再启用。
116
144
 
@@ -15904,6 +15904,9 @@ var init_typed_provider_http = __esm({
15904
15904
  // src/public-cli/run-lifecycle.ts
15905
15905
  import { chmod, lstat, open, readdir as readdir2, readFile as readFile5, unlink as unlink2, writeFile as writeFile3 } from "node:fs/promises";
15906
15906
  import { join as join7 } from "node:path";
15907
+ function selectResumeContinuationPrompt(message) {
15908
+ return message !== void 0 ? message : RESUME_TRANSPORT_ENVELOPE;
15909
+ }
15907
15910
  function isV1ResumableProvider(provider) {
15908
15911
  return V1_RESUMABLE_PROVIDERS.includes(provider);
15909
15912
  }
@@ -17486,8 +17489,11 @@ var init_option_definitions = __esm({
17486
17489
  resume: {
17487
17490
  command: "resume",
17488
17491
  summary: "Reopen an exact role run whose Pi session principal still exists.",
17489
- usage: ["ak-role resume <runId>"],
17490
- examples: ["ak-role resume 01abc\u2026"]
17492
+ usage: ["ak-role resume <runId> [message]"],
17493
+ examples: [
17494
+ "ak-role resume 01abc\u2026",
17495
+ 'ak-role resume 01abc\u2026 "owner ruling"'
17496
+ ]
17491
17497
  }
17492
17498
  };
17493
17499
  PUBLIC_COMMAND_HELP = {
@@ -23571,7 +23577,7 @@ function buildCoderResumeActivationExtraArgs(admitted, options) {
23571
23577
  "--mode",
23572
23578
  "json",
23573
23579
  ...buildSeatModelCliArgs(options.model),
23574
- RESUME_TRANSPORT_ENVELOPE
23580
+ selectResumeContinuationPrompt(options.message)
23575
23581
  ];
23576
23582
  }
23577
23583
  async function presentControlledFailure2(admitted, failureInput, io) {
@@ -23806,25 +23812,10 @@ async function runPublicCoder(argv, env, io, parseCoderArgv2) {
23806
23812
  })
23807
23813
  });
23808
23814
  }
23809
- async function runPublicCoderResume(argv, env, io) {
23810
- const runId = argv[0];
23811
- if (runId === void 0 || runId.trim() === "" || runId.startsWith("-")) {
23812
- presentStructuralRejection(
23813
- new CliUsageError("usage: ak-role resume <runId>"),
23814
- io
23815
- );
23816
- return { exitCode: 2 };
23817
- }
23818
- if (argv.length > 1) {
23819
- presentStructuralRejection(
23820
- new CliUsageError("resume takes exactly one run id"),
23821
- io
23822
- );
23823
- return { exitCode: 2 };
23824
- }
23815
+ async function runPublicCoderResume(request, env, io) {
23825
23816
  let loaded;
23826
23817
  try {
23827
- loaded = await loadResumableCoderRun(env.home, runId);
23818
+ loaded = await loadResumableCoderRun(env.home, request.runId);
23828
23819
  } catch (error) {
23829
23820
  if (error instanceof CliUsageError) {
23830
23821
  presentStructuralRejection(error, io);
@@ -23869,7 +23860,8 @@ async function runPublicCoderResume(argv, env, io) {
23869
23860
  const extraArgs = buildCoderResumeActivationExtraArgs(admitted, {
23870
23861
  packageRoot: env.packageRoot,
23871
23862
  ...env.model === void 0 ? {} : { model: env.model },
23872
- ...env.extraPiArgs === void 0 ? {} : { extraPiArgs: env.extraPiArgs }
23863
+ ...env.extraPiArgs === void 0 ? {} : { extraPiArgs: env.extraPiArgs },
23864
+ ...request.message === void 0 ? {} : { message: request.message }
23873
23865
  });
23874
23866
  const result2 = await dispatchAdmittedCoder({
23875
23867
  admitted,
@@ -24475,7 +24467,7 @@ function buildFixerResumeActivationExtraArgs(admitted, options) {
24475
24467
  "--mode",
24476
24468
  "json",
24477
24469
  ...buildSeatModelCliArgs(options.model),
24478
- RESUME_TRANSPORT_ENVELOPE
24470
+ selectResumeContinuationPrompt(options.message)
24479
24471
  ];
24480
24472
  }
24481
24473
  async function presentControlledFailure5(admitted, failureInput, io) {
@@ -24723,25 +24715,10 @@ async function runPublicFixer(argv, env, io, parseFixerArgv2) {
24723
24715
  })
24724
24716
  });
24725
24717
  }
24726
- async function runPublicFixerResume(argv, env, io) {
24727
- const runId = argv[0];
24728
- if (runId === void 0 || runId.trim() === "" || runId.startsWith("-")) {
24729
- presentStructuralRejection(
24730
- new CliUsageError("usage: ak-role resume <runId>"),
24731
- io
24732
- );
24733
- return { exitCode: 2 };
24734
- }
24735
- if (argv.length > 1) {
24736
- presentStructuralRejection(
24737
- new CliUsageError("resume takes exactly one run id"),
24738
- io
24739
- );
24740
- return { exitCode: 2 };
24741
- }
24718
+ async function runPublicFixerResume(request, env, io) {
24742
24719
  let loaded;
24743
24720
  try {
24744
- loaded = await loadResumableFixerRun(env.home, runId);
24721
+ loaded = await loadResumableFixerRun(env.home, request.runId);
24745
24722
  } catch (error) {
24746
24723
  if (error instanceof CliUsageError) {
24747
24724
  presentStructuralRejection(error, io);
@@ -24779,7 +24756,8 @@ async function runPublicFixerResume(argv, env, io) {
24779
24756
  const extraArgs = buildFixerResumeActivationExtraArgs(admitted, {
24780
24757
  packageRoot: env.packageRoot,
24781
24758
  ...env.model === void 0 ? {} : { model: env.model },
24782
- ...env.extraPiArgs === void 0 ? {} : { extraPiArgs: env.extraPiArgs }
24759
+ ...env.extraPiArgs === void 0 ? {} : { extraPiArgs: env.extraPiArgs },
24760
+ ...request.message === void 0 ? {} : { message: request.message }
24783
24761
  });
24784
24762
  const result2 = await dispatchAdmittedFixer({
24785
24763
  admitted,
@@ -24932,7 +24910,7 @@ function buildJudgeResumeActivationExtraArgs(admitted, options = {}) {
24932
24910
  "--mode",
24933
24911
  "json",
24934
24912
  ...buildSeatModelCliArgs(options.model),
24935
- RESUME_TRANSPORT_ENVELOPE
24913
+ selectResumeContinuationPrompt(options.message)
24936
24914
  ];
24937
24915
  }
24938
24916
  async function presentControlledFailure6(admitted, failureInput, io) {
@@ -25164,25 +25142,10 @@ async function runPublicJudge(argv, env, io, parseJudgeArgv2) {
25164
25142
  })
25165
25143
  });
25166
25144
  }
25167
- async function runPublicResume(argv, env, io) {
25168
- const runId = argv[0];
25169
- if (runId === void 0 || runId.trim() === "" || runId.startsWith("-")) {
25170
- presentStructuralRejection(
25171
- new CliUsageError("usage: ak-role resume <runId>"),
25172
- io
25173
- );
25174
- return { exitCode: 2 };
25175
- }
25176
- if (argv.length > 1) {
25177
- presentStructuralRejection(
25178
- new CliUsageError("resume takes exactly one run id"),
25179
- io
25180
- );
25181
- return { exitCode: 2 };
25182
- }
25145
+ async function runPublicResume(request, env, io) {
25183
25146
  let loaded;
25184
25147
  try {
25185
- loaded = await loadResumableJudgeRun(env.home, runId);
25148
+ loaded = await loadResumableJudgeRun(env.home, request.runId);
25186
25149
  } catch (error) {
25187
25150
  if (error instanceof CliUsageError) {
25188
25151
  presentStructuralRejection(error, io);
@@ -25203,7 +25166,8 @@ async function runPublicResume(argv, env, io) {
25203
25166
  }
25204
25167
  const extraArgs = buildJudgeResumeActivationExtraArgs(admitted, {
25205
25168
  ...env.model === void 0 ? {} : { model: env.model },
25206
- ...env.extraPiArgs === void 0 ? {} : { extraPiArgs: env.extraPiArgs }
25169
+ ...env.extraPiArgs === void 0 ? {} : { extraPiArgs: env.extraPiArgs },
25170
+ ...request.message === void 0 ? {} : { message: request.message }
25207
25171
  });
25208
25172
  const result2 = await dispatchAdmittedJudge({
25209
25173
  admitted,
@@ -25294,7 +25258,7 @@ function buildMergerResumeActivationExtraArgs(admitted, options) {
25294
25258
  "--mode",
25295
25259
  "json",
25296
25260
  ...buildSeatModelCliArgs(options.model),
25297
- RESUME_TRANSPORT_ENVELOPE
25261
+ selectResumeContinuationPrompt(options.message)
25298
25262
  ];
25299
25263
  }
25300
25264
  async function presentControlledFailure7(admitted, failureInput, io) {
@@ -25615,25 +25579,10 @@ async function runPublicMerger(argv, env, io, parseMergerArgv2) {
25615
25579
  })
25616
25580
  });
25617
25581
  }
25618
- async function runPublicMergerResume(argv, env, io) {
25619
- const runId = argv[0];
25620
- if (runId === void 0 || runId.trim() === "" || runId.startsWith("-")) {
25621
- presentStructuralRejection(
25622
- new CliUsageError("usage: ak-role resume <runId>"),
25623
- io
25624
- );
25625
- return { exitCode: 2 };
25626
- }
25627
- if (argv.length > 1) {
25628
- presentStructuralRejection(
25629
- new CliUsageError("resume takes exactly one run id"),
25630
- io
25631
- );
25632
- return { exitCode: 2 };
25633
- }
25582
+ async function runPublicMergerResume(request, env, io) {
25634
25583
  let loaded;
25635
25584
  try {
25636
- loaded = await loadResumableMergerRun(env.home, runId);
25585
+ loaded = await loadResumableMergerRun(env.home, request.runId);
25637
25586
  } catch (error) {
25638
25587
  if (error instanceof CliUsageError) {
25639
25588
  presentStructuralRejection(error, io);
@@ -25672,7 +25621,8 @@ async function runPublicMergerResume(argv, env, io) {
25672
25621
  const extraArgs = buildMergerResumeActivationExtraArgs(admitted, {
25673
25622
  packageRoot: env.packageRoot,
25674
25623
  ...env.model === void 0 ? {} : { model: env.model },
25675
- ...env.extraPiArgs === void 0 ? {} : { extraPiArgs: env.extraPiArgs }
25624
+ ...env.extraPiArgs === void 0 ? {} : { extraPiArgs: env.extraPiArgs },
25625
+ ...request.message === void 0 ? {} : { message: request.message }
25676
25626
  });
25677
25627
  const result2 = await dispatchAdmittedMerger({
25678
25628
  admitted,
@@ -25777,7 +25727,7 @@ function buildReviewerResumeActivationExtraArgs(admitted, options) {
25777
25727
  "--mode",
25778
25728
  "json",
25779
25729
  ...buildSeatModelCliArgs(options.model),
25780
- RESUME_TRANSPORT_ENVELOPE
25730
+ selectResumeContinuationPrompt(options.message)
25781
25731
  ];
25782
25732
  }
25783
25733
  async function presentControlledFailure8(admitted, failureInput, io) {
@@ -26033,25 +25983,10 @@ async function runPublicReviewer(argv, env, io, parseReviewerArgv2) {
26033
25983
  })
26034
25984
  });
26035
25985
  }
26036
- async function runPublicReviewerResume(argv, env, io) {
26037
- const runId = argv[0];
26038
- if (runId === void 0 || runId.trim() === "" || runId.startsWith("-")) {
26039
- presentStructuralRejection(
26040
- new CliUsageError("usage: ak-role resume <runId>"),
26041
- io
26042
- );
26043
- return { exitCode: 2 };
26044
- }
26045
- if (argv.length > 1) {
26046
- presentStructuralRejection(
26047
- new CliUsageError("resume takes exactly one run id"),
26048
- io
26049
- );
26050
- return { exitCode: 2 };
26051
- }
25986
+ async function runPublicReviewerResume(request, env, io) {
26052
25987
  let loaded;
26053
25988
  try {
26054
- loaded = await loadResumableReviewerRun(env.home, runId);
25989
+ loaded = await loadResumableReviewerRun(env.home, request.runId);
26055
25990
  } catch (error) {
26056
25991
  if (error instanceof CliUsageError) {
26057
25992
  presentStructuralRejection(error, io);
@@ -26089,7 +26024,8 @@ async function runPublicReviewerResume(argv, env, io) {
26089
26024
  const extraArgs = buildReviewerResumeActivationExtraArgs(admitted, {
26090
26025
  packageRoot: env.packageRoot,
26091
26026
  ...env.model === void 0 ? {} : { model: env.model },
26092
- ...env.extraPiArgs === void 0 ? {} : { extraPiArgs: env.extraPiArgs }
26027
+ ...env.extraPiArgs === void 0 ? {} : { extraPiArgs: env.extraPiArgs },
26028
+ ...request.message === void 0 ? {} : { message: request.message }
26093
26029
  });
26094
26030
  const result2 = await dispatchAdmittedReviewer({
26095
26031
  admitted,
@@ -28729,6 +28665,10 @@ function parseArgv(argv) {
28729
28665
  const positional = [];
28730
28666
  const globalOptions = createTypedOptionConsumer(PUBLIC_GLOBAL_OPTIONS);
28731
28667
  while (args.length > 0) {
28668
+ if (positional[0] === "resume" && positional.length >= 2) {
28669
+ positional.push(...args);
28670
+ break;
28671
+ }
28732
28672
  if (args[0] === "--") {
28733
28673
  args.shift();
28734
28674
  positional.push(...args);
@@ -28779,6 +28719,19 @@ function parseArgv(argv) {
28779
28719
  help
28780
28720
  };
28781
28721
  }
28722
+ function parseResumeRequest(args) {
28723
+ const runId = args[0];
28724
+ if (runId === void 0 || runId.trim() === "" || runId.startsWith("-")) {
28725
+ throw new CliUsageError("usage: ak-role resume <runId> [message]");
28726
+ }
28727
+ if (args.length > 2) {
28728
+ throw new CliUsageError("usage: ak-role resume <runId> [message]");
28729
+ }
28730
+ if (args.length === 2) {
28731
+ return { runId, message: args[1] };
28732
+ }
28733
+ return { runId };
28734
+ }
28782
28735
  function invocationFromParsed(parsed) {
28783
28736
  if (parsed.model === void 0 && parsed.thinking === void 0 && parsed.engine === void 0) {
28784
28737
  return void 0;
@@ -29149,8 +29102,8 @@ async function runAkRole(argv, env) {
29149
29102
  const cwd = env.cwd ?? process.cwd();
29150
29103
  const config = await loadAndValidateConfig(home, env.packageRoot);
29151
29104
  const credentials = env.credentials ?? await loadCredentialProviders(agentDir);
29152
- const resumeRunId = parsed.args[0];
29153
- const resumeRole = resumeRunId === void 0 || resumeRunId.trim() === "" ? void 0 : await peekRoleRunRole(home, resumeRunId);
29105
+ const resumeRequest = parseResumeRequest(parsed.args);
29106
+ const resumeRole = await peekRoleRunRole(home, resumeRequest.runId);
29154
29107
  if (resumeRole === "collector") {
29155
29108
  throw new CliUsageError(
29156
29109
  "collector role runs are one-shot and cannot be resumed"
@@ -29170,7 +29123,7 @@ async function runAkRole(argv, env) {
29170
29123
  );
29171
29124
  if (resumeRole === "coder") {
29172
29125
  const result3 = await runPublicCoderResume(
29173
- parsed.args,
29126
+ resumeRequest,
29174
29127
  {
29175
29128
  home,
29176
29129
  agentDir,
@@ -29192,7 +29145,7 @@ async function runAkRole(argv, env) {
29192
29145
  }
29193
29146
  if (resumeRole === "fixer") {
29194
29147
  const result3 = await runPublicFixerResume(
29195
- parsed.args,
29148
+ resumeRequest,
29196
29149
  {
29197
29150
  home,
29198
29151
  agentDir,
@@ -29214,7 +29167,7 @@ async function runAkRole(argv, env) {
29214
29167
  }
29215
29168
  if (resumeRole === "reviewer") {
29216
29169
  const result3 = await runPublicReviewerResume(
29217
- parsed.args,
29170
+ resumeRequest,
29218
29171
  {
29219
29172
  home,
29220
29173
  agentDir,
@@ -29236,7 +29189,7 @@ async function runAkRole(argv, env) {
29236
29189
  }
29237
29190
  if (resumeRole === "merger") {
29238
29191
  const result3 = await runPublicMergerResume(
29239
- parsed.args,
29192
+ resumeRequest,
29240
29193
  {
29241
29194
  home,
29242
29195
  agentDir,
@@ -29257,7 +29210,7 @@ async function runAkRole(argv, env) {
29257
29210
  };
29258
29211
  }
29259
29212
  const result2 = await runPublicResume(
29260
- parsed.args,
29213
+ resumeRequest,
29261
29214
  {
29262
29215
  home,
29263
29216
  agentDir,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.2336",
3
+ "version": "0.1.2348",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -398,7 +398,7 @@ const YAMEN_LABELS: Readonly<Record<string, string>> = {
398
398
  coder: "将作监",
399
399
  fixer: "修内司",
400
400
  reviewer: "御史台",
401
- collector: "门下省",
401
+ collector: "通进司",
402
402
  doctor: "太医署",
403
403
  merger: "校书郎",
404
404
  marshal: "刑部",
@@ -808,7 +808,7 @@ const COLUMN_LABELS: Readonly<Record<string, string>> = {
808
808
  court: "大理寺 · 审票",
809
809
  coder: "将作监",
810
810
  marshal: "刑部",
811
- collector: "门下省",
811
+ collector: "通进司",
812
812
  done: "已完成",
813
813
  };
814
814
 
@@ -64,7 +64,11 @@ import { runPublicJudge, runPublicResume } from "./judge-run.ts";
64
64
  import { runPublicMerger, runPublicMergerResume } from "./merger-run.ts";
65
65
  import { runPublicReviewer, runPublicReviewerResume } from "./reviewer-run.ts";
66
66
  import { runPublicAnalyst } from "./analyst-run.ts";
67
- import { AUTO_RESUME_LIMIT, peekRoleRunRole } from "./run-lifecycle.ts";
67
+ import {
68
+ AUTO_RESUME_LIMIT,
69
+ peekRoleRunRole,
70
+ type PublicResumeRequest,
71
+ } from "./run-lifecycle.ts";
68
72
  import {
69
73
  INTERNAL_ROLE_ENTRYPOINT_RELATIVE,
70
74
  isAutomaticConfigurableSeat,
@@ -270,7 +274,14 @@ function parseArgv(argv: readonly string[]): ParsedGlobal {
270
274
  // Global flags may appear before or after the subcommand
271
275
  // (`ak-role --model x roles` and `ak-role roles --model x`).
272
276
  // Grammar authority: shared typed consumer over PUBLIC_OPTION_TABLE.global.
277
+ // #471: after `resume <runId>`, remaining argv is the opaque message segment
278
+ // and must not re-enter the global-option consumer — including bare `--`,
279
+ // which is a legal opaque message token, not an argv delimiter here.
273
280
  while (args.length > 0) {
281
+ if (positional[0] === "resume" && positional.length >= 2) {
282
+ positional.push(...args);
283
+ break;
284
+ }
274
285
  if (args[0] === "--") {
275
286
  args.shift();
276
287
  positional.push(...args);
@@ -326,6 +337,24 @@ function parseArgv(argv: readonly string[]): ParsedGlobal {
326
337
  };
327
338
  }
328
339
 
340
+ /**
341
+ * Unique public resume request parser (#471).
342
+ * One optional argv after runId is the opaque message; no further positionals.
343
+ */
344
+ function parseResumeRequest(args: readonly string[]): PublicResumeRequest {
345
+ const runId = args[0];
346
+ if (runId === undefined || runId.trim() === "" || runId.startsWith("-")) {
347
+ throw new CliUsageError("usage: ak-role resume <runId> [message]");
348
+ }
349
+ if (args.length > 2) {
350
+ throw new CliUsageError("usage: ak-role resume <runId> [message]");
351
+ }
352
+ if (args.length === 2) {
353
+ return { runId, message: args[1]! };
354
+ }
355
+ return { runId };
356
+ }
357
+
329
358
  function invocationFromParsed(parsed: ParsedGlobal): InvocationModelOverride | undefined {
330
359
  if (
331
360
  parsed.model === undefined &&
@@ -784,17 +813,15 @@ export async function runAkRole(
784
813
 
785
814
  // Resume reopens an exact Role run (#416): caller decides; session principal
786
815
  // must still exist. Seat and dispatch follow the durable admitted role.
816
+ // #471: unique parser owns {runId, message?}; five role paths only consume it.
787
817
  if (parsed.command === "resume") {
788
818
  const agentDir = resolveAgentDir(env, home);
789
819
  const cwd = env.cwd ?? process.cwd();
790
820
  const config = await loadAndValidateConfig(home, env.packageRoot);
791
821
  const credentials =
792
822
  env.credentials ?? (await loadCredentialProviders(agentDir));
793
- const resumeRunId = parsed.args[0];
794
- const resumeRole =
795
- resumeRunId === undefined || resumeRunId.trim() === ""
796
- ? undefined
797
- : await peekRoleRunRole(home, resumeRunId);
823
+ const resumeRequest = parseResumeRequest(parsed.args);
824
+ const resumeRole = await peekRoleRunRole(home, resumeRequest.runId);
798
825
  if (resumeRole === "collector") {
799
826
  throw new CliUsageError(
800
827
  "collector role runs are one-shot and cannot be resumed",
@@ -824,7 +851,7 @@ export async function runAkRole(
824
851
  );
825
852
  if (resumeRole === "coder") {
826
853
  const result = await runPublicCoderResume(
827
- parsed.args,
854
+ resumeRequest,
828
855
  {
829
856
  home,
830
857
  agentDir,
@@ -852,7 +879,7 @@ export async function runAkRole(
852
879
  }
853
880
  if (resumeRole === "fixer") {
854
881
  const result = await runPublicFixerResume(
855
- parsed.args,
882
+ resumeRequest,
856
883
  {
857
884
  home,
858
885
  agentDir,
@@ -880,7 +907,7 @@ export async function runAkRole(
880
907
  }
881
908
  if (resumeRole === "reviewer") {
882
909
  const result = await runPublicReviewerResume(
883
- parsed.args,
910
+ resumeRequest,
884
911
  {
885
912
  home,
886
913
  agentDir,
@@ -908,7 +935,7 @@ export async function runAkRole(
908
935
  }
909
936
  if (resumeRole === "merger") {
910
937
  const result = await runPublicMergerResume(
911
- parsed.args,
938
+ resumeRequest,
912
939
  {
913
940
  home,
914
941
  agentDir,
@@ -935,7 +962,7 @@ export async function runAkRole(
935
962
  };
936
963
  }
937
964
  const result = await runPublicResume(
938
- parsed.args,
965
+ resumeRequest,
939
966
  {
940
967
  home,
941
968
  agentDir,
@@ -45,7 +45,8 @@ import {
45
45
  markRunRunning,
46
46
  markRunTerminal,
47
47
  renderResumeCommand,
48
- RESUME_TRANSPORT_ENVELOPE,
48
+ type PublicResumeRequest,
49
+ selectResumeContinuationPrompt,
49
50
  RunWriterLeaseHeldError,
50
51
  type RunWriterLease,
51
52
  type TypedProviderHttpObservation,
@@ -152,6 +153,7 @@ export function buildCoderResumeActivationExtraArgs(
152
153
  packageRoot: string;
153
154
  model?: SeatModelConfig;
154
155
  extraPiArgs?: readonly string[];
156
+ message?: string;
155
157
  },
156
158
  ): string[] {
157
159
  const skillArgs =
@@ -181,7 +183,7 @@ export function buildCoderResumeActivationExtraArgs(
181
183
  "--mode",
182
184
  "json",
183
185
  ...buildSeatModelCliArgs(options.model),
184
- RESUME_TRANSPORT_ENVELOPE,
186
+ selectResumeContinuationPrompt(options.message),
185
187
  ];
186
188
  }
187
189
 
@@ -513,7 +515,7 @@ export async function runPublicCoder(
513
515
  * Restores role/phase/task/session identity; model override is temporary.
514
516
  */
515
517
  export async function runPublicCoderResume(
516
- argv: readonly string[],
518
+ request: PublicResumeRequest,
517
519
  env: CoderRunEnv,
518
520
  io: CliIo,
519
521
  ): Promise<{
@@ -521,25 +523,9 @@ export async function runPublicCoderResume(
521
523
  admitted?: AdmittedCoderInvocation;
522
524
  terminal?: TerminalResult;
523
525
  }> {
524
- const runId = argv[0];
525
- if (runId === undefined || runId.trim() === "" || runId.startsWith("-")) {
526
- presentStructuralRejection(
527
- new CliUsageError("usage: ak-role resume <runId>"),
528
- io,
529
- );
530
- return { exitCode: 2 };
531
- }
532
- if (argv.length > 1) {
533
- presentStructuralRejection(
534
- new CliUsageError("resume takes exactly one run id"),
535
- io,
536
- );
537
- return { exitCode: 2 };
538
- }
539
-
540
526
  let loaded;
541
527
  try {
542
- loaded = await loadResumableCoderRun(env.home, runId);
528
+ loaded = await loadResumableCoderRun(env.home, request.runId);
543
529
  } catch (error) {
544
530
  if (error instanceof CliUsageError) {
545
531
  presentStructuralRejection(error, io);
@@ -590,6 +576,7 @@ export async function runPublicCoderResume(
590
576
  packageRoot: env.packageRoot,
591
577
  ...(env.model === undefined ? {} : { model: env.model }),
592
578
  ...(env.extraPiArgs === undefined ? {} : { extraPiArgs: env.extraPiArgs }),
579
+ ...(request.message === undefined ? {} : { message: request.message }),
593
580
  });
594
581
 
595
582
  const result = await dispatchAdmittedCoder({
@@ -47,7 +47,8 @@ import {
47
47
  markRunRunning,
48
48
  markRunTerminal,
49
49
  renderResumeCommand,
50
- RESUME_TRANSPORT_ENVELOPE,
50
+ type PublicResumeRequest,
51
+ selectResumeContinuationPrompt,
51
52
  RunWriterLeaseHeldError,
52
53
  type RunWriterLease,
53
54
  type TypedProviderHttpObservation,
@@ -161,6 +162,7 @@ export function buildFixerResumeActivationExtraArgs(
161
162
  packageRoot: string;
162
163
  model?: SeatModelConfig;
163
164
  extraPiArgs?: readonly string[];
165
+ message?: string;
164
166
  },
165
167
  ): string[] {
166
168
  const diagnosisSkillPath = resolvePackagedMethodSkillPath(
@@ -196,7 +198,7 @@ export function buildFixerResumeActivationExtraArgs(
196
198
  "--mode",
197
199
  "json",
198
200
  ...buildSeatModelCliArgs(options.model),
199
- RESUME_TRANSPORT_ENVELOPE,
201
+ selectResumeContinuationPrompt(options.message),
200
202
  ];
201
203
  }
202
204
 
@@ -532,7 +534,7 @@ export async function runPublicFixer(
532
534
  * Restores role/phase/packet/prerequisites/session identity; model override is temporary.
533
535
  */
534
536
  export async function runPublicFixerResume(
535
- argv: readonly string[],
537
+ request: PublicResumeRequest,
536
538
  env: FixerRunEnv,
537
539
  io: CliIo,
538
540
  ): Promise<{
@@ -540,25 +542,9 @@ export async function runPublicFixerResume(
540
542
  admitted?: AdmittedFixerInvocation;
541
543
  terminal?: TerminalResult;
542
544
  }> {
543
- const runId = argv[0];
544
- if (runId === undefined || runId.trim() === "" || runId.startsWith("-")) {
545
- presentStructuralRejection(
546
- new CliUsageError("usage: ak-role resume <runId>"),
547
- io,
548
- );
549
- return { exitCode: 2 };
550
- }
551
- if (argv.length > 1) {
552
- presentStructuralRejection(
553
- new CliUsageError("resume takes exactly one run id"),
554
- io,
555
- );
556
- return { exitCode: 2 };
557
- }
558
-
559
545
  let loaded;
560
546
  try {
561
- loaded = await loadResumableFixerRun(env.home, runId);
547
+ loaded = await loadResumableFixerRun(env.home, request.runId);
562
548
  } catch (error) {
563
549
  if (error instanceof CliUsageError) {
564
550
  presentStructuralRejection(error, io);
@@ -601,6 +587,7 @@ export async function runPublicFixerResume(
601
587
  packageRoot: env.packageRoot,
602
588
  ...(env.model === undefined ? {} : { model: env.model }),
603
589
  ...(env.extraPiArgs === undefined ? {} : { extraPiArgs: env.extraPiArgs }),
590
+ ...(request.message === undefined ? {} : { message: request.message }),
604
591
  });
605
592
 
606
593
  const result = await dispatchAdmittedFixer({
@@ -41,7 +41,8 @@ import {
41
41
  markRunTerminal,
42
42
  renderResumeCommand,
43
43
  type TypedProviderHttpObservation,
44
- RESUME_TRANSPORT_ENVELOPE,
44
+ type PublicResumeRequest,
45
+ selectResumeContinuationPrompt,
45
46
  RunWriterLeaseHeldError,
46
47
  type RunWriterLease,
47
48
  } from "./run-lifecycle.ts";
@@ -147,6 +148,7 @@ export function buildJudgeResumeActivationExtraArgs(
147
148
  options: {
148
149
  model?: SeatModelConfig;
149
150
  extraPiArgs?: readonly string[];
151
+ message?: string;
150
152
  } = {},
151
153
  ): string[] {
152
154
  return [
@@ -164,7 +166,7 @@ export function buildJudgeResumeActivationExtraArgs(
164
166
  "--mode",
165
167
  "json",
166
168
  ...buildSeatModelCliArgs(options.model),
167
- RESUME_TRANSPORT_ENVELOPE,
169
+ selectResumeContinuationPrompt(options.message),
168
170
  ];
169
171
  }
170
172
 
@@ -500,7 +502,7 @@ export async function runPublicJudge(
500
502
  * is temporary for this invocation only.
501
503
  */
502
504
  export async function runPublicResume(
503
- argv: readonly string[],
505
+ request: PublicResumeRequest,
504
506
  env: JudgeRunEnv,
505
507
  io: CliIo,
506
508
  ): Promise<{
@@ -508,26 +510,9 @@ export async function runPublicResume(
508
510
  admitted?: AdmittedJudgeInvocation;
509
511
  terminal?: TerminalResult;
510
512
  }> {
511
- const runId = argv[0];
512
- if (runId === undefined || runId.trim() === "" || runId.startsWith("-")) {
513
- presentStructuralRejection(
514
- new CliUsageError("usage: ak-role resume <runId>"),
515
- io,
516
- );
517
- return { exitCode: 2 };
518
- }
519
- if (argv.length > 1) {
520
- // Resume does not accept free positionals beyond runId (model/thinking are global).
521
- presentStructuralRejection(
522
- new CliUsageError("resume takes exactly one run id"),
523
- io,
524
- );
525
- return { exitCode: 2 };
526
- }
527
-
528
513
  let loaded;
529
514
  try {
530
- loaded = await loadResumableJudgeRun(env.home, runId);
515
+ loaded = await loadResumableJudgeRun(env.home, request.runId);
531
516
  } catch (error) {
532
517
  if (error instanceof CliUsageError) {
533
518
  presentStructuralRejection(error, io);
@@ -553,6 +538,7 @@ export async function runPublicResume(
553
538
  const extraArgs = buildJudgeResumeActivationExtraArgs(admitted, {
554
539
  ...(env.model === undefined ? {} : { model: env.model }),
555
540
  ...(env.extraPiArgs === undefined ? {} : { extraPiArgs: env.extraPiArgs }),
541
+ ...(request.message === undefined ? {} : { message: request.message }),
556
542
  });
557
543
 
558
544
  const result = await dispatchAdmittedJudge({
@@ -50,7 +50,8 @@ import {
50
50
  markRunRunning,
51
51
  markRunTerminal,
52
52
  renderResumeCommand,
53
- RESUME_TRANSPORT_ENVELOPE,
53
+ type PublicResumeRequest,
54
+ selectResumeContinuationPrompt,
54
55
  RunWriterLeaseHeldError,
55
56
  type RunWriterLease,
56
57
  type TypedProviderHttpObservation,
@@ -152,6 +153,7 @@ export function buildMergerResumeActivationExtraArgs(
152
153
  packageRoot: string;
153
154
  model?: SeatModelConfig;
154
155
  extraPiArgs?: readonly string[];
156
+ message?: string;
155
157
  },
156
158
  ): string[] {
157
159
  const skillPath = resolvePackagedMethodSkillPath(
@@ -177,7 +179,7 @@ export function buildMergerResumeActivationExtraArgs(
177
179
  "--mode",
178
180
  "json",
179
181
  ...buildSeatModelCliArgs(options.model),
180
- RESUME_TRANSPORT_ENVELOPE,
182
+ selectResumeContinuationPrompt(options.message),
181
183
  ];
182
184
  }
183
185
 
@@ -613,7 +615,7 @@ export async function runPublicMerger(
613
615
  * Restores derived input/session identity; model override is temporary.
614
616
  */
615
617
  export async function runPublicMergerResume(
616
- argv: readonly string[],
618
+ request: PublicResumeRequest,
617
619
  env: MergerRunEnv,
618
620
  io: CliIo,
619
621
  ): Promise<{
@@ -621,25 +623,9 @@ export async function runPublicMergerResume(
621
623
  admitted?: AdmittedMergerInvocation;
622
624
  terminal?: TerminalResult;
623
625
  }> {
624
- const runId = argv[0];
625
- if (runId === undefined || runId.trim() === "" || runId.startsWith("-")) {
626
- presentStructuralRejection(
627
- new CliUsageError("usage: ak-role resume <runId>"),
628
- io,
629
- );
630
- return { exitCode: 2 };
631
- }
632
- if (argv.length > 1) {
633
- presentStructuralRejection(
634
- new CliUsageError("resume takes exactly one run id"),
635
- io,
636
- );
637
- return { exitCode: 2 };
638
- }
639
-
640
626
  let loaded;
641
627
  try {
642
- loaded = await loadResumableMergerRun(env.home, runId);
628
+ loaded = await loadResumableMergerRun(env.home, request.runId);
643
629
  } catch (error) {
644
630
  if (error instanceof CliUsageError) {
645
631
  presentStructuralRejection(error, io);
@@ -683,6 +669,7 @@ export async function runPublicMergerResume(
683
669
  packageRoot: env.packageRoot,
684
670
  ...(env.model === undefined ? {} : { model: env.model }),
685
671
  ...(env.extraPiArgs === undefined ? {} : { extraPiArgs: env.extraPiArgs }),
672
+ ...(request.message === undefined ? {} : { message: request.message }),
686
673
  });
687
674
 
688
675
  const result = await dispatchAdmittedMerger({
@@ -1146,8 +1146,11 @@ const SUPPORT_COMMAND_HELP = {
1146
1146
  resume: {
1147
1147
  command: "resume",
1148
1148
  summary: "Reopen an exact role run whose Pi session principal still exists.",
1149
- usage: ["ak-role resume <runId>"],
1150
- examples: ["ak-role resume 01abc…"],
1149
+ usage: ["ak-role resume <runId> [message]"],
1150
+ examples: [
1151
+ "ak-role resume 01abc…",
1152
+ "ak-role resume 01abc… \"owner ruling\"",
1153
+ ],
1151
1154
  },
1152
1155
  } as const satisfies Record<string, PublicCommandHelpFacts>;
1153
1156
 
@@ -48,7 +48,8 @@ import {
48
48
  markRunRunning,
49
49
  markRunTerminal,
50
50
  renderResumeCommand,
51
- RESUME_TRANSPORT_ENVELOPE,
51
+ type PublicResumeRequest,
52
+ selectResumeContinuationPrompt,
52
53
  RunWriterLeaseHeldError,
53
54
  type RunWriterLease,
54
55
  type TypedProviderHttpObservation,
@@ -169,6 +170,7 @@ export function buildReviewerResumeActivationExtraArgs(
169
170
  packageRoot: string;
170
171
  model?: SeatModelConfig;
171
172
  extraPiArgs?: readonly string[];
173
+ message?: string;
172
174
  },
173
175
  ): string[] {
174
176
  const skillPath = resolvePackagedMethodSkillPath(
@@ -201,7 +203,7 @@ export function buildReviewerResumeActivationExtraArgs(
201
203
  "--mode",
202
204
  "json",
203
205
  ...buildSeatModelCliArgs(options.model),
204
- RESUME_TRANSPORT_ENVELOPE,
206
+ selectResumeContinuationPrompt(options.message),
205
207
  ];
206
208
  }
207
209
 
@@ -552,7 +554,7 @@ export async function runPublicReviewer(
552
554
  * Restores task/base/session identity; model override is temporary.
553
555
  */
554
556
  export async function runPublicReviewerResume(
555
- argv: readonly string[],
557
+ request: PublicResumeRequest,
556
558
  env: ReviewerRunEnv,
557
559
  io: CliIo,
558
560
  ): Promise<{
@@ -560,25 +562,9 @@ export async function runPublicReviewerResume(
560
562
  admitted?: AdmittedReviewerInvocation;
561
563
  terminal?: TerminalResult;
562
564
  }> {
563
- const runId = argv[0];
564
- if (runId === undefined || runId.trim() === "" || runId.startsWith("-")) {
565
- presentStructuralRejection(
566
- new CliUsageError("usage: ak-role resume <runId>"),
567
- io,
568
- );
569
- return { exitCode: 2 };
570
- }
571
- if (argv.length > 1) {
572
- presentStructuralRejection(
573
- new CliUsageError("resume takes exactly one run id"),
574
- io,
575
- );
576
- return { exitCode: 2 };
577
- }
578
-
579
565
  let loaded;
580
566
  try {
581
- loaded = await loadResumableReviewerRun(env.home, runId);
567
+ loaded = await loadResumableReviewerRun(env.home, request.runId);
582
568
  } catch (error) {
583
569
  if (error instanceof CliUsageError) {
584
570
  presentStructuralRejection(error, io);
@@ -621,6 +607,7 @@ export async function runPublicReviewerResume(
621
607
  packageRoot: env.packageRoot,
622
608
  ...(env.model === undefined ? {} : { model: env.model }),
623
609
  ...(env.extraPiArgs === undefined ? {} : { extraPiArgs: env.extraPiArgs }),
610
+ ...(request.message === undefined ? {} : { message: request.message }),
624
611
  });
625
612
 
626
613
  const result = await dispatchAdmittedReviewer({
@@ -88,6 +88,22 @@ export type RoleRunRecord = {
88
88
  /** Package-owned turn trigger for resume. Not caller instruction and not semantic task content. */
89
89
  export const RESUME_TRANSPORT_ENVELOPE = "[ak-role:resume-continue]" as const;
90
90
 
91
+ /** Public manual resume request after the unique CLI parser owns runId + optional message. */
92
+ export type PublicResumeRequest = {
93
+ readonly runId: string;
94
+ /** Present when the caller supplied the post-runId argv (including empty string). */
95
+ readonly message?: string;
96
+ };
97
+
98
+ /**
99
+ * Unique continuation-prompt selector for manual/auto resume (#471).
100
+ * Message present → return bytes unchanged; absent → package transport envelope.
101
+ * Zero parse, zero classify, zero narrow.
102
+ */
103
+ export function selectResumeContinuationPrompt(message?: string): string {
104
+ return message !== undefined ? message : RESUME_TRANSPORT_ENVELOPE;
105
+ }
106
+
91
107
  const RUN_STATE_FILE = "run-state.json";
92
108
  const WRITER_LOCK_FILE = "writer.lock";
93
109