@akagilnc/pi-workflow-roles 0.1.3565 → 0.1.3621

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 (142) hide show
  1. package/README.md +4 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/acp-host/production-host.js +18477 -8099
  4. package/dist/analyst-gate-cycles-read.js +387 -0
  5. package/dist/archivist-record-entry.js +22 -1
  6. package/dist/audit-escalation.js +6 -0
  7. package/dist/auditor-soul.js +74 -0
  8. package/dist/collector-config.js +86 -0
  9. package/dist/collector-evidence.js +316 -0
  10. package/dist/collector-github.js +527 -0
  11. package/dist/collector-ledger.js +853 -0
  12. package/dist/collector-tool-schemas.js +33 -0
  13. package/dist/compliance-transport.js +104 -52
  14. package/dist/diarist-contracts.js +72 -0
  15. package/dist/diarist-mechanical.js +411 -0
  16. package/dist/diarist-ticket-resolution.js +177 -0
  17. package/dist/diarist.js +311 -0
  18. package/dist/doctor-auditor.js +25 -0
  19. package/dist/doctor-contracts.js +2 -0
  20. package/dist/doctor-evidence.js +142 -0
  21. package/dist/gatekeeper-role.js +104 -81
  22. package/dist/host-transition-prior-native.js +78 -0
  23. package/dist/institutional-resolution.js +1 -95
  24. package/dist/judge-auditor.js +26 -0
  25. package/dist/ledger-session-read.js +227 -0
  26. package/dist/merger-git-state.js +78 -0
  27. package/dist/navigator-attendance.js +15 -7
  28. package/dist/navigator-public-session.js +170 -0
  29. package/dist/navigator-session-contracts.js +36 -40
  30. package/dist/notary-source-run.js +122 -0
  31. package/dist/package-contracts/auditor-output.js +66 -0
  32. package/dist/package-contracts/evidence-child-output.js +34 -0
  33. package/dist/package-contracts/judge-output.js +2 -0
  34. package/dist/package-contracts/terminating-tools.js +44 -3
  35. package/dist/package-resources/method-skill.js +254 -0
  36. package/dist/packaged-role-registry.js +56 -0
  37. package/dist/pi/durable-principal.js +61 -0
  38. package/dist/pi/in-process-session.js +25 -6
  39. package/dist/pi/known-failure.js +52 -0
  40. package/dist/pi/role-turn-host.js +430 -0
  41. package/dist/public-cli/auto-resume.js +414 -0
  42. package/dist/public-cli/cli-errors.js +8 -0
  43. package/dist/public-cli/cli-io.js +1 -0
  44. package/dist/public-cli/command-renderer.js +5 -0
  45. package/dist/public-cli/doctor-run.js +84 -0
  46. package/dist/public-cli/inspector-run.js +136 -0
  47. package/dist/public-cli/instruction-seat-run.js +256 -0
  48. package/dist/public-cli/invocation.js +2225 -0
  49. package/dist/public-cli/judge-run.js +118 -0
  50. package/dist/public-cli/load-production-acp-host.js +40 -0
  51. package/dist/public-cli/main.js +1511 -1124
  52. package/dist/public-cli/notary-run.js +161 -0
  53. package/dist/public-cli/option-definitions.js +1192 -0
  54. package/dist/public-cli/post-admission.js +667 -0
  55. package/dist/public-cli/public-run-credentials.js +49 -0
  56. package/dist/public-cli/registry.js +8 -2
  57. package/dist/public-cli/reviewer-dispatch-rejection.js +77 -0
  58. package/dist/public-cli/run-lifecycle.js +1344 -0
  59. package/dist/public-cli/seat-ticket-binding.js +113 -0
  60. package/dist/public-cli/settlement.js +3441 -0
  61. package/dist/public-cli/terminal.js +135 -0
  62. package/dist/public-cli/turn-request.js +25 -0
  63. package/dist/public-role-summons.js +301 -0
  64. package/dist/receipt-delivery-policy.js +10 -0
  65. package/dist/reviewer-child-executor.js +80 -8
  66. package/dist/reviewer-execution-ledger.js +2 -1
  67. package/dist/run-terminal-artifacts.js +195 -0
  68. package/dist/run-ticket-number.js +40 -0
  69. package/dist/session-assistant-usage.js +107 -0
  70. package/dist/shape-unreadable-failure.js +34 -0
  71. package/dist/submission-errors.js +3 -0
  72. package/dist/submission-ledger.js +388 -0
  73. package/dist/ticket-provenance-contracts.js +115 -0
  74. package/dist/ticket-provenance.js +340 -0
  75. package/extensions/role-runtime.ts +8 -0
  76. package/package.json +1 -1
  77. package/resources/diarist-collect.md +6 -10
  78. package/scripts/build-package.mjs +2 -1
  79. package/souls/diarist.md +9 -0
  80. package/souls/doctor-auditor.md +1 -0
  81. package/souls/evidence-child.md +1 -0
  82. package/src/acp-host/production-host.ts +4 -0
  83. package/src/acp-host/role-envelope.ts +4 -3
  84. package/src/acp-host/role-turn-host.ts +9 -1
  85. package/src/analyst-gate-cycles-read.ts +37 -2
  86. package/src/archivist-record-entry.ts +45 -1
  87. package/src/audit-escalation.ts +17 -0
  88. package/src/auditor-role.ts +22 -0
  89. package/src/auditor-soul.ts +42 -0
  90. package/src/compliance-transport.ts +177 -66
  91. package/src/diarist-contracts.ts +88 -0
  92. package/src/diarist-role.ts +60 -0
  93. package/src/diarist.ts +253 -179
  94. package/src/doctor-auditor.ts +10 -14
  95. package/src/doctor-contracts.ts +1 -0
  96. package/src/doctor-role.ts +2 -2
  97. package/src/evidence-child-role.ts +22 -0
  98. package/src/gatekeeper-pass-envelope.ts +109 -0
  99. package/src/gatekeeper-role.ts +145 -104
  100. package/src/host-contracts.ts +15 -2
  101. package/src/institutional-resolution.ts +8 -163
  102. package/src/judge-auditor.ts +10 -15
  103. package/src/judge-role.ts +8 -0
  104. package/src/navigator-attendance.ts +25 -9
  105. package/src/navigator-public-session.ts +217 -0
  106. package/src/navigator-session-contracts.ts +61 -54
  107. package/src/notary-source-run.ts +3 -1
  108. package/src/package-contracts/auditor-output.ts +82 -0
  109. package/src/package-contracts/evidence-child-output.ts +51 -0
  110. package/src/package-contracts/judge-output.ts +1 -0
  111. package/src/package-contracts/reviewer-output.ts +2 -2
  112. package/src/package-contracts/terminating-tools.ts +53 -3
  113. package/src/packaged-role-registry.ts +63 -0
  114. package/src/pi/adapter.ts +2 -1
  115. package/src/pi/in-process-session.ts +38 -12
  116. package/src/pi/role-turn-host.ts +42 -0
  117. package/src/public-cli/cli.ts +43 -6
  118. package/src/public-cli/countersign-run.ts +9 -129
  119. package/src/public-cli/diarist-run.ts +312 -0
  120. package/src/public-cli/instruction-seat-run.ts +289 -50
  121. package/src/public-cli/invocation.ts +87 -25
  122. package/src/public-cli/option-definitions.ts +79 -0
  123. package/src/public-cli/post-admission.ts +9 -3
  124. package/src/public-cli/registry.ts +7 -1
  125. package/src/public-cli/run-lifecycle.ts +53 -6
  126. package/src/public-cli/settlement.ts +170 -7
  127. package/src/public-cli/terminal.ts +4 -1
  128. package/src/public-role-summons.ts +431 -0
  129. package/src/receipt-delivery-policy.ts +10 -0
  130. package/src/reviewer-agent.ts +1 -1
  131. package/src/reviewer-child-executor.ts +114 -12
  132. package/src/reviewer-execution-ledger.ts +3 -2
  133. package/src/role-runtime.ts +275 -23
  134. package/src/session-assistant-usage.ts +119 -0
  135. package/src/session-opening-materials.ts +1 -1
  136. package/src/shape-unreadable-failure.ts +49 -0
  137. package/src/submission-errors.ts +3 -0
  138. package/src/ticket-provenance-contracts.ts +1 -0
  139. package/src/ticket-provenance.ts +0 -23
  140. package/dist/evidence-child-executor.js +0 -829
  141. package/src/diarist-llm-collector.ts +0 -324
  142. package/src/evidence-child-executor.ts +0 -1140
