@akagilnc/pi-workflow-roles 0.1.5015 → 0.1.5063

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.
Files changed (95) hide show
  1. package/README.md +2 -2
  2. package/README.zh-CN.md +2 -2
  3. package/THIRD_PARTY_NOTICES.md +8 -8
  4. package/dist/acp-host/production-host.js +525 -978
  5. package/dist/headless-host/description.js +0 -2
  6. package/dist/headless-host/production-host.js +528 -1014
  7. package/dist/host-descriptions.js +2 -3
  8. package/dist/migrate-book-topology.js +7 -12
  9. package/dist/packaged-role-registry.js +20 -18
  10. package/dist/pi/role-turn-host.js +3 -27
  11. package/dist/public-cli/cli.js +23 -2
  12. package/dist/public-cli/countersign-run.js +13 -8
  13. package/dist/public-cli/instruction-seat-run.js +75 -107
  14. package/dist/public-cli/invocation.js +13 -32
  15. package/dist/public-cli/machine-method-skills.js +150 -0
  16. package/dist/public-cli/main.js +549 -679
  17. package/dist/public-cli/post-admission.js +10 -5
  18. package/dist/public-cli/registry.js +2 -1
  19. package/dist/public-cli/run-lifecycle.js +12 -15
  20. package/dist/public-cli/seat-ticket-binding.js +5 -8
  21. package/dist/public-cli/settlement.js +12 -99
  22. package/dist/public-cli/turn-request.js +16 -8
  23. package/dist/public-role-summons.js +8 -3
  24. package/package.json +5 -1
  25. package/resources/countersign-ticket-issue.md +5 -0
  26. package/resources/diarist-collect.md +1 -0
  27. package/resources/secretariat-ticket-identity.md +15 -0
  28. package/scripts/build-package.mjs +1 -6
  29. package/src/headless-host/description.ts +0 -3
  30. package/src/headless-host/role-turn-host.ts +2 -23
  31. package/src/host-contracts.ts +2 -23
  32. package/src/host-descriptions.ts +2 -3
  33. package/src/packaged-role-registry.ts +17 -16
  34. package/src/pi/adapter.ts +0 -27
  35. package/src/pi/role-turn-host.ts +3 -31
  36. package/src/public-cli/cli.ts +24 -2
  37. package/src/public-cli/countersign-run.ts +17 -10
  38. package/src/public-cli/instruction-seat-run.ts +75 -105
  39. package/src/public-cli/invocation.ts +10 -38
  40. package/src/public-cli/machine-method-skills.ts +153 -0
  41. package/src/public-cli/post-admission.ts +12 -6
  42. package/src/public-cli/registry.ts +1 -0
  43. package/src/public-cli/run-lifecycle.ts +12 -13
  44. package/src/public-cli/seat-ticket-binding.ts +5 -8
  45. package/src/public-cli/settlement.ts +6 -138
  46. package/src/public-cli/turn-request.ts +24 -16
  47. package/src/public-role-summons.ts +16 -3
  48. package/src/reviewer-role.ts +1 -12
  49. package/src/role-envelope.ts +0 -13
  50. package/src/role-runtime-dependencies.ts +0 -13
  51. package/src/role-runtime.ts +4 -56
  52. package/src/worker-role.ts +1 -81
  53. package/dist/method-host-plugin/.claude-plugin/plugin.json +0 -5
  54. package/dist/method-host-plugin/skills/ak-cross-m-review/CONTEXT.md +0 -48
  55. package/dist/method-host-plugin/skills/ak-cross-m-review/LICENSE +0 -21
  56. package/dist/method-host-plugin/skills/ak-cross-m-review/SKILL.md +0 -170
  57. package/dist/method-host-plugin/skills/ak-cross-m-review/prompts/cmr-completeness.md +0 -118
  58. package/dist/method-host-plugin/skills/ak-cross-m-review/prompts/cmr-reviewer.md +0 -128
  59. package/dist/method-host-plugin/skills/ak-cross-m-review/provenance.json +0 -41
  60. package/dist/method-host-plugin/skills/diagnosing-bugs/SKILL.md +0 -134
  61. package/dist/method-host-plugin/skills/diagnosing-bugs/agents/openai.yaml +0 -3
  62. package/dist/method-host-plugin/skills/diagnosing-bugs/provenance.json +0 -31
  63. package/dist/method-host-plugin/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -41
  64. package/dist/method-host-plugin/skills/resolving-merge-conflicts/SKILL.md +0 -14
  65. package/dist/method-host-plugin/skills/resolving-merge-conflicts/agents/openai.yaml +0 -3
  66. package/dist/method-host-plugin/skills/resolving-merge-conflicts/provenance.json +0 -26
  67. package/dist/method-host-plugin/skills/tdd/SKILL.md +0 -38
  68. package/dist/method-host-plugin/skills/tdd/agents/openai.yaml +0 -3
  69. package/dist/method-host-plugin/skills/tdd/mocking.md +0 -59
  70. package/dist/method-host-plugin/skills/tdd/provenance.json +0 -36
  71. package/dist/method-host-plugin/skills/tdd/tests.md +0 -77
  72. package/dist/package-resources/method-skill.js +0 -259
  73. package/resources/method-host-plugin/.claude-plugin/plugin.json +0 -5
  74. package/resources/methods/ak-cross-m-review/CONTEXT.md +0 -48
  75. package/resources/methods/ak-cross-m-review/LICENSE +0 -21
  76. package/resources/methods/ak-cross-m-review/SKILL.md +0 -170
  77. package/resources/methods/ak-cross-m-review/prompts/cmr-completeness.md +0 -118
  78. package/resources/methods/ak-cross-m-review/prompts/cmr-reviewer.md +0 -128
  79. package/resources/methods/ak-cross-m-review/provenance.json +0 -41
  80. package/resources/methods/diagnosing-bugs/SKILL.md +0 -134
  81. package/resources/methods/diagnosing-bugs/agents/openai.yaml +0 -3
  82. package/resources/methods/diagnosing-bugs/provenance.json +0 -31
  83. package/resources/methods/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -41
  84. package/resources/methods/resolving-merge-conflicts/SKILL.md +0 -14
  85. package/resources/methods/resolving-merge-conflicts/agents/openai.yaml +0 -3
  86. package/resources/methods/resolving-merge-conflicts/provenance.json +0 -26
  87. package/resources/methods/tdd/SKILL.md +0 -38
  88. package/resources/methods/tdd/agents/openai.yaml +0 -3
  89. package/resources/methods/tdd/mocking.md +0 -59
  90. package/resources/methods/tdd/provenance.json +0 -36
  91. package/resources/methods/tdd/tests.md +0 -77
  92. package/src/canonical-skill-binding.ts +0 -118
  93. package/src/host-native-method.ts +0 -185
  94. package/src/package-resources/method-skill-binding.ts +0 -60
  95. package/src/package-resources/method-skill.ts +0 -363
