@akagilnc/pi-workflow-roles 0.1.5382 → 0.1.5462

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 (87) hide show
  1. package/README.md +4 -2
  2. package/README.zh-CN.md +4 -2
  3. package/dist/acp-host/production-host.js +8843 -9012
  4. package/dist/archivist-record-pointer.js +38 -0
  5. package/dist/compliance-transport.js +7 -5
  6. package/dist/doctor-auditor.js +1 -0
  7. package/dist/engine-detour-usage.js +9 -36
  8. package/dist/gatekeeper-role.js +4 -18
  9. package/dist/headless-host/description.js +0 -209
  10. package/dist/headless-host/production-host.js +7828 -8247
  11. package/dist/host-contracts.js +8 -0
  12. package/dist/judge-role.js +14 -1
  13. package/dist/migrate-book-topology.js +215 -96
  14. package/dist/navigator-attendance.js +10 -9
  15. package/dist/navigator-invocation-identity.js +18 -0
  16. package/dist/packaged-role-registry.js +1 -9
  17. package/dist/pi/role-turn-host.js +46 -19
  18. package/dist/public-cli/auto-resume.js +29 -66
  19. package/dist/public-cli/cli.js +1 -0
  20. package/dist/public-cli/countersign-run.js +4 -18
  21. package/dist/public-cli/instruction-seat-run.js +258 -69
  22. package/dist/public-cli/main.js +7488 -7758
  23. package/dist/public-cli/post-admission.js +451 -349
  24. package/dist/public-cli/process-cancel.js +8 -0
  25. package/dist/public-cli/run-lifecycle.js +24 -94
  26. package/dist/public-cli/settlement.js +624 -1471
  27. package/dist/public-cli/terminal.js +34 -53
  28. package/dist/public-cli/turn-request.js +3 -0
  29. package/dist/public-role-summons.js +3 -8
  30. package/dist/receipt-delivery-policy.js +162 -28
  31. package/dist/run-terminal-artifacts.js +19 -14
  32. package/dist/serialize-thrown-value.js +7 -1
  33. package/dist/submission-errors.js +11 -1
  34. package/dist/submission-gate.js +27 -4
  35. package/dist/submission-ledger.js +21 -89
  36. package/package.json +1 -1
  37. package/scripts/build-package.mjs +0 -2
  38. package/src/acp-host/role-turn-host.ts +168 -75
  39. package/src/archivist-record-pointer.ts +48 -0
  40. package/src/compliance-transport.ts +13 -8
  41. package/src/doctor-auditor.ts +3 -0
  42. package/src/engine-detour-usage.ts +10 -39
  43. package/src/external-host-turn-loop.ts +100 -43
  44. package/src/gatekeeper-role.ts +7 -22
  45. package/src/headless-host/description.ts +4 -214
  46. package/src/headless-host/production-host.ts +2 -1
  47. package/src/headless-host/role-turn-host.ts +153 -186
  48. package/src/host-contracts.ts +20 -10
  49. package/src/judge-role.ts +24 -5
  50. package/src/navigator-attendance.ts +17 -12
  51. package/src/navigator-invocation-identity.ts +31 -0
  52. package/src/navigator-public-session.ts +1 -1
  53. package/src/navigator-session-contracts.ts +1 -1
  54. package/src/packaged-role-registry.ts +2 -10
  55. package/src/pi/adapter.ts +33 -11
  56. package/src/pi/role-turn-host.ts +49 -28
  57. package/src/prepared-role-turn.ts +2 -0
  58. package/src/public-cli/auto-resume.ts +27 -66
  59. package/src/public-cli/cli.ts +1 -0
  60. package/src/public-cli/countersign-run.ts +4 -22
  61. package/src/public-cli/instruction-seat-run.ts +307 -74
  62. package/src/public-cli/post-admission.ts +615 -459
  63. package/src/public-cli/process-cancel.ts +9 -0
  64. package/src/public-cli/run-lifecycle.ts +25 -126
  65. package/src/public-cli/settlement.ts +801 -1859
  66. package/src/public-cli/terminal.ts +42 -70
  67. package/src/public-cli/turn-request.ts +10 -1
  68. package/src/public-role-summons.ts +3 -8
  69. package/src/receipt-delivery-policy.ts +219 -28
  70. package/src/role-envelope.ts +21 -10
  71. package/src/role-runtime-dependencies.ts +1 -0
  72. package/src/role-runtime.ts +79 -102
  73. package/src/run-terminal-artifacts.ts +21 -17
  74. package/src/serialize-thrown-value.ts +7 -1
  75. package/src/submission-correctable-error.ts +5 -2
  76. package/src/submission-errors.ts +12 -1
  77. package/src/submission-gate.ts +34 -4
  78. package/src/submission-ledger.ts +30 -116
  79. package/src/worker-role.ts +19 -7
  80. package/src/worker-submission-gates.ts +29 -7
  81. package/dist/audit-escalation.js +0 -7
  82. package/dist/pi/known-failure.js +0 -51
  83. package/dist/typed-provider-http.js +0 -57
  84. package/dist/upstream-error-testimony.js +0 -19
  85. package/src/pi/known-failure.ts +0 -76
  86. package/src/typed-provider-http.ts +0 -89
  87. package/src/upstream-error-testimony.ts +0 -45