@@ -0,0 +1,414 @@
1
+ /**
2
+ * Single generic auto-resume loop for #416 (owner scope = single LLM call).
3
+ * Call-local retries, at most the effective autoResumeLimit times (injected once
4
+ * per call by the caller, #422 — never re-read from disk inside the loop),
5
+ * in-place (same runId/session).
6
+ * Unifies presentation: intermediate attempts use dummyIo, only final Terminal is presented.
7
+ *
8
+ * Owner 2026-08-23: a dispatch that exits by throwing used to bypass the entire
9
+ * retry mechanism (the throw escaped the while-loop before the count check ever
10
+ * ran). Every exception is now retained whole, in place, and the ordinary retry
11
+ * path continues: same budget, same call-local count semantics. No failure-type
12
+ * classification — every thrown value is treated identically.
13
+ */
14
+ import { constants as fsConstants } from "node:fs";
15
+ import { randomUUID } from "node:crypto";
16
+ import { lstat, mkdir, open } from "node:fs/promises";
17
+ import { join } from "node:path";
18
+ import { AUTO_RESUME_LIMIT, describeErrorIdentity, acquireRunWriterLease, markRunTerminal, RunWriterLeaseHeldError, } from "./run-lifecycle.js";
19
+ import { parseAutoResumeLimit } from "./config.js";
20
+ import { isLawfulTypedTerminalOutcome, formatTerminalResult } from "./terminal.js";
21
+ import { presentFailureTerminal, presentStructuralRejection, } from "./settlement.js";
22
+ const dummyIo = { stdout: () => { }, stderr: () => { } };
23
+ function presentTerminal(terminal, io) {
24
+ if (terminal.roleOutcome.kind === "failure" || terminal.roleOutcome.kind === "no_receipt") {
25
+ presentFailureTerminal(terminal, io);
26
+ }
27
+ else {
28
+ io.stdout(formatTerminalResult(terminal));
29
+ }
30
+ }
31
+ /**
32
+ * Best-effort finalization of the durable run state before an exception-path
33
+ * synthetic failure terminal is returned (#426 review: analyst-ledger classifies
34
+ * running runs as live — an exhausted invocation must not remain live
35
+ * indefinitely). Finalization failure must not mask the real cause.
36
+ */
37
+ async function finalizeExceptionRunBestEffort(runDirectory, io) {
38
+ try {
39
+ await markRunTerminal(runDirectory);
40
+ }
41
+ catch (error) {
42
+ io.stderr(`run terminal-state finalization failed (best-effort continue): ${describeErrorIdentity(error)}\n`);
43
+ }
44
+ }
45
+ /** Session custom-entry type carrying the pointer to one dispatch error file. */
46
+ export const DISPATCH_ERROR_RETENTION_ENTRY_TYPE = "ak_run_dispatch_error_retention";
47
+ /** Artifacts subdirectory of a run directory (established run-artifacts location). */
48
+ function runArtifactsDirectory(runDirectory) {
49
+ return join(runDirectory, "artifacts");
50
+ }
51
+ /**
52
+ * #182-A hardened path identity, mirrored from settlement.ts's
53
+ * ensureAuditEvidenceDirectory: a planted symlink at the run directory or the
54
+ * artifacts path must not receive the dispatch error dump (O_NOFOLLOW only
55
+ * protects the final file name; recursive mkdir would accept a symlinked
56
+ * parent). Fails loudly with the true cause instead.
57
+ */
58
+ async function ensureRealArtifactsDirectory(runDirectory) {
59
+ const runStat = await lstat(runDirectory);
60
+ if (runStat.isSymbolicLink() || !runStat.isDirectory()) {
61
+ throw new Error("dispatch error retention: run directory is not a real directory");
62
+ }
63
+ const artifactsDir = runArtifactsDirectory(runDirectory);
64
+ try {
65
+ const existing = await lstat(artifactsDir);
66
+ if (existing.isSymbolicLink() || !existing.isDirectory()) {
67
+ throw new Error("dispatch error retention: artifacts path is not a real directory");
68
+ }
69
+ }
70
+ catch (error) {
71
+ if (!isMissingPathError(error))
72
+ throw error;
73
+ await mkdir(artifactsDir, { recursive: true });
74
+ const created = await lstat(artifactsDir);
75
+ if (created.isSymbolicLink() || !created.isDirectory()) {
76
+ throw new Error("dispatch error retention: artifacts directory is not a real directory");
77
+ }
78
+ }
79
+ return artifactsDir;
80
+ }
81
+ /**
82
+ * Whole-object transfer of a thrown value (owner 2026-08-23: 「记录所有错误信息。
83
+ * 不能丢详细情况」). Every own property of the Error object — enumerable or not,
84
+ * which is how message/stack and any attached identity land verbatim — plus the
85
+ * constructor name and the full cause chain. No field list is prescribed or
86
+ * filtered: whatever the exception object carries goes into the file as-is.
87
+ */
88
+ function serializeThrownValue(value, depth = 0, seen = new WeakSet()) {
89
+ if (value instanceof Error) {
90
+ if (seen.has(value))
91
+ return "[circular]";
92
+ seen.add(value);
93
+ const transferred = {};
94
+ for (const key of Object.getOwnPropertyNames(value)) {
95
+ transferred[key] = transferNestedValue(value[key], depth + 1, seen);
96
+ }
97
+ return {
98
+ errorKind: "Error",
99
+ constructorName: value.constructor?.name,
100
+ ...transferred,
101
+ ...(value.cause === undefined
102
+ ? {}
103
+ : {
104
+ causeChain: depth >= 10
105
+ ? "[cause-chain-depth-limit]"
106
+ : serializeThrownValue(value.cause, depth + 1, seen),
107
+ }),
108
+ };
109
+ }
110
+ return value;
111
+ }
112
+ /** ENOENT identity shared with settlement.ts's hardened audit-artifact path. */
113
+ function isMissingPathError(error) {
114
+ return (error instanceof Error &&
115
+ "code" in error &&
116
+ error.code === "ENOENT");
117
+ }
118
+ /**
119
+ * Recursive Error-property transfer (#426 review: nested Errors must not be
120
+ * passed raw to JSON.stringify — their non-enumerable message/stack would
121
+ * serialize as {}). Depth-limited; cycle-safe via the seen set so the recursive
122
+ * construction itself cannot diverge before stringify runs.
123
+ */
124
+ function transferNestedValue(value, depth, seen) {
125
+ if (value instanceof Error)
126
+ return serializeThrownValue(value, depth, seen);
127
+ if (depth >= 10)
128
+ return "[nested-depth-limit]";
129
+ if (Array.isArray(value)) {
130
+ return value.map((item) => transferNestedValue(item, depth + 1, seen));
131
+ }
132
+ if (value !== null && typeof value === "object") {
133
+ if (seen.has(value))
134
+ return "[circular]";
135
+ seen.add(value);
136
+ const transferred = {};
137
+ for (const key of Object.getOwnPropertyNames(value)) {
138
+ transferred[key] = transferNestedValue(value[key], depth + 1, seen);
139
+ }
140
+ return transferred;
141
+ }
142
+ return value;
143
+ }
144
+ /** Cycle- and bigint-safe JSON replacer so serialization itself cannot drop data. */
145
+ function jsonSafeReplacer() {
146
+ const seen = new WeakSet();
147
+ return (_key, value) => {
148
+ if (typeof value === "bigint")
149
+ return `${value}n`;
150
+ if (typeof value === "object" && value !== null) {
151
+ if (seen.has(value))
152
+ return "[circular]";
153
+ seen.add(value);
154
+ }
155
+ return value;
156
+ };
157
+ }
158
+ /**
159
+ * Retain one throwing dispatch attempt's complete exception as an independent
160
+ * per-attempt file under the run's artifacts directory, then leave an
161
+ * addressable pointer in the session principal (custom entry). Exclusive-create
162
+ * open (O_EXCL) with a per-attempt unique name enforces 史必追加 (#419): a later
163
+ * attempt can never overwrite an earlier attempt's file.
164
+ */
165
+ async function retainDispatchError(admitted, principalAuthority, sessionAppender, attempt, error) {
166
+ const artifactsDir = await ensureRealArtifactsDirectory(admitted.runDirectory);
167
+ const filePath = join(artifactsDir, `dispatch-error-attempt-${attempt}-${randomUUID()}.json`);
168
+ // Whole-object dump: everything the thrown value carries, nothing picked.
169
+ const payload = `${JSON.stringify({
170
+ version: 1,
171
+ attempt,
172
+ recordedAt: new Date().toISOString(),
173
+ error: serializeThrownValue(error),
174
+ }, jsonSafeReplacer(), 2)}\n`;
175
+ // O_EXCL: exclusive create — the retention history is append-only by
176
+ // construction; a colliding name fails loudly instead of overwriting.
177
+ // O_NOFOLLOW when the platform provides it keeps a planted symlink from
178
+ // being followed; on platforms without it, exclusivity still holds.
179
+ // AK artifact owner stays here; only the session JSONL pointer uses Pi codec.
180
+ const noFollowFlag = typeof fsConstants.O_NOFOLLOW === "number" ? fsConstants.O_NOFOLLOW : 0;
181
+ const handle = await open(filePath, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | noFollowFlag, 0o600);
182
+ try {
183
+ await handle.writeFile(payload, "utf8");
184
+ }
185
+ finally {
186
+ await handle.close();
187
+ }
188
+ // Addressable pointer in the dossier (卷宗): Pi session custom-entry codec
189
+ // (appendPiSessionCustomEntry). Lease still owned here with run-writer.
190
+ let pointerLease;
191
+ try {
192
+ pointerLease = await acquireRunWriterLease(admitted.runDirectory);
193
+ }
194
+ catch (error) {
195
+ if (error instanceof RunWriterLeaseHeldError)
196
+ return { file: filePath };
197
+ throw error;
198
+ }
199
+ // Pointer-stage failure (#426 fix_now #5) is separated from the file write:
200
+ // once the error file is durably on disk, a failed session append must not
201
+ // reject through here and orphan it — the file path is still handed back.
202
+ let pointerError;
203
+ try {
204
+ const timestamp = new Date().toISOString();
205
+ await sessionAppender(principalAuthority, admitted.principal, DISPATCH_ERROR_RETENTION_ENTRY_TYPE, { version: 1, attempt, file: filePath, recordedAt: timestamp });
206
+ }
207
+ catch (error) {
208
+ pointerError = error;
209
+ }
210
+ finally {
211
+ await pointerLease.release();
212
+ }
213
+ return pointerError === undefined ? { file: filePath } : { file: filePath, pointerError };
214
+ }
215
+ /**
216
+ * Typed failure terminal for a retry path that ended with only exceptions:
217
+ * loud, non-lawful, carrying the last true cause and the pointers to the
218
+ * full per-attempt error files. Never rethrows the raw exception at callers.
219
+ */
220
+ function dispatchExceptionFailureTerminal(input) {
221
+ // #426 review: this terminal fires whenever the FINAL dispatch throws, not
222
+ // only when every attempt threw — do not misrepresent a mixed retry history.
223
+ const history = input.everyAttemptThrew
224
+ ? "dispatch threw an exception on every attempt"
225
+ : "the final dispatch threw an exception";
226
+ const diagnostic = `${history} (${input.endReason}; resumes used ${input.autoResumeAttempts}); last cause: ${describeErrorIdentity(input.causeError)}`;
227
+ const decisiveFacts = {
228
+ cause: "unrecognized",
229
+ diagnostic,
230
+ resumesUsed: input.autoResumeAttempts,
231
+ dispatchErrorFiles: [...input.errorFiles],
232
+ };
233
+ if (input.errorFiles.length > 0) {
234
+ decisiveFacts.lastDispatchErrorFile = input.errorFiles[input.errorFiles.length - 1];
235
+ }
236
+ const candidate = input.causeError;
237
+ if (typeof candidate?.name === "string")
238
+ decisiveFacts.errorName = candidate.name;
239
+ if (typeof candidate?.code === "string" || typeof candidate?.code === "number") {
240
+ decisiveFacts.errorCode = candidate.code;
241
+ }
242
+ const artifacts = input.errorFiles.map((path) => ({
243
+ kind: "error",
244
+ path,
245
+ }));
246
+ return {
247
+ roleOutcome: {
248
+ kind: "failure",
249
+ role: input.role,
250
+ cause: "unrecognized",
251
+ diagnostic,
252
+ decisiveFacts,
253
+ },
254
+ navigator: { disposition: "no-advice" },
255
+ artifacts,
256
+ runId: input.runId,
257
+ autoResumeCount: input.autoResumeAttempts,
258
+ };
259
+ }
260
+ export async function runWithAutoResumeLoop(options) {
261
+ // #422 single-point resolution + domain validation. NaN would bypass every
262
+ // `attempts >= limit` comparison (always false) — reject here, before any dispatch.
263
+ const limit = options.autoResumeLimit ?? AUTO_RESUME_LIMIT;
264
+ parseAutoResumeLimit(limit);
265
+ let autoResumeAttempts = 0;
266
+ let isFirst = true;
267
+ let currentPayload = options.buildInitialPayload();
268
+ let dispatchOrdinal = 0;
269
+ let lastThrownError;
270
+ let everyAttemptThrew = true;
271
+ const retainedErrorFiles = [];
272
+ while (true) {
273
+ let lease;
274
+ try {
275
+ lease = await acquireRunWriterLease(options.admitted.runDirectory, (diagnostic) => options.io.stderr(diagnostic));
276
+ }
277
+ catch (error) {
278
+ if (error instanceof RunWriterLeaseHeldError) {
279
+ presentStructuralRejection(error, options.io);
280
+ return { exitCode: 2 };
281
+ }
282
+ throw error;
283
+ }
284
+ let result;
285
+ try {
286
+ result = await options.dispatch(currentPayload, lease, isFirst, dummyIo);
287
+ }
288
+ catch (error) {
289
+ // Owner 2026-08-23: 「出了异常,就原地记录错误信息,然后重试。」
290
+ // Retain the whole exception in place (per-attempt full file + dossier
291
+ // pointer); recording failure must not break the retry path (PR #418
292
+ // diagnostic-sink-isolation precedent). The dispatcher owns lease release
293
+ // in its own finally, so the retry round starts with the lock free.
294
+ lastThrownError = error;
295
+ const attempt = dispatchOrdinal;
296
+ try {
297
+ // Track the file as soon as it is durably written (#426 review):
298
+ // a pointer-stage failure comes back separately (pointerError) and must
299
+ // never orphan the retained file (#426 fix_now #5).
300
+ const { file, pointerError } = await retainDispatchError(options.admitted, options.principalAuthority, options.sessionAppender, attempt, error);
301
+ retainedErrorFiles.push(file);
302
+ options.io.stderr(`dispatch attempt ${attempt} threw (${describeErrorIdentity(error)}); full error retained at ${file}\n`);
303
+ if (pointerError !== undefined) {
304
+ options.io.stderr(`dispatch error retention failed (best-effort continue): ${describeErrorIdentity(pointerError)}\n`);
305
+ }
306
+ }
307
+ catch (retentionError) {
308
+ options.io.stderr(`dispatch error retention failed (best-effort continue): ${describeErrorIdentity(retentionError)}\n`);
309
+ }
310
+ }
311
+ dispatchOrdinal += 1;
312
+ if (result !== undefined) {
313
+ everyAttemptThrew = false;
314
+ const terminal = result.terminal;
315
+ if (terminal !== undefined) {
316
+ terminal.autoResumeCount = autoResumeAttempts;
317
+ }
318
+ const lawful = terminal !== undefined && isLawfulTypedTerminalOutcome(terminal.roleOutcome);
319
+ if (lawful) {
320
+ if (terminal !== undefined) {
321
+ // Present lawful terminal once to real io (dummy was used inside dispatch)
322
+ options.io.stdout(formatTerminalResult(terminal));
323
+ }
324
+ return result;
325
+ }
326
+ }
327
+ // Settlement-owned sealed disposition before any redispatch (#648 / #672):
328
+ // shared for non-lawful return and direct throw. Only entry presentation
329
+ // differs (present returned terminal vs throw-path synthetic).
330
+ if (options.sealedAcceptanceDisposition !== undefined) {
331
+ const disposition = await options.sealedAcceptanceDisposition();
332
+ if (disposition.kind === "block") {
333
+ if (disposition.reason === "sealed-accepted" && result !== undefined) {
334
+ const terminal = result.terminal;
335
+ if (terminal !== undefined)
336
+ presentTerminal(terminal, options.io);
337
+ return result;
338
+ }
339
+ const terminal = dispatchExceptionFailureTerminal({
340
+ role: options.admitted.role,
341
+ runId: options.admitted.runId,
342
+ causeError: disposition.reason === "authority-failed"
343
+ ? disposition.cause
344
+ : lastThrownError,
345
+ errorFiles: retainedErrorFiles,
346
+ autoResumeAttempts,
347
+ endReason: disposition.reason === "authority-failed"
348
+ ? "sealed-acceptance authority failed closed"
349
+ : "sealed accepted projection already present",
350
+ everyAttemptThrew,
351
+ });
352
+ await finalizeExceptionRunBestEffort(options.admitted.runDirectory, options.io);
353
+ presentTerminal(terminal, options.io);
354
+ return {
355
+ exitCode: 1,
356
+ terminal,
357
+ };
358
+ }
359
+ }
360
+ if (result !== undefined) {
361
+ const terminal = result.terminal;
362
+ if (autoResumeAttempts >= limit) {
363
+ if (terminal !== undefined)
364
+ presentTerminal(terminal, options.io);
365
+ return result;
366
+ }
367
+ if (!(await options.principalAuthority.isAvailable(options.admitted.principal))) {
368
+ if (terminal !== undefined)
369
+ presentTerminal(terminal, options.io);
370
+ return result;
371
+ }
372
+ }
373
+ else {
374
+ // Exception path: continue through the identical budget/session gates.
375
+ if (autoResumeAttempts >= limit) {
376
+ const terminal = dispatchExceptionFailureTerminal({
377
+ role: options.admitted.role,
378
+ runId: options.admitted.runId,
379
+ causeError: lastThrownError,
380
+ errorFiles: retainedErrorFiles,
381
+ autoResumeAttempts,
382
+ endReason: "auto-resume budget exhausted",
383
+ everyAttemptThrew,
384
+ });
385
+ await finalizeExceptionRunBestEffort(options.admitted.runDirectory, options.io);
386
+ presentTerminal(terminal, options.io);
387
+ return {
388
+ exitCode: 1,
389
+ terminal,
390
+ };
391
+ }
392
+ if (!(await options.principalAuthority.isAvailable(options.admitted.principal))) {
393
+ const terminal = dispatchExceptionFailureTerminal({
394
+ role: options.admitted.role,
395
+ runId: options.admitted.runId,
396
+ causeError: lastThrownError,
397
+ errorFiles: retainedErrorFiles,
398
+ autoResumeAttempts,
399
+ endReason: "session principal unavailable before further resume",
400
+ everyAttemptThrew,
401
+ });
402
+ await finalizeExceptionRunBestEffort(options.admitted.runDirectory, options.io);
403
+ presentTerminal(terminal, options.io);
404
+ return {
405
+ exitCode: 1,
406
+ terminal,
407
+ };
408
+ }
409
+ }
410
+ autoResumeAttempts++;
411
+ currentPayload = options.buildResumePayload();
412
+ isFirst = false;
413
+ }
414
+ }
@@ -0,0 +1,8 @@
1
+ /** Shared public CLI usage error (structural reject before admission). */
2
+ export class CliUsageError extends Error {
3
+ code = "AK_ROLE_USAGE";
4
+ constructor(message, options) {
5
+ super(message, options?.cause === undefined ? undefined : { cause: options.cause });
6
+ this.name = "CliUsageError";
7
+ }
8
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Public CLI re-export of the shared registry-owned command renderer.
3
+ * Single implementation lives in src/public-command-renderer.ts.
4
+ */
5
+ export { isPublicCallableRole, renderPublicAkRoleCommand, } from "../public-command-renderer.js";
@@ -0,0 +1,84 @@
1
+ import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.js";
2
+ import { CliUsageError } from "./cli-errors.js";
3
+ import { admitDoctorInvocation, buildDoctorTransportPrompt } from "./invocation.js";
4
+ import { runPostAdmissionOneShot, runPostAdmissionSeatResume, resumeTurnRequestProjectionOptions, } from "./post-admission.js";
5
+ import { loadResumableDoctorRun, markRunAdmitted, } from "./run-lifecycle.js";
6
+ import { presentStructuralRejection, trySettleDoctorTerminalResult, } from "./settlement.js";
7
+ import { isLawfulTypedTerminalOutcome, } from "./terminal.js";
8
+ import { projectRoleTurnRequest, } from "./turn-request.js";
9
+ /** Project admitted invocation onto the host-neutral turn request. */
10
+ export function buildDoctorTurnRequest(admitted, options) {
11
+ return projectRoleTurnRequest(admitted, {
12
+ activation: { role: "doctor", casePath: admitted.caseRunsPath },
13
+ }, options);
14
+ }
15
+ export async function runPublicDoctor(argv, env, io, parseDoctorArgv) {
16
+ let admitted;
17
+ try {
18
+ const parsed = parseDoctorArgv(argv);
19
+ admitted = await admitDoctorInvocation({
20
+ home: env.home,
21
+ principalAuthority: env.principalAuthority,
22
+ cwd: env.cwd,
23
+ issueNumber: parsed.issueNumber,
24
+ instruction: parsed.instruction,
25
+ attachmentPaths: parsed.attachmentPaths,
26
+ ...(parsed.project === undefined ? {} : { project: parsed.project }),
27
+ ...(parsed.runs === undefined ? {} : { runs: parsed.runs }),
28
+ ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
29
+ ...(env.model === undefined ? {} : { model: env.model }),
30
+ });
31
+ }
32
+ catch (error) {
33
+ if (error instanceof CliUsageError) {
34
+ presentStructuralRejection(error, io);
35
+ return { exitCode: 2 };
36
+ }
37
+ throw error;
38
+ }
39
+ await markRunAdmitted(admitted, env.principalAuthority);
40
+ const turnRequest = buildDoctorTurnRequest(admitted, {
41
+ packageRoot: env.packageRoot,
42
+ home: env.home,
43
+ agentDir: env.agentDir,
44
+ ...(env.model === undefined ? {} : { model: env.model }),
45
+ ...(env.engine === undefined ? {} : { engine: env.engine }),
46
+ ...(env.timeoutMs === undefined ? {} : { timeoutMs: env.timeoutMs }),
47
+ ...(env.correlationId === undefined || env.correlationId.trim() === ""
48
+ ? {}
49
+ : { correlationId: env.correlationId }),
50
+ continuation: {
51
+ kind: "initial",
52
+ prompt: buildDoctorTransportPrompt(admitted, engineSessionMaterialFromOptions({ ...(env.engine === undefined ? {} : { engine: env.engine }), packageRoot: env.packageRoot })),
53
+ },
54
+ });
55
+ return await runPostAdmissionOneShot({
56
+ admitted,
57
+ env,
58
+ io,
59
+ request: turnRequest,
60
+ adapters: doctorAdapters(),
61
+ ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
62
+ });
63
+ }
64
+ function doctorAdapters() {
65
+ return {
66
+ trySettle: (admitted, authority) => trySettleDoctorTerminalResult(admitted, authority),
67
+ shouldPresentSettled: (terminal) => isLawfulTypedTerminalOutcome(terminal.roleOutcome),
68
+ };
69
+ }
70
+ /**
71
+ * Resume a previously admitted Doctor run (#633). Issue + retained case
72
+ * identity restore from the durable admitted request; the session principal reopens.
73
+ */
74
+ export async function runPublicDoctorResume(request, env, io) {
75
+ return await runPostAdmissionSeatResume({
76
+ request,
77
+ env,
78
+ io,
79
+ load: (effective) => loadResumableDoctorRun(env.home, effective.runId, env.principalAuthority),
80
+ buildTurnRequest: (admitted, effective) => buildDoctorTurnRequest(admitted, resumeTurnRequestProjectionOptions(admitted, effective, env)),
81
+ adapters: doctorAdapters(),
82
+ ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
83
+ });
84
+ }
@@ -0,0 +1,136 @@
1
+ import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.js";
2
+ import { CliUsageError } from "./cli-errors.js";
3
+ import { applyInstructionTicketProbe, probeInstructionTicket, ticketNumberFromProbe, tryResumeSameTicketSeatRun, } from "./seat-ticket-binding.js";
4
+ import { admitInspectorInvocation, buildInspectorTransportPrompt, } from "./invocation.js";
5
+ import { prepareSummonsResumeMaterials, runPostAdmissionOneShot, runPostAdmissionSeatResume, resumeTurnRequestProjectionOptions, } from "./post-admission.js";
6
+ import { loadResumableInspectorRun, markRunAdmitted, } from "./run-lifecycle.js";
7
+ import { presentStructuralRejection, trySettleInspectorTerminalResult, } from "./settlement.js";
8
+ import { projectRoleTurnRequest, } from "./turn-request.js";
9
+ /** Project admitted invocation onto the host-neutral turn request. */
10
+ export function buildInspectorTurnRequest(admitted, options) {
11
+ return projectRoleTurnRequest(admitted, {
12
+ activation: {
13
+ role: "inspector",
14
+ },
15
+ }, options);
16
+ }
17
+ export async function runPublicInspector(argv, env, io, parseInspectorArgv) {
18
+ let parsed;
19
+ try {
20
+ parsed = parseInspectorArgv(argv);
21
+ }
22
+ catch (error) {
23
+ if (error instanceof CliUsageError) {
24
+ presentStructuralRejection(error, io);
25
+ return { exitCode: 2 };
26
+ }
27
+ throw error;
28
+ }
29
+ // #637: same ticket → resume prior inspector run with this summons' materials.
30
+ // Probe captures DiaristTicketResolutionError so admit+beforeDispatch can settle
31
+ // controlled failure (bare pre-admit throw skips terminal settlement).
32
+ // No bare catch→fresh: lookup/resume failures surface; only true absence mints new.
33
+ const projectRoot = parsed.project ?? env.cwd;
34
+ const ticketProbe = await probeInstructionTicket(parsed.instruction, projectRoot, env);
35
+ const probedTicketNumber = ticketNumberFromProbe(ticketProbe);
36
+ if (probedTicketNumber !== undefined) {
37
+ const summons = {
38
+ instruction: parsed.instruction,
39
+ instructionEmpty: parsed.instruction.trim() === "",
40
+ attachmentPaths: parsed.attachmentPaths,
41
+ };
42
+ const resumed = await tryResumeSameTicketSeatRun({
43
+ home: env.home,
44
+ projectRoot,
45
+ role: "inspector",
46
+ ticketNumber: probedTicketNumber,
47
+ freshSummons: env.freshSummons,
48
+ summons,
49
+ resume: (runId, materials) => runPublicInspectorResume({ runId, ...(materials === undefined ? {} : { summons: materials }) }, env, io),
50
+ });
51
+ if (resumed !== undefined)
52
+ return resumed;
53
+ }
54
+ let admitted;
55
+ try {
56
+ admitted = await admitInspectorInvocation({
57
+ home: env.home,
58
+ principalAuthority: env.principalAuthority,
59
+ cwd: env.cwd,
60
+ instruction: parsed.instruction,
61
+ attachmentPaths: parsed.attachmentPaths,
62
+ ...(parsed.project === undefined ? {} : { project: parsed.project }),
63
+ ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
64
+ ...(env.model === undefined ? {} : { model: env.model }),
65
+ ...(env.correlationId === undefined ? {} : { correlationId: env.correlationId }),
66
+ });
67
+ }
68
+ catch (error) {
69
+ if (error instanceof CliUsageError) {
70
+ presentStructuralRejection(error, io);
71
+ return { exitCode: 2 };
72
+ }
73
+ throw error;
74
+ }
75
+ await markRunAdmitted(admitted, env.principalAuthority);
76
+ const engineMaterial = engineSessionMaterialFromOptions({
77
+ ...(env.engine === undefined ? {} : { engine: env.engine }),
78
+ packageRoot: env.packageRoot,
79
+ });
80
+ const turnRequest = buildInspectorTurnRequest(admitted, {
81
+ packageRoot: env.packageRoot,
82
+ home: env.home,
83
+ agentDir: env.agentDir,
84
+ ...(env.model === undefined ? {} : { model: env.model }),
85
+ ...(env.engine === undefined ? {} : { engine: env.engine }),
86
+ ...(env.timeoutMs === undefined ? {} : { timeoutMs: env.timeoutMs }),
87
+ ...(env.correlationId === undefined || env.correlationId.trim() === ""
88
+ ? {}
89
+ : { correlationId: env.correlationId }),
90
+ continuation: {
91
+ kind: "initial",
92
+ prompt: buildInspectorTransportPrompt(admitted, engineMaterial),
93
+ },
94
+ });
95
+ return await runPostAdmissionOneShot({
96
+ admitted,
97
+ env,
98
+ io,
99
+ request: turnRequest,
100
+ adapters: inspectorAdapters({
101
+ beforeDispatch: async (admittedSeat) => {
102
+ // #635/#637: apply pre-admit probe inside controlled-failure boundary.
103
+ await applyInstructionTicketProbe(admittedSeat, ticketProbe);
104
+ },
105
+ }),
106
+ ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
107
+ });
108
+ }
109
+ function inspectorAdapters(options) {
110
+ return {
111
+ trySettle: (admitted, authority, scope) => trySettleInspectorTerminalResult(admitted, authority, scope),
112
+ shouldPresentSettled: () => true,
113
+ ...(options?.beforeDispatch === undefined
114
+ ? {}
115
+ : { beforeDispatch: options.beforeDispatch }),
116
+ };
117
+ }
118
+ /**
119
+ * Resume a previously admitted Inspector run (#633 / #637); the session principal reopens.
120
+ * Same-ticket summons deliver this turn's instruction + frozen attachments; manual
121
+ * resume keeps package-envelope / caller-message semantics and birth attachments.
122
+ */
123
+ export async function runPublicInspectorResume(request, env, io) {
124
+ return await runPostAdmissionSeatResume({
125
+ request,
126
+ env,
127
+ io,
128
+ load: (effective) => loadResumableInspectorRun(env.home, effective.runId, env.principalAuthority),
129
+ buildTurnRequest: async (admitted, effective) => {
130
+ const summonsPrepared = await prepareSummonsResumeMaterials(admitted.runDirectory, effective.summons);
131
+ return buildInspectorTurnRequest(admitted, resumeTurnRequestProjectionOptions(admitted, effective, env, summonsPrepared));
132
+ },
133
+ adapters: inspectorAdapters(),
134
+ ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
135
+ });
136
+ }