@akagilnc/pi-workflow-roles 0.1.1817 → 0.1.1837

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.
@@ -19,6 +19,7 @@ import {
19
19
  resolveActivationLedgerHome,
20
20
  } from "../activation-ledger-topology.ts";
21
21
  import { resolveBookKeyFromGit } from "../activation-ledger-git.ts";
22
+ import { roleRunSessionCoordinates } from "../sitian-role-run-coordinates.ts";
22
23
  import {
23
24
  loadDoctorCase,
24
25
  } from "../doctor-evidence.ts";
@@ -57,14 +58,6 @@ export type FrozenAttachment = {
57
58
  readonly mediaKind: "regular-file";
58
59
  };
59
60
 
60
- /** Durable Pi session file principal name under a Role run's private session directory. */
61
- export const ROLE_RUN_SESSION_FILE_NAME = "session.jsonl" as const;
62
-
63
- /** Exact Pi session file principal path for a Role run session directory. */
64
- export function roleRunSessionFile(sessionDirectory: string): string {
65
- return join(sessionDirectory, ROLE_RUN_SESSION_FILE_NAME);
66
- }
67
-
68
61
  /** Shared admitted Role run identity (#106 common Invocation + #109 Coder). */
69
62
  export type AdmittedRoleInvocationBase = {
70
63
  readonly runId: string;
@@ -583,16 +576,9 @@ export async function admitJudgeInvocation(
583
576
  requireOptionPath("--project", options.project);
584
577
  }
585
578
  const projectRoot = resolve(options.project ?? options.cwd);
586
- const bookKey = resolveBookKeyFromGit(projectRoot);
587
- const ledgerHome = resolveActivationLedgerHome(() => options.home);
588
579
  const runId = (options.createRunId ?? uuidv7)();
589
- const runDirectory = join(
590
- activationBookDirectory(ledgerHome, bookKey),
591
- "runs",
592
- `${runId}@judge`,
593
- );
594
- const sessionDirectory = join(runDirectory, "session");
595
- const sessionFile = roleRunSessionFile(sessionDirectory);
580
+ const { ledgerHome, bookKey, runDirectory, sessionDirectory, sessionFile } =
581
+ roleRunSessionCoordinates({ cwd: projectRoot, runId, role: "judge", home: options.home });
596
582
  const attachmentsDirectory = join(runDirectory, "attachments");
597
583
  ensureRealDirectoryTree(ledgerHome, sessionDirectory);
598
584
  ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
@@ -728,16 +714,9 @@ export async function admitCoderInvocation(
728
714
  }
729
715
 
730
716
  const projectRoot = resolve(options.project ?? options.cwd);
731
- const bookKey = resolveBookKeyFromGit(projectRoot);
732
- const ledgerHome = resolveActivationLedgerHome(() => options.home);
733
717
  const runId = (options.createRunId ?? uuidv7)();
734
- const runDirectory = join(
735
- activationBookDirectory(ledgerHome, bookKey),
736
- "runs",
737
- `${runId}@coder`,
738
- );
739
- const sessionDirectory = join(runDirectory, "session");
740
- const sessionFile = roleRunSessionFile(sessionDirectory);
718
+ const { ledgerHome, bookKey, runDirectory, sessionDirectory, sessionFile } =
719
+ roleRunSessionCoordinates({ cwd: projectRoot, runId, role: "coder", home: options.home });
741
720
  const attachmentsDirectory = join(runDirectory, "attachments");
742
721
  ensureRealDirectoryTree(ledgerHome, sessionDirectory);
743
722
  ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
@@ -850,16 +829,9 @@ export async function admitFixerInvocation(
850
829
  }
851
830
 
852
831
  const projectRoot = resolve(options.project ?? options.cwd);
853
- const bookKey = resolveBookKeyFromGit(projectRoot);
854
- const ledgerHome = resolveActivationLedgerHome(() => options.home);
855
832
  const runId = (options.createRunId ?? uuidv7)();
856
- const runDirectory = join(
857
- activationBookDirectory(ledgerHome, bookKey),
858
- "runs",
859
- `${runId}@fixer`,
860
- );
861
- const sessionDirectory = join(runDirectory, "session");
862
- const sessionFile = roleRunSessionFile(sessionDirectory);
833
+ const { ledgerHome, bookKey, runDirectory, sessionDirectory, sessionFile } =
834
+ roleRunSessionCoordinates({ cwd: projectRoot, runId, role: "fixer", home: options.home });
863
835
  const attachmentsDirectory = join(runDirectory, "attachments");
864
836
  ensureRealDirectoryTree(ledgerHome, sessionDirectory);
865
837
  ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
@@ -1131,16 +1103,9 @@ export async function admitCollectorInvocation(
1131
1103
  repository = resolveGitHubRemoteRepository(projectRoot);
1132
1104
  }
1133
1105
 
1134
- const bookKey = resolveBookKeyFromGit(projectRoot);
1135
- const ledgerHome = resolveActivationLedgerHome(() => options.home);
1136
1106
  const runId = (options.createRunId ?? uuidv7)();
1137
- const runDirectory = join(
1138
- activationBookDirectory(ledgerHome, bookKey),
1139
- "runs",
1140
- `${runId}@collector`,
1141
- );
1142
- const sessionDirectory = join(runDirectory, "session");
1143
- const sessionFile = roleRunSessionFile(sessionDirectory);
1107
+ const { ledgerHome, bookKey, runDirectory, sessionDirectory, sessionFile } =
1108
+ roleRunSessionCoordinates({ cwd: projectRoot, runId, role: "collector", home: options.home });
1144
1109
  const attachmentsDirectory = join(runDirectory, "attachments");
1145
1110
  ensureRealDirectoryTree(ledgerHome, sessionDirectory);
1146
1111
  ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
@@ -1449,8 +1414,9 @@ export async function admitDoctorInvocation(
1449
1414
  }
1450
1415
 
1451
1416
  const projectRoot = resolve(options.project ?? options.cwd);
1452
- const bookKey = resolveBookKeyFromGit(projectRoot);
1453
- const ledgerHome = resolveActivationLedgerHome(() => options.home);
1417
+ const runId = (options.createRunId ?? uuidv7)();
1418
+ const { ledgerHome, bookKey, runDirectory, sessionDirectory, sessionFile } =
1419
+ roleRunSessionCoordinates({ cwd: projectRoot, runId, role: "doctor", home: options.home });
1454
1420
 
1455
1421
  let caseRunsPath: string;
1456
1422
  try {
@@ -1492,14 +1458,6 @@ export async function admitDoctorInvocation(
1492
1458
  );
1493
1459
  }
1494
1460
 
1495
- const runId = (options.createRunId ?? uuidv7)();
1496
- const runDirectory = join(
1497
- activationBookDirectory(ledgerHome, bookKey),
1498
- "runs",
1499
- `${runId}@doctor`,
1500
- );
1501
- const sessionDirectory = join(runDirectory, "session");
1502
- const sessionFile = roleRunSessionFile(sessionDirectory);
1503
1461
  const attachmentsDirectory = join(runDirectory, "attachments");
1504
1462
  ensureRealDirectoryTree(ledgerHome, sessionDirectory);
1505
1463
  ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
@@ -1659,16 +1617,9 @@ export async function admitReviewerInvocation(
1659
1617
  }
1660
1618
 
1661
1619
  const projectRoot = resolve(options.project ?? options.cwd);
1662
- const bookKey = resolveBookKeyFromGit(projectRoot);
1663
- const ledgerHome = resolveActivationLedgerHome(() => options.home);
1664
1620
  const runId = (options.createRunId ?? uuidv7)();
1665
- const runDirectory = join(
1666
- activationBookDirectory(ledgerHome, bookKey),
1667
- "runs",
1668
- `${runId}@reviewer`,
1669
- );
1670
- const sessionDirectory = join(runDirectory, "session");
1671
- const sessionFile = roleRunSessionFile(sessionDirectory);
1621
+ const { ledgerHome, bookKey, runDirectory, sessionDirectory, sessionFile } =
1622
+ roleRunSessionCoordinates({ cwd: projectRoot, runId, role: "reviewer", home: options.home });
1672
1623
  const attachmentsDirectory = join(runDirectory, "attachments");
1673
1624
  ensureRealDirectoryTree(ledgerHome, sessionDirectory);
1674
1625
  ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
@@ -1887,16 +1838,9 @@ export async function admitMergerInvocation(
1887
1838
  options.gitState ?? createProductionMergerGitState(projectRoot),
1888
1839
  );
1889
1840
 
1890
- const bookKey = resolveBookKeyFromGit(projectRoot);
1891
- const ledgerHome = resolveActivationLedgerHome(() => options.home);
1892
1841
  const runId = (options.createRunId ?? uuidv7)();
1893
- const runDirectory = join(
1894
- activationBookDirectory(ledgerHome, bookKey),
1895
- "runs",
1896
- `${runId}@merger`,
1897
- );
1898
- const sessionDirectory = join(runDirectory, "session");
1899
- const sessionFile = roleRunSessionFile(sessionDirectory);
1842
+ const { ledgerHome, bookKey, runDirectory, sessionDirectory, sessionFile } =
1843
+ roleRunSessionCoordinates({ cwd: projectRoot, runId, role: "merger", home: options.home });
1900
1844
  const attachmentsDirectory = join(runDirectory, "attachments");
1901
1845
  ensureRealDirectoryTree(ledgerHome, sessionDirectory);
1902
1846
  ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
@@ -6,12 +6,8 @@
6
6
  import { mkdir, writeFile } from "node:fs/promises";
7
7
  import { join, resolve } from "node:path";
8
8
 
9
- import { resolveBookKeyFromGit } from "../activation-ledger-git.ts";
10
- import {
11
- activationBookDirectory,
12
- ensureRealDirectoryTree,
13
- resolveActivationLedgerHome,
14
- } from "../activation-ledger-topology.ts";
9
+ import { ensureRealDirectoryTree } from "../activation-ledger-topology.ts";
10
+ import { roleRunSessionCoordinates } from "../sitian-role-run-coordinates.ts";
15
11
  import {
16
12
  loadPackagedMethodSkillMaterial,
17
13
  resolvePackagedMethodSkillPath,
@@ -30,7 +26,6 @@ import {
30
26
  admitMergerInvocation,
31
27
  buildMergerTransportPrompt,
32
28
  MergerEnvelopeDerivationError,
33
- roleRunSessionFile,
34
29
  type AdmittedMergerInvocation,
35
30
  } from "./invocation.ts";
36
31
  import {
@@ -414,16 +409,9 @@ async function admitMergerShellForActivationFailure(options: {
414
409
  createRunId?: () => string;
415
410
  }): Promise<AdmittedMergerInvocation> {
416
411
  const projectRoot = resolve(options.project ?? options.cwd);
417
- const bookKey = resolveBookKeyFromGit(projectRoot);
418
- const ledgerHome = resolveActivationLedgerHome(() => options.home);
419
412
  const runId = (options.createRunId ?? uuidv7)();
420
- const runDirectory = join(
421
- activationBookDirectory(ledgerHome, bookKey),
422
- "runs",
423
- `${runId}@merger`,
424
- );
425
- const sessionDirectory = join(runDirectory, "session");
426
- const sessionFile = roleRunSessionFile(sessionDirectory);
413
+ const { ledgerHome, bookKey, runDirectory, sessionDirectory, sessionFile } =
414
+ roleRunSessionCoordinates({ cwd: projectRoot, runId, role: "merger", home: options.home });
427
415
  ensureRealDirectoryTree(ledgerHome, sessionDirectory);
428
416
  await mkdir(runDirectory, { recursive: true });
429
417
  const emptyDerived = {
@@ -15,7 +15,6 @@ import { CliUsageError } from "./cli-errors.ts";
15
15
  import type { FixerPhase } from "../package-contracts/fixer-output.ts";
16
16
  import type { FixerPrerequisite } from "../package-contracts/fixer-packet.ts";
17
17
  import {
18
- roleRunSessionFile,
19
18
  type AdmittedCoderInvocation,
20
19
  type AdmittedFixerInvocation,
21
20
  type AdmittedJudgeInvocation,
@@ -187,10 +186,12 @@ export async function writeRoleRunState(
187
186
  export async function readRoleRunState(
188
187
  runDirectory: string,
189
188
  ): Promise<RoleRunRecord | undefined> {
189
+ let raw: unknown;
190
190
  try {
191
- const raw: unknown = JSON.parse(
192
- await readFile(join(runDirectory, RUN_STATE_FILE), "utf8"),
193
- );
191
+ raw = JSON.parse(await readFile(join(runDirectory, RUN_STATE_FILE), "utf8"));
192
+ } catch {
193
+ return undefined;
194
+ }
194
195
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
195
196
  return undefined;
196
197
  }
@@ -225,12 +226,12 @@ export async function readRoleRunState(
225
226
  typeof record.runDirectory === "string" && record.runDirectory.trim() !== ""
226
227
  ? record.runDirectory
227
228
  : runDirectory;
228
- // Prefer durable principal; fall back only for in-progress records that
229
- // predate the field but still own a private session directory.
229
+ // Legacy states predate sessionFile. Their persisted session directory is
230
+ // the authority for the historical principal, even if project topology moved.
230
231
  const sessionFile =
231
232
  typeof record.sessionFile === "string" && record.sessionFile.trim() !== ""
232
233
  ? record.sessionFile
233
- : roleRunSessionFile(record.sessionDirectory);
234
+ : join(record.sessionDirectory, "session.jsonl");
234
235
  let resumable: TypedHttp429Observation | undefined;
235
236
  if (record.resumable !== undefined && record.resumable !== null) {
236
237
  if (
@@ -264,9 +265,6 @@ export async function readRoleRunState(
264
265
  ...(phase === undefined ? {} : { phase }),
265
266
  ...(resumable === undefined ? {} : { resumable }),
266
267
  };
267
- } catch {
268
- return undefined;
269
- }
270
268
  }
271
269
 
272
270
  export async function markRunAdmitted(
@@ -7,7 +7,7 @@ export const NO_RECEIPT_LIFECYCLE_ENTRY_TYPE = "ak-no-receipt-lifecycle" as cons
7
7
  /** The sole schema shared by lifecycle owners and Terminal projections. */
8
8
  export type NoReceiptLifecycleFacts = {
9
9
  terminalToolCalled: boolean;
10
- rejectedReceipts: readonly { reason: string }[];
10
+ rejectedReceipts: readonly { reason: string; diagnosticAvailable: boolean }[];
11
11
  deliveryTurns: typeof RECEIPT_DELIVERY_TURN_LIMIT;
12
12
  sessionCompletion: "settled-without-accepted-receipt";
13
13
  runPointer: string;
@@ -30,12 +30,15 @@ export function parseNoReceiptLifecycleFacts(input: unknown): NoReceiptLifecycle
30
30
  || typeof input.attemptPointer !== "string" || input.attemptPointer.trim() === ""
31
31
  || !Array.isArray(input.rejectedReceipts)
32
32
  || !input.rejectedReceipts.every((item) => isRecord(item)
33
- && typeof item.reason === "string" && item.reason.trim() !== "")) {
33
+ && typeof item.reason === "string")) {
34
34
  throw new TypeError("malformed no-receipt lifecycle facts");
35
35
  }
36
36
  return {
37
37
  terminalToolCalled: input.terminalToolCalled,
38
- rejectedReceipts: input.rejectedReceipts.map((item) => ({ reason: item.reason as string })),
38
+ rejectedReceipts: input.rejectedReceipts.map((item) => ({
39
+ reason: item.reason as string,
40
+ diagnosticAvailable: (item.reason as string).trim() !== "",
41
+ })),
39
42
  deliveryTurns: RECEIPT_DELIVERY_TURN_LIMIT,
40
43
  sessionCompletion: "settled-without-accepted-receipt",
41
44
  runPointer: input.runPointer,
@@ -45,14 +48,20 @@ export function parseNoReceiptLifecycleFacts(input: unknown): NoReceiptLifecycle
45
48
  }
46
49
 
47
50
  export function noReceiptLifecycleFacts(
48
- input: Omit<NoReceiptLifecycleFacts, "deliveryTurns" | "sessionCompletion" | "acceptedReceipt"> & { deliveryTurns: number },
51
+ input: Omit<NoReceiptLifecycleFacts, "rejectedReceipts" | "deliveryTurns" | "sessionCompletion" | "acceptedReceipt"> & {
52
+ rejectedReceipts: readonly { reason: string }[];
53
+ deliveryTurns: number;
54
+ },
49
55
  ): NoReceiptLifecycleFacts {
50
56
  if (input.deliveryTurns !== RECEIPT_DELIVERY_TURN_LIMIT) {
51
57
  throw new TypeError("no-receipt lifecycle requires an exhausted delivery budget");
52
58
  }
53
59
  return {
54
60
  terminalToolCalled: input.terminalToolCalled,
55
- rejectedReceipts: input.rejectedReceipts.map(({ reason }) => ({ reason })),
61
+ rejectedReceipts: input.rejectedReceipts.map(({ reason }) => ({
62
+ reason,
63
+ diagnosticAvailable: reason.trim() !== "",
64
+ })),
56
65
  deliveryTurns: RECEIPT_DELIVERY_TURN_LIMIT,
57
66
  sessionCompletion: "settled-without-accepted-receipt",
58
67
  runPointer: input.runPointer,
@@ -65,14 +74,17 @@ export function createReceiptDeliveryPolicy() {
65
74
  let accepted = false;
66
75
  let terminalToolCalled = false;
67
76
  let deliveryTurns = 0;
68
- const rejectedReceipts: { reason: string }[] = [];
77
+ const rejectedReceipts: { reason: string; diagnosticAvailable: boolean }[] = [];
69
78
  return {
70
- recordAccepted() { accepted = true; terminalToolCalled = true; },
79
+ recordAccepted() {
80
+ accepted = true;
81
+ terminalToolCalled = true;
82
+ },
71
83
  /** Infrastructure owns terminality and must never trigger receipt催交. */
72
84
  stopForInfrastructure() { accepted = true; },
73
85
  recordRejected(reason: string) {
74
86
  terminalToolCalled = true;
75
- rejectedReceipts.push({ reason });
87
+ rejectedReceipts.push({ reason, diagnosticAvailable: reason.trim() !== "" });
76
88
  deliveryTurns = Math.min(RECEIPT_DELIVERY_TURN_LIMIT, deliveryTurns + 1);
77
89
  },
78
90
  recordDeliveryRequest() {
@@ -163,9 +163,9 @@ export function createReviewerRoleRuntime(pi: ExtensionAPI, dependencies: Review
163
163
  try { await dependencies.shutdownAgent?.(); } catch (error) { hostActions.failInfrastructure(ledger.recordInfrastructureFailure(error), toolCtx, id); }
164
164
  return { content: [{ type: "text" as const, text: "Reviewer report accepted" }], details: candidate, terminate: true as const, ...(usage === undefined ? {} : { usage }) };
165
165
  },
166
- noReceipt: async (auditNoReceipt) => {
166
+ noReceipt: async (auditNoReceipt, usageProjection) => {
167
167
  try { await dependencies.shutdownAgent?.(); } catch (error) { hostActions.failInfrastructure(ledger.recordInfrastructureFailure(error), toolCtx, id); }
168
- return { content: [{ type: "text" as const, text: "Reviewer report accepted; compliance audit produced no receipt" }], details: { ...candidate, auditNoReceipt }, terminate: true as const };
168
+ return { content: [{ type: "text" as const, text: "Reviewer report accepted; compliance audit produced no receipt" }], details: { ...candidate, auditNoReceipt }, terminate: true as const, ...usageProjection };
169
169
  },
170
170
  revise: (violations) => {
171
171
  throw new AggregateError([], `Reviewer receipt rejected:\n${violations.join("\n")}`, { cause: Object.freeze([...violations]) });
@@ -567,8 +567,9 @@ export function createRoleRuntimeExtension(
567
567
  // whose queued next turn is consumed before settlement.
568
568
  pi.on("agent_end", (event) => {
569
569
  const lastMessage = event.messages.at(-1);
570
- if (lastMessage?.role === "assistant" && lastMessage.stopReason === "error") {
571
- // Provider failure has no tool_result event; classify it here so the
570
+ if (lastMessage?.role === "assistant"
571
+ && (lastMessage.stopReason === "error" || lastMessage.stopReason === "aborted")) {
572
+ // Provider failure/abort has no tool_result event; classify it here so the
572
573
  // receipt policy cannot turn infrastructure death into an exit-0 lifecycle.
573
574
  receiptDelivery.stopForInfrastructure();
574
575
  return;
@@ -7,6 +7,10 @@ import { dirname, resolve, join } from "node:path";
7
7
  import { SessionManager } from "@earendil-works/pi-coding-agent";
8
8
 
9
9
  import { resolveBookKeyFromGit } from "./activation-ledger-git.ts";
10
+ export {
11
+ roleRunSessionCoordinates,
12
+ type RoleRunSessionCoordinates,
13
+ } from "./sitian-role-run-coordinates.ts";
10
14
  import {
11
15
  activationBookDirectory,
12
16
  ensureRealDirectoryTree,
@@ -0,0 +1,45 @@
1
+ import { join } from "node:path";
2
+
3
+ import { resolveBookKeyFromGit } from "./activation-ledger-git.ts";
4
+ import {
5
+ activationBookDirectory,
6
+ resolveActivationLedgerHome,
7
+ } from "./activation-ledger-topology.ts";
8
+
9
+ export type RoleRunSessionCoordinates = {
10
+ readonly ledgerHome: string;
11
+ readonly bookKey: string;
12
+ readonly runDirectory: string;
13
+ readonly sessionDirectory: string;
14
+ readonly sessionFile: string;
15
+ };
16
+
17
+ /**
18
+ * Sole source of public top-level Role run session coordinates.
19
+ * Callers supply identity only; no destination can be injected.
20
+ */
21
+ export function roleRunSessionCoordinates(options: {
22
+ readonly cwd: string;
23
+ readonly runId: string;
24
+ readonly role: string;
25
+ /** Machine-home identity; not a record destination. */
26
+ readonly home?: string;
27
+ }): RoleRunSessionCoordinates {
28
+ const ledgerHome = resolveActivationLedgerHome(
29
+ options.home === undefined ? undefined : () => options.home!,
30
+ );
31
+ const bookKey = resolveBookKeyFromGit(options.cwd);
32
+ const runDirectory = join(
33
+ activationBookDirectory(ledgerHome, bookKey),
34
+ "runs",
35
+ `${options.runId}@${options.role}`,
36
+ );
37
+ const sessionDirectory = join(runDirectory, "session");
38
+ return {
39
+ ledgerHome,
40
+ bookKey,
41
+ runDirectory,
42
+ sessionDirectory,
43
+ sessionFile: join(sessionDirectory, "session.jsonl"),
44
+ };
45
+ }