@akagilnc/pi-workflow-roles 0.1.2340 → 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
@@ -23,11 +23,11 @@ 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, Doctor, and Notary 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 <provider/model[:thinking]> 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
32
  Every run also prepares Navigator advice in the same Terminal. Configure seats like this:
33
33
 
@@ -100,6 +100,9 @@ ak-role analyst sweep --attach ./payload.md
100
100
  ak-role analyst --cohort \
101
101
  --group-a-label A --group-a-issues 1,2 \
102
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>"
103
106
  ```
104
107
 
105
108
  ## Names
package/README.zh-CN.md CHANGED
@@ -23,11 +23,11 @@ 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 <provider/model[:thinking]> 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
 
@@ -100,6 +100,9 @@ ak-role analyst sweep --attach ./payload.md
100
100
  ak-role analyst --cohort \
101
101
  --group-a-label A --group-a-issues 1,2 \
102
102
  --group-b-label B --group-b-issues 3,4
103
+
104
+ # escalate 后:把 owner 裁定喂回同一 session(标准链;细则见上方「读结果」resume 段)
105
+ ak-role resume <runId> "<裁定>"
103
106
  ```
104
107
 
105
108
  ## 班子(唐宋官署命名)
@@ -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.2340",
3
+ "version": "0.1.2348",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -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