@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
@@ -146,6 +146,18 @@ function buildActivationFlagArgs(activation: RoleTurnActivation): string[] {
146
146
  return ["--ak-role", "gatekeeper"];
147
147
  case "navigator":
148
148
  return ["--ak-role", "navigator"];
149
+ case "auditor":
150
+ return ["--ak-role", "auditor"];
151
+ case "evidence-child":
152
+ return ["--ak-role", "evidence-child"];
153
+ case "diarist":
154
+ return [
155
+ "--ak-role",
156
+ "diarist",
157
+ ...(activation.sourcesPath === undefined
158
+ ? []
159
+ : ["--ak-diarist-sources", activation.sourcesPath]),
160
+ ];
149
161
  default: {
150
162
  const _exhaustive: never = activation;
151
163
  return _exhaustive;
@@ -205,6 +217,8 @@ export type PiSpawnRunner = (
205
217
  cwd: string;
206
218
  env: NodeJS.ProcessEnv;
207
219
  timeoutMs?: number;
220
+ /** Parent cancellation; the child gets the same graceful SIGTERM as a budget. */
221
+ signal?: AbortSignal;
208
222
  },
209
223
  ) => Promise<{
210
224
  code: number | null;
@@ -338,9 +352,17 @@ export function createDefaultPiSpawnRunner(options: {
338
352
  child.kill("SIGTERM");
339
353
  }, spawnOptions.timeoutMs);
340
354
  };
355
+ // Parent cancellation reaches the nested activation: same graceful SIGTERM,
356
+ // same single close settlement. SIGKILL stays forbidden (#675 / ADR 0010).
357
+ const parentSignal = spawnOptions.signal;
358
+ const terminateForParentAbort = (): void => {
359
+ child.kill("SIGTERM");
360
+ };
361
+ parentSignal?.addEventListener("abort", terminateForParentAbort, { once: true });
341
362
  let identityRecorded: Promise<void> = Promise.resolve();
342
363
  child.once("spawn", () => {
343
364
  hasSpawned = true;
365
+ if (parentSignal?.aborted === true) terminateForParentAbort();
344
366
  armTimeoutAfterChildReady();
345
367
  const runDirectory = spawnOptions.env.AK_ROLE_RUN_DIR;
346
368
  if (
@@ -363,11 +385,13 @@ export function createDefaultPiSpawnRunner(options: {
363
385
  return;
364
386
  }
365
387
  if (timer !== undefined) clearTimeout(timer);
388
+ parentSignal?.removeEventListener("abort", terminateForParentAbort);
366
389
  settled = true;
367
390
  reject(error);
368
391
  });
369
392
  child.on("close", (code) => {
370
393
  if (timer !== undefined) clearTimeout(timer);
394
+ parentSignal?.removeEventListener("abort", terminateForParentAbort);
371
395
  void identityRecorded.then(
372
396
  () => {
373
397
  if (settled) return;
@@ -439,10 +463,27 @@ export function createPiRoleTurnHost(config: PiRoleTurnHostConfig): RoleTurnHost
439
463
  HOME: request.home,
440
464
  PI_CODING_AGENT_DIR: request.agentDir,
441
465
  AK_ROLE_RUN_DIR: request.runDirectory,
466
+ // Nested public summons resolve package root without import.meta under jiti (#675).
467
+ // Child-process scoped only — not written back onto the parent process.env.
468
+ AK_ROLE_PACKAGE_ROOT: config.packageRoot,
442
469
  };
443
470
  if (request.courtAttemptId === undefined) delete env.AK_ROLE_COURT_ATTEMPT;
444
471
  else env.AK_ROLE_COURT_ATTEMPT = request.courtAttemptId;
445
472
  applyEngineChildEnv(env, request.engine);
473
+ // Nested auditor dossier tool binds the parent run pointer when published.
474
+ if (
475
+ typeof process.env.AK_ROLE_AUDITOR_SOURCE_RUN === "string"
476
+ && process.env.AK_ROLE_AUDITOR_SOURCE_RUN.trim() !== ""
477
+ ) {
478
+ env.AK_ROLE_AUDITOR_SOURCE_RUN = process.env.AK_ROLE_AUDITOR_SOURCE_RUN;
479
+ }
480
+ // Audited-subject input selects soul materials (same for nested and direct).
481
+ if (
482
+ typeof process.env.AK_ROLE_AUDITOR_SUBJECT === "string"
483
+ && process.env.AK_ROLE_AUDITOR_SUBJECT.trim() !== ""
484
+ ) {
485
+ env.AK_ROLE_AUDITOR_SUBJECT = process.env.AK_ROLE_AUDITOR_SUBJECT;
486
+ }
446
487
  if (request.correlationId !== undefined && request.correlationId.trim() !== "") {
447
488
  env.AK_CORRELATION_ID = request.correlationId;
448
489
  }
@@ -460,6 +501,7 @@ export function createPiRoleTurnHost(config: PiRoleTurnHostConfig): RoleTurnHost
460
501
  cwd: request.cwd,
461
502
  env,
462
503
  ...(timeoutMs === undefined ? {} : { timeoutMs }),
504
+ ...(request.signal === undefined ? {} : { signal: request.signal }),
463
505
  });
464
506
  },
465
507
  };
@@ -51,6 +51,7 @@ import {
51
51
  parseCoderArgv,
52
52
  parseCollectorArgv,
53
53
  parseCountersignArgv,
54
+ parseDiaristArgv,
54
55
  parseGleanerLeftArgv,
55
56
  parseDoctorArgv,
56
57
  parseFixerArgv,
@@ -59,6 +60,8 @@ import {
59
60
  parseInspectorArgv,
60
61
  parseMergerArgv,
61
62
  parseNavigatorArgv,
63
+ parseAuditorArgv,
64
+ parseEvidenceChildArgv,
62
65
  parseNotaryArgv,
63
66
  parseReviewerArgv,
64
67
  recordLaunchedPiIdentity,
@@ -78,6 +81,7 @@ import { runPublicCoder, runPublicCoderResume } from "./coder-run.ts";
78
81
  import { runPublicInstructionSeat, runPublicInstructionSeatResume } from "./instruction-seat-run.ts";
79
82
  import { runPublicCollector, runPublicCollectorResume } from "./collector-run.ts";
80
83
  import { runPublicCountersign, runPublicCountersignResume } from "./countersign-run.ts";
84
+ import { runPublicDiarist, runPublicDiaristResume } from "./diarist-run.ts";
81
85
  import { runPublicGleanerLeft, runPublicGleanerLeftResume } from "./gleaner-left-run.ts";
82
86
  import { runPublicDoctor, runPublicDoctorResume } from "./doctor-run.ts";
83
87
  import { runPublicFixer, runPublicFixerResume } from "./fixer-run.ts";
@@ -126,6 +130,9 @@ const RESUME_SEAT_DISPATCH: Record<
126
130
  inspector: { seat: "inspector", run: runPublicInspectorResume },
127
131
  gatekeeper: { seat: "gatekeeper", run: runPublicInstructionSeatResume },
128
132
  navigator: { seat: "navigator", run: runPublicInstructionSeatResume },
133
+ auditor: { seat: "auditor", run: runPublicInstructionSeatResume },
134
+ "evidence-child": { seat: "evidence-child", run: runPublicInstructionSeatResume },
135
+ diarist: { seat: "diarist", run: runPublicDiaristResume },
129
136
  };
130
137
  import {
131
138
  INTERNAL_ROLE_ENTRYPOINT_RELATIVE,
@@ -168,6 +175,9 @@ export const PUBLIC_ROLE_ARGV = {
168
175
  reviewer: { parse: parseReviewerArgv, options: optionsForOwner("reviewer") },
169
176
  gatekeeper: { parse: parseGatekeeperArgv, options: optionsForOwner("gatekeeper") },
170
177
  navigator: { parse: parseNavigatorArgv, options: optionsForOwner("navigator") },
178
+ auditor: { parse: parseAuditorArgv, options: optionsForOwner("auditor") },
179
+ "evidence-child": { parse: parseEvidenceChildArgv, options: optionsForOwner("evidence-child") },
180
+ diarist: { parse: parseDiaristArgv, options: optionsForOwner("diarist") },
171
181
  /** Deterministic analysis seat (#336) — argv parse only; no LLM admission. */
172
182
  analyst: { parse: parseAnalystArgv, options: optionsForOwner("analyst") },
173
183
  } as const;
@@ -1311,6 +1321,31 @@ export async function runAkRole(
1311
1321
  };
1312
1322
  }
1313
1323
 
1324
+ // Diarist public run path (#708): 起居郎 is summoned like any other seat.
1325
+ if (parsed.command === "diarist") {
1326
+ const agentDir = resolveAgentDir(env, home);
1327
+ const cwd = env.cwd ?? process.cwd();
1328
+ const config = await loadAndValidateConfig(home, env.packageRoot);
1329
+ const credentials =
1330
+ env.credentials ?? (await loadCredentialProviders(agentDir));
1331
+ const seat = resolveEffectiveSeat(
1332
+ config,
1333
+ "diarist",
1334
+ credentials,
1335
+ invocationFromParsed(parsed),
1336
+ );
1337
+ const result = await runPublicDiarist(
1338
+ parsed.args,
1339
+ createRoleEnvironment(env, { role: "diarist", home, agentDir, cwd, credentials, seat, config }),
1340
+ io,
1341
+ PUBLIC_ROLE_ARGV.diarist.parse,
1342
+ );
1343
+ return {
1344
+ exitCode: result.exitCode,
1345
+ ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
1346
+ };
1347
+ }
1348
+
1314
1349
  // Coder public run path with package-owned TDD method (#109).
1315
1350
  if (parsed.command === "coder") {
1316
1351
  const agentDir = resolveAgentDir(env, home);
@@ -1510,9 +1545,13 @@ export async function runAkRole(
1510
1545
  };
1511
1546
  }
1512
1547
 
1513
- // Gatekeeper/Navigator direct public run paths (#639) — instruction seats,
1514
- // a role like any other; one parameterized branch for both.
1515
- if (parsed.command === "gatekeeper" || parsed.command === "navigator") {
1548
+ // Instruction seats (#639 / #675): gatekeeper / navigator / auditor / evidence-child.
1549
+ if (
1550
+ parsed.command === "gatekeeper"
1551
+ || parsed.command === "navigator"
1552
+ || parsed.command === "auditor"
1553
+ || parsed.command === "evidence-child"
1554
+ ) {
1516
1555
  const agentDir = resolveAgentDir(env, home);
1517
1556
  const cwd = env.cwd ?? process.cwd();
1518
1557
  const config = await loadAndValidateConfig(home, env.packageRoot);
@@ -1529,9 +1568,7 @@ export async function runAkRole(
1529
1568
  createRoleEnvironment(env, { role: parsed.command, home, agentDir, cwd, credentials, seat, config }),
1530
1569
  io,
1531
1570
  parsed.command,
1532
- parsed.command === "gatekeeper"
1533
- ? PUBLIC_ROLE_ARGV.gatekeeper.parse
1534
- : PUBLIC_ROLE_ARGV.navigator.parse,
1571
+ PUBLIC_ROLE_ARGV[parsed.command].parse,
1535
1572
  );
1536
1573
  return {
1537
1574
  exitCode: result.exitCode,
@@ -1,20 +1,11 @@
1
1
  /**
2
- * Public Countersign Role run: admit ticket materials → diarist pipeline step →
3
- * shared post-admission coordinator → settle Terminal result
4
- * (#572 / ADR 0074 / ADR 0075). #599: manual resume continues the exact session.
5
- * Diarist is a prior station on the court pipeline, not a countersign call.
6
- * Unbound admission resolves ticket via shared seat LLM bind (#635) before the diary station.
2
+ * Public Countersign Role run: admit ticket materials → shared post-admission
3
+ * coordinator → settle Terminal result (#572 / ADR 0074). #599: manual resume
4
+ * continues the exact session. Unbound admission resolves its ticket via the
5
+ * shared seat LLM bind (#635). Who runs 起居郎, and when, is the caller's
6
+ * composition (ADR 0010 / ADR 0075 `no-call-rule`) — not this seat's business.
7
7
  */
8
8
  import type { DurablePrincipalAuthority, RoleTurnRequest } from "../host-contracts.ts";
9
- import {
10
- createDiaristIssueFaceFetcher,
11
- DiaristIssueSourceError,
12
- resolveDiaristGithubOrigin,
13
- runDiarist,
14
- type DiaristIssueFace,
15
- type DiaristRunResult,
16
- } from "../diarist.ts";
17
- import { appendIssueSourceFailureDiagnostic } from "../ticket-provenance.ts";
18
9
  import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.ts";
19
10
  import { CliUsageError } from "./cli-errors.ts";
20
11
  import {
@@ -29,7 +20,6 @@ import {
29
20
  ticketNumberFromProbe,
30
21
  tryResumeSameTicketSeatRun,
31
22
  } from "./seat-ticket-binding.ts";
32
- import { tryHomeFromAkRolesPath } from "../activation-ledger-topology.ts";
33
23
  import {
34
24
  prepareSummonsResumeMaterials,
35
25
  runPostAdmissionOneShot,
@@ -194,7 +184,6 @@ export async function runPublicCountersign(
194
184
  turnRequest,
195
185
  buildCountersignTurnRequest(admitted, turnProjection),
196
186
  );
197
- await runCountersignDiaristStation(admitted, env);
198
187
  },
199
188
  }),
200
189
  ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
@@ -222,10 +211,9 @@ function countersignAdapters(options?: {
222
211
 
223
212
  /**
224
213
  * Resume a previously admitted Countersign run (#599 / DK-3 / #637).
225
- * Restores role/ticket/session identity. Every court re-entry runs the diarist
226
- * station first (ADR 0075 refresh-every-court). Same-ticket summons deliver this
227
- * turn's instruction + frozen attachments on the resume prompt; manual resume
228
- * keeps package-envelope / caller-message semantics and birth attachments.
214
+ * Restores role/ticket/session identity. Same-ticket summons deliver this turn's
215
+ * instruction + frozen attachments on the resume prompt; manual resume keeps
216
+ * package-envelope / caller-message semantics and birth attachments.
229
217
  */
230
218
  export async function runPublicCountersignResume(
231
219
  request: PublicResumeRequest,
@@ -261,115 +249,7 @@ export async function runPublicCountersignResume(
261
249
  ),
262
250
  );
263
251
  },
264
- adapters: countersignAdapters({
265
- beforeDispatch: async (admitted) => {
266
- await runCountersignDiaristStation(admitted, env);
267
- },
268
- }),
252
+ adapters: countersignAdapters(),
269
253
  ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
270
254
  });
271
255
  }
272
-
273
- /**
274
- * Court-pipeline prior station: refresh ticket-provenance before countersign turn.
275
- * Caller-invisible — collector failures append durable volume diagnostics and the
276
- * station continues; issue-source / ADR source-read / watermark honesty failures
277
- * leave typed durable diagnostics and propagate (失败诚实).
278
- * Missing ticketNumber (true-unbound after pre-court resolution) skips the station
279
- * — no diary is minted for a true-unbound run.
280
- *
281
- * Issue body/comments come from the shared GitHub seam only. Attachments stay
282
- * attachments — never merged and mislabeled as issue-body-comment.
283
- * Bound ticket never silently degrades to “no issue face”.
284
- */
285
- export async function runCountersignDiaristStation(
286
- admitted: AdmittedCountersignInvocation,
287
- env: Pick<CountersignRunEnv, "cwd" | "packageRoot">,
288
- ): Promise<DiaristRunResult | undefined> {
289
- if (admitted.ticketNumber === undefined) return undefined;
290
-
291
- const issueFace = await loadBoundIssueFace(admitted);
292
- const home = tryHomeFromAkRolesPath(admitted.runDirectory);
293
-
294
- const result = await runDiarist({
295
- ticketNumber: admitted.ticketNumber,
296
- cwd: admitted.projectRoot,
297
- ...(home === undefined ? {} : { home }),
298
- issueFace,
299
- sessionCwds: [admitted.projectRoot, env.cwd],
300
- ...(env.packageRoot === undefined ? {} : { packageRoot: env.packageRoot }),
301
- });
302
- return result;
303
- }
304
-
305
- /**
306
- * Acquire issue face for a bound ticket. Failures are typed + durable on the
307
- * ticket-provenance volume, then propagated — never washed into empty face.
308
- */
309
- function persistIssueSourceFailure(
310
- admitted: AdmittedCountersignInvocation,
311
- ticketNumber: number,
312
- error: DiaristIssueSourceError,
313
- ): never {
314
- const home = tryHomeFromAkRolesPath(admitted.runDirectory);
315
- appendIssueSourceFailureDiagnostic({
316
- ticketNumber,
317
- cwd: admitted.projectRoot,
318
- ...(home === undefined ? {} : { home }),
319
- cause: error.message,
320
- reason: error.reason,
321
- });
322
- throw error;
323
- }
324
-
325
- async function loadBoundIssueFace(
326
- admitted: AdmittedCountersignInvocation,
327
- ): Promise<DiaristIssueFace> {
328
- const ticketNumber = admitted.ticketNumber;
329
- if (ticketNumber === undefined) {
330
- throw new Error("loadBoundIssueFace requires a bound ticketNumber");
331
- }
332
-
333
- const origin = resolveDiaristGithubOrigin(admitted.projectRoot);
334
- if (origin === undefined) {
335
- persistIssueSourceFailure(
336
- admitted,
337
- ticketNumber,
338
- new DiaristIssueSourceError(
339
- "origin-unresolved",
340
- `bound ticket #${ticketNumber} issue face requires a resolvable github.com origin remote`,
341
- ),
342
- );
343
- }
344
-
345
- const fetcher = createDiaristIssueFaceFetcher();
346
- let face: DiaristIssueFace | undefined;
347
- try {
348
- face = await fetcher({
349
- owner: origin.owner,
350
- repo: origin.repo,
351
- ticketNumber,
352
- });
353
- } catch (error) {
354
- const typed =
355
- error instanceof DiaristIssueSourceError
356
- ? error
357
- : new DiaristIssueSourceError(
358
- "issue-unavailable",
359
- `issue face fetch failed for ${origin.owner}/${origin.repo}#${ticketNumber}`,
360
- { cause: error },
361
- );
362
- persistIssueSourceFailure(admitted, ticketNumber, typed);
363
- }
364
- if (face === undefined) {
365
- persistIssueSourceFailure(
366
- admitted,
367
- ticketNumber,
368
- new DiaristIssueSourceError(
369
- "issue-unavailable",
370
- `issue face unavailable for ${origin.owner}/${origin.repo}#${ticketNumber}`,
371
- ),
372
- );
373
- }
374
- return face;
375
- }
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Public Diarist (起居郎) Role run — #708 / ADR 0075 `diarist-is-role`.
3
+ * Same admit → post-admission → settle shape as the other instruction seats.
4
+ * Semantic collection is the role's own turn; this seat only freezes the
5
+ * mechanical source catalog the turn selects from (`diarist-collector-is-own-turn`).
6
+ * Who calls it and in what order is the caller's business (ADR 0010 `no-call-rule`).
7
+ */
8
+ import { existsSync } from "node:fs";
9
+ import { writeFile } from "node:fs/promises";
10
+ import { join } from "node:path";
11
+
12
+ import type { DurablePrincipalAuthority, RoleTurnRequest } from "../host-contracts.ts";
13
+ import {
14
+ loadDiaristIssueFace,
15
+ prepareDiaristSourceCatalog,
16
+ serializeDiaristSourceCatalog,
17
+ } from "../diarist.ts";
18
+ import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.ts";
19
+ import { CliUsageError } from "./cli-errors.ts";
20
+ import {
21
+ admitDiaristInvocation,
22
+ buildInstructionTransportPrompt,
23
+ type AdmittedDiaristInvocation,
24
+ type ParseDiaristArgvResult,
25
+ } from "./invocation.ts";
26
+ import {
27
+ applyInstructionTicketProbe,
28
+ probeInstructionTicket,
29
+ ticketNumberFromProbe,
30
+ tryResumeSameTicketSeatRun,
31
+ } from "./seat-ticket-binding.ts";
32
+ import {
33
+ prepareSummonsResumeMaterials,
34
+ runPostAdmissionOneShot,
35
+ runPostAdmissionSeatResume,
36
+ resumeTurnRequestProjectionOptions,
37
+ type PostAdmissionAdapters,
38
+ type PostAdmissionEnv,
39
+ } from "./post-admission.ts";
40
+ import {
41
+ loadResumableDiaristRun,
42
+ markRunAdmitted,
43
+ readRoleRunIdentity,
44
+ type PublicResumeRequest,
45
+ type SameTicketSummonsMaterials,
46
+ } from "./run-lifecycle.ts";
47
+ import {
48
+ presentStructuralRejection,
49
+ trySettleDiaristTerminalResult,
50
+ } from "./settlement.ts";
51
+ import type { CliIo } from "./cli-io.ts";
52
+ import type { TerminalResult } from "./terminal.ts";
53
+ import {
54
+ projectRoleTurnRequest,
55
+ type RoleTurnRequestProjectionOptions,
56
+ } from "./turn-request.ts";
57
+
58
+ export type DiaristRunEnv = PostAdmissionEnv & {
59
+ principalAuthority: DurablePrincipalAuthority;
60
+ createRunId?: () => string;
61
+ };
62
+
63
+ /** Frozen catalog filename inside the run dossier (durable material, not argv). */
64
+ const DIARIST_SOURCE_CATALOG_FILE = "diarist-sources.json" as const;
65
+
66
+ /** Neutral path identifier only (ADR 0073) — catalog bytes stay on disk. */
67
+ function withDiaristCatalogPath(
68
+ prompt: string,
69
+ sourcesPath: string | undefined,
70
+ ): string {
71
+ if (sourcesPath === undefined || sourcesPath.trim() === "") return prompt;
72
+ return `${prompt}\n\n已冻结来源文件(路径):\n- ${sourcesPath}`;
73
+ }
74
+
75
+ /** Project admitted invocation onto the host-neutral turn request. */
76
+ export function buildDiaristTurnRequest(
77
+ admitted: AdmittedDiaristInvocation,
78
+ options: RoleTurnRequestProjectionOptions,
79
+ sourcesPath?: string,
80
+ ): RoleTurnRequest {
81
+ const continuation =
82
+ sourcesPath === undefined
83
+ ? options.continuation
84
+ : {
85
+ ...options.continuation,
86
+ prompt: withDiaristCatalogPath(options.continuation.prompt, sourcesPath),
87
+ };
88
+ return projectRoleTurnRequest(
89
+ admitted,
90
+ {
91
+ activation: {
92
+ role: "diarist" as const,
93
+ ...(sourcesPath === undefined ? {} : { sourcesPath }),
94
+ },
95
+ },
96
+ { ...options, continuation },
97
+ );
98
+ }
99
+
100
+ /**
101
+ * Mechanical source enumeration for a bound summons: establish the per-ticket
102
+ * volume, freeze this turn's candidate catalog into the run dossier.
103
+ * A true-unbound summons has no ticket, so no diary is minted — undefined.
104
+ *
105
+ * A run that has not settled yet (crash-resume, open-court continuation) keeps
106
+ * the catalog its candidateIndexes were minted against — re-enumerating there
107
+ * would rebind the role's indexes to bytes it never saw. Only a settled run
108
+ * re-enumerates on the next same-ticket summons (ADR 0075 增量).
109
+ */
110
+ async function freezeDiaristSourceCatalog(
111
+ admitted: AdmittedDiaristInvocation,
112
+ env: Pick<DiaristRunEnv, "cwd" | "home">,
113
+ ): Promise<string | undefined> {
114
+ if (admitted.ticketNumber === undefined) return undefined;
115
+ const path = join(admitted.runDirectory, DIARIST_SOURCE_CATALOG_FILE);
116
+ if (existsSync(path)) {
117
+ const identity = await readRoleRunIdentity(admitted.runDirectory);
118
+ if (identity !== undefined && identity.state !== "terminal") return path;
119
+ }
120
+ const issueFace = await loadDiaristIssueFace({
121
+ ticketNumber: admitted.ticketNumber,
122
+ projectRoot: admitted.projectRoot,
123
+ home: env.home,
124
+ });
125
+ const catalog = await prepareDiaristSourceCatalog({
126
+ ticketNumber: admitted.ticketNumber,
127
+ cwd: admitted.projectRoot,
128
+ home: env.home,
129
+ issueFace,
130
+ sessionCwds: [admitted.projectRoot, env.cwd],
131
+ });
132
+ await writeFile(path, serializeDiaristSourceCatalog(catalog), "utf8");
133
+ return path;
134
+ }
135
+
136
+ function diaristAdapters(options?: {
137
+ beforeDispatch?: (
138
+ admitted: AdmittedDiaristInvocation,
139
+ ) => void | Promise<void>;
140
+ }): PostAdmissionAdapters<AdmittedDiaristInvocation> {
141
+ return {
142
+ trySettle: (admitted, authority, scope) =>
143
+ trySettleDiaristTerminalResult(admitted, authority, scope),
144
+ shouldPresentSettled: () => true,
145
+ ...(options?.beforeDispatch === undefined
146
+ ? {}
147
+ : { beforeDispatch: options.beforeDispatch }),
148
+ };
149
+ }
150
+
151
+ export async function runPublicDiarist(
152
+ argv: readonly string[],
153
+ env: DiaristRunEnv,
154
+ io: CliIo,
155
+ parseDiaristArgv: (args: readonly string[]) => ParseDiaristArgvResult,
156
+ ): Promise<{
157
+ exitCode: number;
158
+ admitted?: AdmittedDiaristInvocation;
159
+ terminal?: TerminalResult;
160
+ }> {
161
+ let parsed: ParseDiaristArgvResult;
162
+ try {
163
+ parsed = parseDiaristArgv(argv);
164
+ } catch (error) {
165
+ if (error instanceof CliUsageError) {
166
+ presentStructuralRejection(error, io);
167
+ return { exitCode: 2 };
168
+ }
169
+ throw error;
170
+ }
171
+
172
+ // #637: same ticket → resume this seat's prior run with this summons' materials.
173
+ // Probe captures DiaristTicketResolutionError so admit+beforeDispatch can settle
174
+ // controlled failure (bare pre-admit throw skips terminal settlement).
175
+ const projectRoot = parsed.project ?? env.cwd;
176
+ const ticketProbe = await probeInstructionTicket(
177
+ parsed.instruction,
178
+ projectRoot,
179
+ env,
180
+ );
181
+ const probedTicketNumber = ticketNumberFromProbe(ticketProbe);
182
+ if (probedTicketNumber !== undefined) {
183
+ const summons: SameTicketSummonsMaterials = {
184
+ instruction: parsed.instruction,
185
+ instructionEmpty: parsed.instruction.trim() === "",
186
+ attachmentPaths: parsed.attachmentPaths,
187
+ };
188
+ const resumed = await tryResumeSameTicketSeatRun({
189
+ home: env.home,
190
+ projectRoot,
191
+ role: "diarist",
192
+ ticketNumber: probedTicketNumber,
193
+ freshSummons: env.freshSummons,
194
+ summons,
195
+ resume: (runId, materials) =>
196
+ runPublicDiaristResume(
197
+ { runId, ...(materials === undefined ? {} : { summons: materials }) },
198
+ env,
199
+ io,
200
+ ),
201
+ });
202
+ if (resumed !== undefined) return resumed;
203
+ }
204
+
205
+ let admitted: AdmittedDiaristInvocation;
206
+ try {
207
+ admitted = await admitDiaristInvocation({
208
+ home: env.home,
209
+ principalAuthority: env.principalAuthority,
210
+ cwd: env.cwd,
211
+ instruction: parsed.instruction,
212
+ attachmentPaths: parsed.attachmentPaths,
213
+ ...(parsed.project === undefined ? {} : { project: parsed.project }),
214
+ ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
215
+ ...(env.model === undefined ? {} : { model: env.model }),
216
+ ...(env.correlationId === undefined ? {} : { correlationId: env.correlationId }),
217
+ });
218
+ } catch (error) {
219
+ if (error instanceof CliUsageError) {
220
+ presentStructuralRejection(error, io);
221
+ return { exitCode: 2 };
222
+ }
223
+ throw error;
224
+ }
225
+
226
+ await markRunAdmitted(admitted, env.principalAuthority);
227
+
228
+ const turnProjection: RoleTurnRequestProjectionOptions = {
229
+ packageRoot: env.packageRoot,
230
+ home: env.home,
231
+ agentDir: env.agentDir,
232
+ ...(env.model === undefined ? {} : { model: env.model }),
233
+ ...(env.engine === undefined ? {} : { engine: env.engine }),
234
+ ...(env.timeoutMs === undefined ? {} : { timeoutMs: env.timeoutMs }),
235
+ ...(env.correlationId === undefined || env.correlationId.trim() === ""
236
+ ? {}
237
+ : { correlationId: env.correlationId }),
238
+ continuation: {
239
+ kind: "initial",
240
+ prompt: buildInstructionTransportPrompt(
241
+ admitted,
242
+ engineSessionMaterialFromOptions({
243
+ ...(env.engine === undefined ? {} : { engine: env.engine }),
244
+ packageRoot: env.packageRoot,
245
+ }),
246
+ ),
247
+ },
248
+ };
249
+ // Mutable shell: ticket bind + catalog freeze re-project activation before executeTurn.
250
+ const turnRequest = buildDiaristTurnRequest(admitted, turnProjection);
251
+
252
+ return await runPostAdmissionOneShot({
253
+ admitted,
254
+ env,
255
+ io,
256
+ request: turnRequest,
257
+ adapters: diaristAdapters({
258
+ beforeDispatch: async (admittedSeat) => {
259
+ await applyInstructionTicketProbe(admittedSeat, ticketProbe);
260
+ const sourcesPath = await freezeDiaristSourceCatalog(admittedSeat, env);
261
+ Object.assign(
262
+ turnRequest,
263
+ buildDiaristTurnRequest(admittedSeat, turnProjection, sourcesPath),
264
+ );
265
+ },
266
+ }),
267
+ ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
268
+ });
269
+ }
270
+
271
+ /**
272
+ * Resume a previously admitted Diarist run (#708 / ADR 0079 同票传召 = resume).
273
+ * A re-entry into a settled run re-enumerates fresh sources; the offered
274
+ * watermark keeps the pass incremental so already-seen blocks are not
275
+ * re-offered. An unfinished run keeps its frozen catalog (see freeze above).
276
+ */
277
+ export async function runPublicDiaristResume(
278
+ request: PublicResumeRequest,
279
+ env: DiaristRunEnv,
280
+ io: CliIo,
281
+ ): Promise<{
282
+ exitCode: number;
283
+ admitted?: AdmittedDiaristInvocation;
284
+ terminal?: TerminalResult;
285
+ }> {
286
+ return await runPostAdmissionSeatResume({
287
+ request,
288
+ env,
289
+ io,
290
+ load: (effective) =>
291
+ loadResumableDiaristRun(env.home, effective.runId, env.principalAuthority),
292
+ buildTurnRequest: async (admitted, effective) => {
293
+ const summonsPrepared = await prepareSummonsResumeMaterials(
294
+ admitted.runDirectory,
295
+ effective.summons,
296
+ );
297
+ const sourcesPath = await freezeDiaristSourceCatalog(admitted, env);
298
+ return buildDiaristTurnRequest(
299
+ admitted,
300
+ resumeTurnRequestProjectionOptions(
301
+ admitted,
302
+ effective,
303
+ env,
304
+ summonsPrepared,
305
+ ),
306
+ sourcesPath,
307
+ );
308
+ },
309
+ adapters: diaristAdapters(),
310
+ ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
311
+ });
312
+ }