@@ -56,9 +56,8 @@ export const HOST_DESCRIPTIONS: Readonly<Record<string, AcpHostDescription>> = O
56
56
  * Headless CLI family (#645 / #646). Claude print-mode is the first row;
57
57
  * codex exec (#646) adds another. Protocol-specific argv/parse live in
58
58
  * headless-host helpers (#752 per-host impl).
59
- * Claude fixedArgs: print mode, full permissions. stream-json + verbose: live
60
- * host events for sitian records (#811); result is last line. Forced methods
61
- * ride `--plugin-dir` (#922); operator skill/setting surfaces stay open.
59
+ * Claude fixedArgs: print mode. stream-json + verbose: live
60
+ * host events for sitian records (#811); result is last line.
62
61
  */
63
62
  export const HEADLESS_HOST_DESCRIPTIONS: Readonly<Record<string, HeadlessHostDescription>> = Object.freeze({
64
63
  "claude": Object.freeze({
@@ -42,7 +42,6 @@ export type PackagedArtifactFace = {
42
42
  readonly reportPhase?: true;
43
43
  readonly evidenceRole?: true;
44
44
  readonly leaves: readonly PackagedArtifactLeaf[];
45
- readonly method?: "optional" | "observed";
46
45
  readonly doctorReportFacts?: true;
47
46
  };
48
47
 
@@ -132,7 +131,6 @@ export const PUBLIC_ROLE_RECORDS = [
132
131
  { key: "prerequisitesPath", omitUndefined: true },
133
132
  { key: "prerequisites" },
134
133
  ],
135
- method: "observed",
136
134
  },
137
135
  boardPlacement: "marshal",
138
136
  activationFlags: [
@@ -158,7 +156,6 @@ export const PUBLIC_ROLE_RECORDS = [
158
156
  worker: true,
159
157
  applyMethod: "tdd",
160
158
  /** Method-material load failure keeps the activation cause (not reviewer/fixer). */
161
- methodLoadFailureCause: "activation",
162
159
  boardPlacement: "coder",
163
160
  phases: ["plan", "apply"],
164
161
  outputTool: CODER_OUTPUT_TOOL_NAME,
@@ -170,7 +167,6 @@ export const PUBLIC_ROLE_RECORDS = [
170
167
  { key: "phase" },
171
168
  { key: "taskPath" },
172
169
  ],
173
- method: "optional",
174
170
  },
175
171
  activationFlags: [
176
172
  { field: "taskPath", flag: "ak-coder-task", binds: "input" },
@@ -212,7 +208,6 @@ export const PUBLIC_ROLE_RECORDS = [
212
208
  { key: "authorityRefs", copyArray: true },
213
209
  { key: "callerProvenance", callerProvenance: true },
214
210
  ],
215
- method: "observed",
216
211
  },
217
212
  /** Frozen base/lens/authority become the initial prompt; instruction follows. */
218
213
  transportPrompt: "skill-args",
@@ -311,7 +306,6 @@ export const PUBLIC_ROLE_RECORDS = [
311
306
  methodSkills: ["resolving-merge-conflicts"],
312
307
  settleMethod: "resolving-merge-conflicts",
313
308
  /** Method-material load failure keeps the activation cause (not reviewer/fixer). */
314
- methodLoadFailureCause: "activation",
315
309
  phases: [null],
316
310
  outputTool: MERGER_OUTPUT_TOOL_NAME,
317
311
  settlement: "residual",
@@ -324,7 +318,6 @@ export const PUBLIC_ROLE_RECORDS = [
324
318
  { key: "mergerInputPath" },
325
319
  { key: "derived" },
326
320
  ],
327
- method: "observed",
328
321
  },
329
322
  activationFlags: [
330
323
  { field: "inputPath", from: "mergerInputPath", flag: "ak-merger-input", binds: "input" },
@@ -391,7 +384,7 @@ export const PUBLIC_ROLE_RECORDS = [
391
384
  activationStage: "load-and-install",
392
385
  receiptStatusKey: "status",
393
386
  // #924: 公用《票面法》三席同装
394
- sessionMaterials: ["CLAUDE.md", "souls/countersign.md", "souls/ticket-law.md"],
387
+ sessionMaterials: ["CLAUDE.md", "souls/countersign.md", "souls/ticket-law.md", "resources/countersign-ticket-issue.md"],
395
388
  },
396
389
  {
397
390
  role: "secretariat",
@@ -399,7 +392,7 @@ export const PUBLIC_ROLE_RECORDS = [
399
392
  presentSettled: "always",
400
393
  summonResume: false,
401
394
  admission: "instruction",
402
- sameParent: "court-diarist",
395
+ sameParent: "none",
403
396
  phases: [null],
404
397
  outputTool: SECRETARIAT_OUTPUT_TOOL_NAME,
405
398
  settlement: "accepted",
@@ -415,6 +408,7 @@ export const PUBLIC_ROLE_RECORDS = [
415
408
  "CLAUDE.md",
416
409
  "souls/secretariat.md",
417
410
  "souls/ticket-law.md",
411
+ "resources/secretariat-ticket-identity.md",
418
412
  "souls/countersign.md",
419
413
  "souls/notary.md",
420
414
  ],
@@ -581,6 +575,20 @@ export function packagedRoleMetadata(role: string): PackagedRoleMetadata | undef
581
575
  return PACKAGED_ROLE_REGISTRY.find((entry) => entry.role === role);
582
576
  }
583
577
 
578
+ /** Names any public seat may require. Setup and missing-Skill checks both read this. */
579
+ export function packagedMethodSkillNames(): readonly string[] {
580
+ const names: string[] = [];
581
+ for (const record of PACKAGED_ROLE_REGISTRY) {
582
+ if ("methodSkills" in record && record.methodSkills !== undefined) {
583
+ for (const name of record.methodSkills) if (!names.includes(name)) names.push(name);
584
+ }
585
+ if ("applyMethod" in record && record.applyMethod !== undefined && !names.includes(record.applyMethod)) {
586
+ names.push(record.applyMethod);
587
+ }
588
+ }
589
+ return names;
590
+ }
591
+
584
592
  /** Review officers (台院 / 符宝郎 / 审刑院). Absent on every other seat. */
585
593
  export function isOfficerReviewSeat(role: string): boolean {
586
594
  const record = packagedRoleMetadata(role);
@@ -651,13 +659,6 @@ export function packagedRoleOutputTool(role: string): string | undefined {
651
659
  return packagedRoleMetadata(role)?.outputTool;
652
660
  }
653
661
 
654
- /** Method-material load failure cause declared on the seat. Absent means no typed cause. */
655
- export function packagedMethodLoadFailureCause(role: string): "activation" | undefined {
656
- const record = packagedRoleMetadata(role);
657
- if (record === undefined || !("methodLoadFailureCause" in record)) return undefined;
658
- return record.methodLoadFailureCause;
659
- }
660
-
661
662
  /** Diarist binds a board ticket onto its own run. Other seats do not. */
662
663
  export function packagedBindsBoardTicket(role: string): boolean {
663
664
  const record = packagedRoleMetadata(role);
package/src/pi/adapter.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import {
2
- parseSkillBlock,
3
2
  type BeforeAgentStartEventResult,
4
3
  type ExtensionAPI,
5
4
  type ExtensionContext,
@@ -166,32 +165,6 @@ export function createPiRoleHostAdapter(
166
165
  deliverSubmissionRejection(_rejection) {
167
166
  // #836 删 1/A4.5: no package-authored non-sole resume sentence.
168
167
  },
169
- capabilities: {
170
- skillExpansion(prompt) {
171
- const parsed = parseSkillBlock(prompt);
172
- if (parsed == null) return undefined;
173
- const userMessage = parsed.userMessage ?? "";
174
- return Object.freeze({
175
- name: parsed.name,
176
- location: parsed.location,
177
- content: parsed.content,
178
- userMessage,
179
- });
180
- },
181
- /** Recover plain original request from Pi-native `/skill:` turn text (ADR 0082). */
182
- skillOriginalRequest(name, text) {
183
- const token = `/skill:${name}`;
184
- const trimmed = text.trimStart();
185
- if (
186
- trimmed === token
187
- || trimmed.startsWith(`${token} `)
188
- || trimmed.startsWith(`${token}\n`)
189
- ) {
190
- return text.slice(text.indexOf(token) + token.length).trim();
191
- }
192
- return text.trim();
193
- },
194
- },
195
168
  registerFlag: (name, definition) => pi.registerFlag(name, definition),
196
169
  getFlag: (name) => pi.getFlag(name),
197
170
  registerTool: (tool) => pi.registerTool(toPiToolDefinition(
@@ -6,7 +6,7 @@
6
6
  import { execFile, spawn } from "node:child_process";
7
7
  import { constants } from "node:fs";
8
8
  import { access, appendFile, readFile, realpath } from "node:fs/promises";
9
- import { basename, delimiter, dirname, isAbsolute, join, resolve } from "node:path";
9
+ import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
10
10
  import { platform } from "node:process";
11
11
  import { promisify } from "node:util";
12
12
  import { randomUUID } from "node:crypto";
@@ -86,38 +86,12 @@ function buildMethodArgs(methods: readonly MethodBinding[]): string[] {
86
86
  const skillArgs: string[] = [];
87
87
  for (const method of methods) {
88
88
  if (method.kind === "skill") {
89
- skillArgs.push("--skill", method.path);
89
+ skillArgs.push("--skill", dirname(method.path));
90
90
  }
91
91
  }
92
92
  return skillArgs;
93
93
  }
94
94
 
95
- /**
96
- * Pi-native `/skill:<name>` for a single forced method (ADR 0082: pi is one adapter
97
- * with no privilege; Pi-only seams stay inside the pi adapter).
98
- * Driven by typed RoleTurnRequest.methods — middle-layer decision, Pi-only syntax.
99
- * Zero/many methods leave the prompt alone (Fixer optional pair stays `--skill` only).
100
- */
101
- export function applyPiNativeSkillInvocation(
102
- methods: readonly MethodBinding[],
103
- prompt: string,
104
- ): string {
105
- const skills = methods.filter((method) => method.kind === "skill");
106
- if (skills.length !== 1) return prompt;
107
- const name = basename(dirname(skills[0]!.path));
108
- if (name.length === 0) return prompt;
109
- const token = `/skill:${name}`;
110
- const trimmed = prompt.trimStart();
111
- if (
112
- trimmed === token
113
- || trimmed.startsWith(`${token} `)
114
- || trimmed.startsWith(`${token}\n`)
115
- ) {
116
- return prompt;
117
- }
118
- return prompt.length === 0 ? token : `${token} ${prompt}`;
119
- }
120
-
121
95
  /**
122
96
  * Pi last-hop argv after `--no-extensions -e entry` (#819).
123
97
  * Activation flag membership comes from middle-layer projectActivationFlags;
@@ -165,9 +139,7 @@ function piUserDialogueBody(request: RoleTurnRequest): string {
165
139
  const _exhaustive: never = request.continuation;
166
140
  return _exhaustive;
167
141
  })();
168
- return request.continuation.kind === "resume"
169
- ? rawPrompt
170
- : applyPiNativeSkillInvocation(request.methods, rawPrompt);
142
+ return rawPrompt;
171
143
  }
172
144
 
173
145
  export type PiSpawnRunner = (
@@ -48,6 +48,7 @@ import { packagedModelParent } from "../packaged-role-registry.ts";
48
48
  import { seatModelOnly } from "./registry.ts";
49
49
  import { CliUsageError } from "./cli-errors.ts";
50
50
  import type { CliIo } from "./cli-io.ts";
51
+ import { runMachineSkillSetup } from "./machine-method-skills.ts";
51
52
  import type { PostAdmissionEnv } from "./post-admission.ts";
52
53
  import type { RoleTurnHost } from "../host-contracts.ts";
53
54
  import { appendPiSessionCustomEntry } from "../pi/role-turn-host.ts";
@@ -77,7 +78,8 @@ import {
77
78
  type PublicRoleOptionOwner,
78
79
  type TypedOptionConsumer,
79
80
  } from "./option-definitions.ts";
80
- import { runPublicInstructionSeat, runPublicInstructionSeatResume } from "./instruction-seat-run.ts";
81
+ import { continueParentAfterChild, runPublicInstructionSeat, runPublicInstructionSeatResume } from "./instruction-seat-run.ts";
82
+ import { courtDiaristEscalated } from "./countersign-run.ts";
81
83
  import { runPublicAnalyst } from "./analyst-run.ts";
82
84
  import {
83
85
  AUTO_RESUME_LIMIT,
@@ -1178,6 +1180,11 @@ export async function runAkRole(
1178
1180
  };
1179
1181
  }
1180
1182
 
1183
+ if (parsed.command === "setup") {
1184
+ if (parsed.args.length !== 0) throw new CliUsageError("setup takes no arguments");
1185
+ return { exitCode: await runMachineSkillSetup(home, io.stdout) };
1186
+ }
1187
+
1181
1188
  // #724 explicit fresh summons: `ak-role new <role> …` — same role argv, always mint.
1182
1189
  // Rewrites onto the role command with freshSummons; auto-resume and resume stay intact.
1183
1190
  if (parsed.command === "new") {
@@ -1229,7 +1236,22 @@ export async function runAkRole(
1229
1236
  }),
1230
1237
  io,
1231
1238
  );
1232
- return cliResultFromRoleRun(result);
1239
+ let current = result;
1240
+ while (
1241
+ current.exitCode === 0 && current.admitted?.correlationId !== undefined
1242
+ && current.terminal?.roleOutcome.kind === "accepted"
1243
+ && !courtDiaristEscalated(current.terminal.roleOutcome)
1244
+ ) {
1245
+ const parentRunId = current.admitted.correlationId;
1246
+ const parentRole = await peekRoleRunRole(home, parentRunId);
1247
+ if (parentRole === undefined) break;
1248
+ const parentSeat = resolveEffectiveSeat(config, parentRole, credentials, invocationFromParsed(parsed));
1249
+ const parentEnv = createRoleEnvironment(env, {
1250
+ role: parentRole, home, agentDir, cwd, credentials, seat: parentSeat, config,
1251
+ });
1252
+ current = await continueParentAfterChild(parentRunId, current.admitted, parentEnv, io);
1253
+ }
1254
+ return cliResultFromRoleRun(current);
1233
1255
  }
1234
1256
 
1235
1257
  if (
@@ -45,8 +45,8 @@ export type CountersignRunEnv = PostAdmissionEnv & {
45
45
  */
46
46
  gateReviewInstruction?: string;
47
47
  /**
48
- * #969 / #987 gate path: parent run durable board ticket handoff for bind only.
49
- * Resume lookup uses parentRunPath — never this ticket number (#987 Result 7).
48
+ * #969 / #987 gate path: parent run durable board ticket handoff for bind.
49
+ * Same-ticket resume lookup narrows the parent path by this typed number.
50
50
  */
51
51
  boundTicketNumber?: number;
52
52
  /**
@@ -79,6 +79,7 @@ export type CourtDiaristIdentity =
79
79
  export type CourtDiaristInvocationResult = {
80
80
  readonly identity: CourtDiaristIdentity;
81
81
  readonly admitted?: import("./invocation.ts").AdmittedRoleInvocation;
82
+ readonly terminal?: import("./terminal.ts").TerminalResult;
82
83
  readonly failedWithoutEscalate?: { readonly diagnostic: string };
83
84
  };
84
85
 
@@ -161,7 +162,7 @@ export type CourtDiaristSummonEnv = Pick<
161
162
  * - Multiple submissions: last qualifying set wins.
162
163
  * Live path never shape-rejects the role turn; durable damage is a separate seam.
163
164
  */
164
- function courtTicketNumbersFromOutcome(
165
+ export function courtTicketNumbersFromOutcome(
165
166
  roleOutcome: TerminalRoleOutcome | undefined,
166
167
  principalTicket: number,
167
168
  submissions?: readonly unknown[],
@@ -199,7 +200,7 @@ function courtTicketNumbersFromOutcome(
199
200
  }
200
201
 
201
202
  /** Routing boolean over the child's own typed sequence — does not pick or rewrite a sole row. */
202
- function courtDiaristEscalated(
203
+ export function courtDiaristEscalated(
203
204
  roleOutcome: TerminalRoleOutcome | undefined,
204
205
  ): boolean {
205
206
  if (roleOutcome === undefined) return false;
@@ -280,6 +281,7 @@ export async function invokeCourtDiarist(
280
281
  diagnostic: courtDiaristEscalateDiagnostic(roleOutcome, submissions),
281
282
  },
282
283
  ...(result.admitted === undefined ? {} : { admitted: result.admitted }),
284
+ ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
283
285
  };
284
286
  }
285
287
 
@@ -328,14 +330,15 @@ export async function invokeCourtDiarist(
328
330
  * Missing ticketNumber (true-unbound / identity deferred) skips the refresh
329
331
  * station — no diary is minted for a true-unbound run. First-entry identity
330
332
  * lives on the shared countersign entry (typed 起居郎 key for bind). Bound refresh:
331
- * 起居郎 failure propagates (失败诚实).
333
+ * technical failure propagates; LLM escalation remains a recorded conclusion.
332
334
  * Path delivery onto materials is not this station's job — post-admission owns it.
333
335
  */
334
336
  export async function runCountersignCourtDiaristStation(
335
337
  admitted: AdmittedCountersignInvocation,
336
338
  env: CountersignRunEnv,
337
339
  io: CliIo,
338
- ): Promise<void> {
340
+ afterTicketNumber?: number,
341
+ ): Promise<{ readonly terminal: import("./terminal.ts").TerminalResult; readonly childRunId?: string } | undefined> {
339
342
  if (env.runCourtDiaristStation !== undefined) {
340
343
  await env.runCourtDiaristStation(admitted);
341
344
  return;
@@ -362,7 +365,8 @@ export async function runCountersignCourtDiaristStation(
362
365
  refreshTickets = [admitted.ticketNumber];
363
366
  }
364
367
 
365
- for (const ticketNumber of refreshTickets) {
368
+ const completedIndex = afterTicketNumber === undefined ? -1 : refreshTickets.indexOf(afterTicketNumber);
369
+ for (const ticketNumber of refreshTickets.slice(completedIndex + 1)) {
366
370
  const outcome = await invokeCourtDiarist(
367
371
  {
368
372
  instruction: `整理 #${ticketNumber} 的本案依据。`,
@@ -376,13 +380,16 @@ export async function runCountersignCourtDiaristStation(
376
380
  io,
377
381
  );
378
382
 
379
- if (outcome.identity.kind === "escalate") {
380
- throw new StationChildExhaustedError(outcome.identity.diagnostic);
381
- }
382
383
  if (outcome.failedWithoutEscalate !== undefined) {
383
384
  throw new StationChildExhaustedError(
384
385
  outcome.failedWithoutEscalate.diagnostic,
385
386
  );
386
387
  }
388
+ if (outcome.identity.kind === "escalate" && outcome.terminal !== undefined) {
389
+ return {
390
+ terminal: outcome.terminal,
391
+ ...(outcome.admitted === undefined ? {} : { childRunId: outcome.admitted.runId }),
392
+ };
393
+ }
387
394
  }
388
395
  }