@@ -31,7 +31,10 @@ import {
31
31
  } from "./navigator-session-contracts.js";
32
32
  import { sitianReport } from "./sitian-facade.js";
33
33
  import { issueRoot, subjectPath } from "./work-subject-identity.js";
34
- import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE } from "./receipt-delivery-policy.js";
34
+ import {
35
+ createReceiptDeliveryPolicy,
36
+ NO_RECEIPT_LIFECYCLE_ENTRY_TYPE
37
+ } from "./receipt-delivery-policy.js";
35
38
  import { navigatorProseFromUnknown } from "./package-contracts/navigator-output.js";
36
39
  import { persistNavigatorWorkBase } from "./navigator-work-base.js";
37
40
  import { sha256Hex } from "./sha256.js";
@@ -250,8 +253,8 @@ function createNavigatorAttendance(options) {
250
253
  try {
251
254
  try {
252
255
  if (disposed) throw navigatorUnavailableError("session", new Error("Navigator attendance was disposed"));
253
- const delivery = createReceiptDeliveryPolicy();
254
- const promptAllowingRejectedPrepare = async (text, deliveryRequest) => {
256
+ const delivery = createReceiptDeliveryPolicy(options.deliveryRequestLimit);
257
+ const promptAllowingRejectedPrepare = async (text) => {
255
258
  await persistNavigatorWorkBase(activeSession.recordPointer(), { subject, authority });
256
259
  const entryStart = activeSession.entries().length;
257
260
  prepareBatchRejected = false;
@@ -278,17 +281,15 @@ function createNavigatorAttendance(options) {
278
281
  delivery.recordNestedNoReceipt(sessionNoReceipt);
279
282
  return;
280
283
  }
281
- if (deliveryRequest && output === void 0) delivery.recordDeliveryRequest();
282
284
  };
283
- await promptAllowingRejectedPrepare(request, false);
285
+ await promptAllowingRejectedPrepare(request);
284
286
  if (boundSettlement !== void 0) {
285
287
  while (output === void 0 && prepareBatchRejected && delivery.nextAction() === "request-delivery") {
286
- await promptAllowingRejectedPrepare(JSON.stringify(delivery.deliveryState()), true);
288
+ delivery.recordDeliveryRequest();
289
+ await promptAllowingRejectedPrepare(JSON.stringify(delivery.deliveryState()));
287
290
  }
288
291
  if (output === void 0 && delivery.nextAction() === "request-delivery") {
289
- while (delivery.nextAction() === "request-delivery") {
290
- delivery.recordDeliveryRequest();
291
- }
292
+ delivery.closeBudget();
292
293
  }
293
294
  if (output === void 0 && delivery.nextAction() === "no-receipt" && activeSession.providerFailure?.() === void 0) {
294
295
  const facts = delivery.facts({ runPointer: activeSession.recordPointer(), attemptPointer: invocationId });
@@ -86,6 +86,23 @@ function markerMatchesExpectedIdentity(marker, expected) {
86
86
  }
87
87
  return true;
88
88
  }
89
+ function packagedRoleOutputRejectionReason(message) {
90
+ if (typeof message.toolName !== "string") return void 0;
91
+ if (!PACKAGED_ROLE_OUTPUT_TOOLS.has(message.toolName) && message.toolName !== "ak_submission_output") {
92
+ return void 0;
93
+ }
94
+ if (message.isError !== true) return void 0;
95
+ if (classifyPackagedRoleTerminalResult(message).kind !== "nonterminal") return void 0;
96
+ const content = message.content;
97
+ if (typeof content === "string") return content.trim();
98
+ if (!Array.isArray(content)) return "";
99
+ return content.map((part) => {
100
+ if (isRecord(part) && part.type === "text" && typeof part.text === "string") {
101
+ return part.text;
102
+ }
103
+ return "";
104
+ }).join("").trim();
105
+ }
89
106
  function classifyPackagedRoleTerminalResult(message) {
90
107
  if (typeof message.toolName !== "string") return { kind: "nonterminal" };
91
108
  if (!PACKAGED_ROLE_OUTPUT_TOOLS.has(message.toolName) && message.toolName !== "ak_submission_output") return { kind: "nonterminal" };
@@ -181,6 +198,7 @@ export {
181
198
  isNavigatorInfrastructureFailureFact,
182
199
  markerMatchesExpectedIdentity,
183
200
  mintNavigatorInvocationId,
201
+ packagedRoleOutputRejectionReason,
184
202
  parseInvocationMarkerIdentity,
185
203
  resolveLifecycleInvocationPrincipal
186
204
  };
@@ -59,7 +59,6 @@ const PUBLIC_ROLE_RECORDS = [
59
59
  outputTool: JUDGE_OUTPUT_TOOL_NAME,
60
60
  auditTool: JUDGE_AUDIT_TOOL_NAME,
61
61
  settlement: "sealed",
62
- runnerFailure: "engine-detour-known-first",
63
62
  /** Factory board: court until another station has started, then marshal. */
64
63
  boardPlacement: "judge-history",
65
64
  /** Navigator subject is the public admitted instruction when the run is bound. */
@@ -177,7 +176,6 @@ const PUBLIC_ROLE_RECORDS = [
177
176
  },
178
177
  /** Frozen base/lens/authority become the initial prompt; instruction follows. */
179
178
  transportPrompt: "skill-args",
180
- runnerFailure: "engine-detour-record-first",
181
179
  boardPlacement: "marshal",
182
180
  activationFlags: [
183
181
  { field: "baseRevision", flag: "ak-review-base" },
@@ -272,10 +270,7 @@ const PUBLIC_ROLE_RECORDS = [
272
270
  /** Method-material load failure keeps the activation cause (not reviewer/fixer). */
273
271
  phases: [null],
274
272
  outputTool: MERGER_OUTPUT_TOOL_NAME,
275
- settlement: "residual",
276
- /** #836: residual scan stays on the whole host session. */
277
- residualScan: "session",
278
- residualTool: MERGER_OUTPUT_TOOL_NAME,
273
+ settlement: "sealed",
279
274
  artifactFace: {
280
275
  evidenceRole: true,
281
276
  leaves: [
@@ -452,7 +447,6 @@ const PUBLIC_ROLE_RECORDS = [
452
447
  phases: [null],
453
448
  outputTool: GATEKEEPER_OUTPUT_TOOL_NAME,
454
449
  settlement: "accepted",
455
- runnerFailure: "engine-detour-record-first",
456
450
  activationStage: "load-and-install",
457
451
  // Province materials; officers reuse their own public records below.
458
452
  sessionMaterials: ["CLAUDE.md", "souls/gatekeeper.md", "souls/quality-law.md", "souls/gate-output-guide.md"]
@@ -467,7 +461,6 @@ const PUBLIC_ROLE_RECORDS = [
467
461
  phases: [null],
468
462
  outputTool: NAVIGATOR_OUTPUT_TOOL_NAME,
469
463
  settlement: "accepted",
470
- runnerFailure: "engine-detour-record-first",
471
464
  activationStage: "load-and-install",
472
465
  sessionMaterials: ["CLAUDE.md", "souls/navigator.md"]
473
466
  },
@@ -490,7 +483,6 @@ const PUBLIC_ROLE_RECORDS = [
490
483
  settlement: "accepted",
491
484
  /** Court reask replaces the initial prompt. */
492
485
  reaskPrompt: true,
493
- runnerFailure: "engine-detour-record-first",
494
486
  gateStageLabel: "\u5BA1\u5211\u9662",
495
487
  /** Gate summon binds --subject judge and --source-run. */
496
488
  gateSummon: "subject-source",
@@ -14,6 +14,9 @@ import { ExplicitInternalActivationError } from "../host-contracts.js";
14
14
  import { applyEngineChildEnv, ENGINE_MODEL_FLAG_NAME, normalizeEngineName } from "../engine-detour.js";
15
15
  import { projectActivationFlags } from "../role-activation-flags.js";
16
16
  import { encodeUserDialogueStdin } from "../user-dialogue-stdin.js";
17
+ import { projectThrownFailureLeaf, retainPackageFault } from "../public-cli/settlement.js";
18
+ import { describeErrorIdentity } from "../public-cli/run-lifecycle.js";
19
+ import { RECEIPT_DELIVERY_LIMIT_ENV } from "../receipt-delivery-policy.js";
17
20
  /** Package-relative Internal role entrypoint (ADR 0052; same path as public-cli registry). */
18
21
  const INTERNAL_ROLE_ENTRYPOINT_RELATIVE = "extensions/role-runtime.ts";
19
22
  export function resolveInternalRoleEntrypoint(packageRoot) {
@@ -190,7 +193,6 @@ export function createDefaultPiSpawnRunner(options) {
190
193
  // fall back to rejecting on `error` if `close` never fires (e.g. spawn
191
194
  // never succeeded so no `close` event will arrive).
192
195
  let hasSpawned = false;
193
- let executionError;
194
196
  const armTimeoutAfterChildReady = () => {
195
197
  if (spawnOptions.timeoutMs === undefined)
196
198
  return;
@@ -206,6 +208,8 @@ export function createDefaultPiSpawnRunner(options) {
206
208
  child.kill("SIGTERM");
207
209
  };
208
210
  parentSignal?.addEventListener("abort", terminateForParentAbort, { once: true });
211
+ const packageErrors = [];
212
+ let identityFailure;
209
213
  let identityRecorded = Promise.resolve();
210
214
  child.once("spawn", () => {
211
215
  hasSpawned = true;
@@ -216,7 +220,10 @@ export function createDefaultPiSpawnRunner(options) {
216
220
  if (typeof runDirectory === "string" &&
217
221
  runDirectory !== "" &&
218
222
  options.recordLaunchedPiIdentity !== undefined) {
219
- identityRecorded = options.recordLaunchedPiIdentity(runDirectory, piIdentity);
223
+ identityRecorded = Promise.resolve().then(() => options.recordLaunchedPiIdentity(runDirectory, piIdentity)).catch((error) => {
224
+ packageErrors.push(error);
225
+ identityFailure = projectThrownFailureLeaf(error);
226
+ });
220
227
  }
221
228
  });
222
229
  child.stderr.setEncoding("utf8").on("data", (chunk) => {
@@ -226,8 +233,10 @@ export function createDefaultPiSpawnRunner(options) {
226
233
  // A pre-spawn error has no child lifecycle to close. After spawn,
227
234
  // retain the execution error and let the mandatory close event own
228
235
  // cleanup and the single settlement.
229
- if (settled || hasSpawned) {
230
- executionError = error;
236
+ if (settled)
237
+ return;
238
+ if (hasSpawned) {
239
+ packageErrors.push(error);
231
240
  return;
232
241
  }
233
242
  if (timer !== undefined)
@@ -236,33 +245,43 @@ export function createDefaultPiSpawnRunner(options) {
236
245
  settled = true;
237
246
  reject(error);
238
247
  });
239
- child.on("close", (code) => {
248
+ // `close` carries (code, signal): a signal-killed child reports code null.
249
+ // Both travel out so a signal death is never read as a clean exit.
250
+ child.on("close", (code, closeSignal) => {
240
251
  if (timer !== undefined)
241
252
  clearTimeout(timer);
242
253
  parentSignal?.removeEventListener("abort", terminateForParentAbort);
243
- void identityRecorded.then(() => {
254
+ void (async () => {
244
255
  if (settled)
245
256
  return;
246
257
  settled = true;
247
- if (executionError !== undefined) {
248
- reject(executionError);
249
- return;
250
- }
251
- if (stdinDeliveryError !== undefined) {
252
- reject(stdinDeliveryError);
253
- return;
258
+ await identityRecorded;
259
+ if (stdinDeliveryError !== undefined)
260
+ packageErrors.push(stdinDeliveryError);
261
+ for (const error of packageErrors) {
262
+ const diagnostic = `Pi transport handling failed beside host terminal: ${describeErrorIdentity(error)}`;
263
+ const runDirectory = spawnOptions.env.AK_ROLE_RUN_DIR;
264
+ if (runDirectory !== undefined) {
265
+ await retainPackageFault({ runDirectory, diagnostic, error });
266
+ }
267
+ else {
268
+ // Bare runner callers have no admitted artifact directory.
269
+ try {
270
+ process.stderr.write(`${diagnostic}\n`);
271
+ }
272
+ catch { /* Best-effort presentation beside the already closed child. */ }
273
+ }
254
274
  }
255
275
  resolveResult({
256
276
  code,
257
277
  stderr,
258
278
  timedOut,
279
+ ...(closeSignal === null ? {} : { signal: closeSignal }),
280
+ ...(code === 0 && !timedOut && closeSignal === null && identityFailure !== undefined
281
+ ? { knownFailure: identityFailure }
282
+ : {}),
259
283
  });
260
- }, (error) => {
261
- if (settled)
262
- return;
263
- settled = true;
264
- reject(error);
265
- });
284
+ })();
266
285
  });
267
286
  });
268
287
  };
@@ -307,6 +326,14 @@ export function createPiRoleTurnHost(config) {
307
326
  delete env.AK_ROLE_HOST;
308
327
  else
309
328
  env.AK_ROLE_HOST = request.host.trim();
329
+ // #1132: the one effective delivery-request ceiling travels to the child
330
+ // on its own env so the in-child role runtime and the worker submission
331
+ // gate count with the same configured number the AK seam resolved. Omit
332
+ // must not inherit a parent value (same rule as the other AK_ROLE_ axes).
333
+ if (request.deliveryRequestLimit === undefined)
334
+ delete env[RECEIPT_DELIVERY_LIMIT_ENV];
335
+ else
336
+ env[RECEIPT_DELIVERY_LIMIT_ENV] = String(request.deliveryRequestLimit);
310
337
  applyEngineChildEnv(env, request.engine);
311
338
  // Nested auditor dossier tool binds the parent run pointer when published.
312
339
  if (typeof process.env.AK_ROLE_AUDITOR_SOURCE_RUN === "string"
@@ -13,45 +13,46 @@
13
13
  */
14
14
  import { constants as fsConstants } from "node:fs";
15
15
  import { randomUUID } from "node:crypto";
16
- import { lstat, mkdir, open } from "node:fs/promises";
16
+ import { open } from "node:fs/promises";
17
17
  import { join } from "node:path";
18
- import { roleRunArtifactsDirectory } from "../role-run-placement.js";
19
- import { AUTO_RESUME_LIMIT, describeErrorIdentity, acquireRunWriterLease, markRunResumable, markRunTerminal, RunWriterLeaseHeldError, } from "./run-lifecycle.js";
18
+ import { AUTO_RESUME_LIMIT, describeErrorIdentity, acquireRunWriterLease, markRunTerminal, RunWriterLeaseHeldError, } from "./run-lifecycle.js";
20
19
  import { parseAutoResumeLimit } from "./config.js";
21
20
  import { processCancelSignalName } from "./process-cancel.js";
22
21
  import { isLawfulTypedTerminalOutcome, formatTerminalResult } from "./terminal.js";
23
- import { attachRecordedSubmissions, presentFailureTerminal, resolveControlledFailureResumeObservation, } from "./settlement.js";
22
+ import { attachRecordedSubmissions, ensureRealArtifactsDirectory, presentFailureTerminal, retainPackageFault, } from "./settlement.js";
23
+ export { ensureRealArtifactsDirectory };
24
24
  import { serializeThrownValue } from "../serialize-thrown-value.js";
25
- import { errorText, isEnoent } from "../unknown-value.js";
25
+ import { errorText } from "../unknown-value.js";
26
26
  const dummyIo = { stdout: () => { }, stderr: () => { } };
27
27
  /**
28
28
  * Persist run-state after a host-turn result, outside the retried dispatch try.
29
- * Lawful settlement always seals terminal — a typed 429 observation must not
30
- * win (#416 成功即停). 429 only marks resumable on the controlled-failure path.
31
- * Write failure must escape the retried dispatch try instead of becoming a
32
- * synthetic host-turn terminal.
29
+ * The host outcome already decided the terminal. This write only seals the run.
33
30
  */
34
- export async function persistReturnedRunState(admitted, options) {
35
- if (options?.lawful === true) {
36
- await markRunTerminal(admitted.runDirectory);
37
- return;
38
- }
39
- const resumeObservation = await resolveControlledFailureResumeObservation({
40
- runDirectory: admitted.runDirectory,
41
- });
42
- const typedHttp429 = resumeObservation.typedHttp429;
43
- if (admitted.principal !== undefined && typedHttp429 !== undefined) {
44
- await markRunResumable(admitted.runDirectory, typedHttp429);
45
- return;
46
- }
31
+ export async function persistReturnedRunState(admitted) {
47
32
  await markRunTerminal(admitted.runDirectory);
48
33
  }
49
- function presentTerminal(terminal, io) {
50
- if (terminal.roleOutcome.kind === "failure" || terminal.roleOutcome.kind === "no_receipt") {
51
- presentFailureTerminal(terminal, io);
34
+ export async function presentTerminal(terminal, io, runDirectory, held) {
35
+ try {
36
+ if (held !== undefined) {
37
+ for (const value of held)
38
+ io.stdout(value);
39
+ }
40
+ else if (terminal.roleOutcome.kind === "failure" || terminal.roleOutcome.kind === "no_receipt") {
41
+ presentFailureTerminal(terminal, io);
42
+ }
43
+ else {
44
+ io.stdout(formatTerminalResult(terminal));
45
+ }
52
46
  }
53
- else {
54
- io.stdout(formatTerminalResult(terminal));
47
+ catch (error) {
48
+ // Presentation consumes a settled report; it never reopens dispatch or
49
+ // replaces the host's cause, identity or open details.
50
+ await retainPackageFault({
51
+ runDirectory,
52
+ diagnostic: `terminal presentation failed beside host terminal: ${describeErrorIdentity(error)}`,
53
+ error,
54
+ stderr: (text) => io.stderr(text),
55
+ });
55
56
  }
56
57
  }
57
58
  /**
@@ -89,39 +90,6 @@ export class TurnDispatchedFailure extends Error {
89
90
  }
90
91
  /** Session custom-entry type carrying the pointer to one dispatch error file. */
91
92
  export const DISPATCH_ERROR_RETENTION_ENTRY_TYPE = "ak_run_dispatch_error_retention";
92
- /**
93
- * #182-A hardened path identity, mirrored from settlement.ts's
94
- * ensureAuditEvidenceDirectory: a planted symlink at the run directory or the
95
- * artifacts path must not receive a durable run artifact (O_NOFOLLOW only
96
- * protects the final file name; recursive mkdir would accept a symlinked
97
- * parent). Fails loudly with the true cause instead. Shared by every caller
98
- * that needs a durable run-artifacts write independent of session/dossier
99
- * health — dispatch-error retention here, and post-admission's best-effort
100
- * cleanup diagnostic (#840 bounce class 2).
101
- */
102
- export async function ensureRealArtifactsDirectory(runDirectory) {
103
- const runStat = await lstat(runDirectory);
104
- if (runStat.isSymbolicLink() || !runStat.isDirectory()) {
105
- throw new Error("run artifact retention: run directory is not a real directory");
106
- }
107
- const artifactsDir = roleRunArtifactsDirectory(runDirectory);
108
- try {
109
- const existing = await lstat(artifactsDir);
110
- if (existing.isSymbolicLink() || !existing.isDirectory()) {
111
- throw new Error("run artifact retention: artifacts path is not a real directory");
112
- }
113
- }
114
- catch (error) {
115
- if (!isEnoent(error))
116
- throw error;
117
- await mkdir(artifactsDir, { recursive: true });
118
- const created = await lstat(artifactsDir);
119
- if (created.isSymbolicLink() || !created.isDirectory()) {
120
- throw new Error("run artifact retention: artifacts directory is not a real directory");
121
- }
122
- }
123
- return artifactsDir;
124
- }
125
93
  /** Cycle- and bigint-safe JSON replacer so serialization itself cannot drop data. */
126
94
  function jsonSafeReplacer() {
127
95
  const seen = new WeakSet();
@@ -406,12 +374,7 @@ export async function runWithAutoResumeLoop(options) {
406
374
  } }
407
375
  : terminal;
408
376
  if (complete !== undefined) {
409
- if (isLawfulTypedTerminalOutcome(complete.roleOutcome)) {
410
- options.io.stdout(formatTerminalResult(complete));
411
- }
412
- else {
413
- presentTerminal(complete, options.io);
414
- }
377
+ await presentTerminal(complete, options.io, options.admitted.runDirectory);
415
378
  }
416
379
  return stoppedResult === undefined
417
380
  ? { exitCode: 1, terminal: complete }
@@ -846,6 +846,7 @@ export async function runAkRole(argv, env) {
846
846
  let current = result;
847
847
  while (current.exitCode === 0 && current.admitted?.correlationId !== undefined
848
848
  && current.terminal?.roleOutcome.kind === "accepted"
849
+ && current.terminal.roleOutcome.decisiveFacts?.directionUnsettled !== true
849
850
  && !latestPayloadEscalated(current.terminal.roleOutcome)) {
850
851
  const parentRunId = current.admitted.correlationId;
851
852
  resumeFailureContext = {
@@ -1,24 +1,11 @@
1
1
  import { readableGateItem } from "../readable-gate-item.js";
2
+ import { currentReplyRows } from "./terminal.js";
2
3
  import { isRecord } from "../unknown-value.js";
3
4
  function isEscalatePayload(payload) {
4
5
  if (!isRecord(payload))
5
6
  return false;
6
7
  return (payload.status === "escalate");
7
8
  }
8
- /**
9
- * Rows a parent may read from a nested diarist terminal (#836 / #953).
10
- * Accepted/audit use role result payloads; failure history is submissions only.
11
- */
12
- function courtDiaristPayloadRows(roleOutcome, submissions) {
13
- if (roleOutcome === undefined)
14
- return submissions ?? [];
15
- if (roleOutcome.kind === "accepted" || roleOutcome.kind === "audit_escalation") {
16
- return roleOutcome.payloads ?? [];
17
- }
18
- if (roleOutcome.kind === "failure")
19
- return submissions ?? [];
20
- return [];
21
- }
22
9
  /**
23
10
  * Parent diagnostic for court-diarist escalate (#953 / 失败诚实 / 传话).
24
11
  * Relays diarist escalate payload(s) via readableGateItem — never invents
@@ -29,8 +16,8 @@ function courtDiaristPayloadRows(roleOutcome, submissions) {
29
16
  * no courtAttempt sole-filter on diarist). Same law as secretariat parent face:
30
17
  * every escalate receipt as-is, last = currentConclusion.
31
18
  */
32
- function courtDiaristEscalateDiagnostic(roleOutcome, submissions) {
33
- const payloads = courtDiaristPayloadRows(roleOutcome, submissions);
19
+ function courtDiaristEscalateDiagnostic(terminal) {
20
+ const payloads = currentReplyRows(terminal);
34
21
  const escalatePayloads = [];
35
22
  for (const payload of payloads) {
36
23
  if (isEscalatePayload(payload))
@@ -100,14 +87,13 @@ export async function invokeCourtDiarist(input, env, io) {
100
87
  : { hostAdapters: env.hostAdapters }),
101
88
  });
102
89
  const roleOutcome = result.terminal?.roleOutcome;
103
- const submissions = result.terminal?.submissions;
104
90
  // Escalate routing is a boolean over the preserved sequence (#881). Reasons and
105
91
  // payload bodies stay on roleOutcome — never rewritten into a sole identity reason.
106
92
  if (latestPayloadEscalated(roleOutcome)) {
107
93
  return {
108
94
  identity: {
109
95
  kind: "escalate",
110
- diagnostic: courtDiaristEscalateDiagnostic(roleOutcome, submissions),
96
+ diagnostic: courtDiaristEscalateDiagnostic(result.terminal),
111
97
  },
112
98
  ...(result.admitted === undefined ? {} : { admitted: result.admitted }),
113
99
  ...(result.terminal === undefined ? {} : { terminal: result.terminal }),