@akagilnc/pi-workflow-roles 0.1.3572 → 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 (129) hide show
  1. package/dist/acp-host/production-host.js +20239 -10556
  2. package/dist/analyst-gate-cycles-read.js +387 -0
  3. package/dist/archivist-record-entry.js +22 -1
  4. package/dist/audit-escalation.js +6 -0
  5. package/dist/auditor-soul.js +74 -0
  6. package/dist/collector-config.js +86 -0
  7. package/dist/collector-evidence.js +316 -0
  8. package/dist/collector-github.js +527 -0
  9. package/dist/collector-ledger.js +853 -0
  10. package/dist/collector-tool-schemas.js +33 -0
  11. package/dist/compliance-transport.js +104 -52
  12. package/dist/diarist-mechanical.js +411 -0
  13. package/dist/diarist-ticket-resolution.js +177 -0
  14. package/dist/diarist.js +311 -0
  15. package/dist/doctor-auditor.js +25 -0
  16. package/dist/doctor-contracts.js +2 -0
  17. package/dist/doctor-evidence.js +142 -0
  18. package/dist/gatekeeper-role.js +104 -81
  19. package/dist/host-transition-prior-native.js +78 -0
  20. package/dist/institutional-resolution.js +1 -95
  21. package/dist/judge-auditor.js +26 -0
  22. package/dist/ledger-session-read.js +227 -0
  23. package/dist/merger-git-state.js +78 -0
  24. package/dist/navigator-attendance.js +15 -7
  25. package/dist/navigator-public-session.js +170 -0
  26. package/dist/navigator-session-contracts.js +36 -40
  27. package/dist/notary-source-run.js +122 -0
  28. package/dist/package-contracts/auditor-output.js +66 -0
  29. package/dist/package-contracts/evidence-child-output.js +34 -0
  30. package/dist/package-contracts/judge-output.js +2 -0
  31. package/dist/package-contracts/terminating-tools.js +25 -2
  32. package/dist/package-resources/method-skill.js +254 -0
  33. package/dist/packaged-role-registry.js +36 -0
  34. package/dist/pi/durable-principal.js +61 -0
  35. package/dist/pi/in-process-session.js +25 -6
  36. package/dist/pi/known-failure.js +52 -0
  37. package/dist/pi/role-turn-host.js +430 -0
  38. package/dist/public-cli/auto-resume.js +414 -0
  39. package/dist/public-cli/cli-errors.js +8 -0
  40. package/dist/public-cli/cli-io.js +1 -0
  41. package/dist/public-cli/command-renderer.js +5 -0
  42. package/dist/public-cli/doctor-run.js +84 -0
  43. package/dist/public-cli/inspector-run.js +136 -0
  44. package/dist/public-cli/instruction-seat-run.js +256 -0
  45. package/dist/public-cli/invocation.js +2225 -0
  46. package/dist/public-cli/judge-run.js +118 -0
  47. package/dist/public-cli/load-production-acp-host.js +40 -0
  48. package/dist/public-cli/main.js +1140 -652
  49. package/dist/public-cli/notary-run.js +161 -0
  50. package/dist/public-cli/option-definitions.js +1192 -0
  51. package/dist/public-cli/post-admission.js +667 -0
  52. package/dist/public-cli/public-run-credentials.js +49 -0
  53. package/dist/public-cli/registry.js +4 -1
  54. package/dist/public-cli/reviewer-dispatch-rejection.js +77 -0
  55. package/dist/public-cli/run-lifecycle.js +1344 -0
  56. package/dist/public-cli/seat-ticket-binding.js +113 -0
  57. package/dist/public-cli/settlement.js +3441 -0
  58. package/dist/public-cli/terminal.js +135 -0
  59. package/dist/public-cli/turn-request.js +25 -0
  60. package/dist/public-role-summons.js +301 -0
  61. package/dist/receipt-delivery-policy.js +10 -0
  62. package/dist/reviewer-child-executor.js +80 -8
  63. package/dist/reviewer-execution-ledger.js +2 -1
  64. package/dist/run-terminal-artifacts.js +195 -0
  65. package/dist/run-ticket-number.js +40 -0
  66. package/dist/session-assistant-usage.js +107 -0
  67. package/dist/shape-unreadable-failure.js +34 -0
  68. package/dist/submission-errors.js +3 -0
  69. package/dist/submission-ledger.js +388 -0
  70. package/dist/ticket-provenance-contracts.js +115 -0
  71. package/dist/ticket-provenance.js +340 -0
  72. package/extensions/role-runtime.ts +7 -0
  73. package/package.json +1 -1
  74. package/scripts/build-package.mjs +2 -1
  75. package/souls/doctor-auditor.md +1 -0
  76. package/souls/evidence-child.md +1 -0
  77. package/src/acp-host/production-host.ts +3 -0
  78. package/src/acp-host/role-envelope.ts +2 -2
  79. package/src/acp-host/role-turn-host.ts +9 -1
  80. package/src/analyst-gate-cycles-read.ts +37 -2
  81. package/src/archivist-record-entry.ts +45 -1
  82. package/src/audit-escalation.ts +17 -0
  83. package/src/auditor-role.ts +22 -0
  84. package/src/auditor-soul.ts +42 -0
  85. package/src/compliance-transport.ts +177 -66
  86. package/src/doctor-auditor.ts +10 -14
  87. package/src/doctor-contracts.ts +1 -0
  88. package/src/doctor-role.ts +2 -2
  89. package/src/evidence-child-role.ts +22 -0
  90. package/src/gatekeeper-pass-envelope.ts +109 -0
  91. package/src/gatekeeper-role.ts +145 -104
  92. package/src/host-contracts.ts +9 -1
  93. package/src/institutional-resolution.ts +8 -163
  94. package/src/judge-auditor.ts +10 -15
  95. package/src/judge-role.ts +8 -0
  96. package/src/navigator-attendance.ts +25 -9
  97. package/src/navigator-public-session.ts +217 -0
  98. package/src/navigator-session-contracts.ts +61 -54
  99. package/src/notary-source-run.ts +3 -1
  100. package/src/package-contracts/auditor-output.ts +82 -0
  101. package/src/package-contracts/evidence-child-output.ts +51 -0
  102. package/src/package-contracts/judge-output.ts +1 -0
  103. package/src/package-contracts/reviewer-output.ts +2 -2
  104. package/src/package-contracts/terminating-tools.ts +31 -1
  105. package/src/packaged-role-registry.ts +43 -0
  106. package/src/pi/adapter.ts +2 -1
  107. package/src/pi/in-process-session.ts +38 -12
  108. package/src/pi/role-turn-host.ts +34 -0
  109. package/src/public-cli/cli.ts +14 -6
  110. package/src/public-cli/instruction-seat-run.ts +289 -50
  111. package/src/public-cli/invocation.ts +66 -25
  112. package/src/public-cli/option-definitions.ts +61 -0
  113. package/src/public-cli/post-admission.ts +7 -1
  114. package/src/public-cli/registry.ts +4 -1
  115. package/src/public-cli/run-lifecycle.ts +22 -3
  116. package/src/public-cli/settlement.ts +120 -5
  117. package/src/public-cli/terminal.ts +2 -0
  118. package/src/public-role-summons.ts +431 -0
  119. package/src/receipt-delivery-policy.ts +10 -0
  120. package/src/reviewer-agent.ts +1 -1
  121. package/src/reviewer-child-executor.ts +114 -12
  122. package/src/reviewer-execution-ledger.ts +3 -2
  123. package/src/role-runtime.ts +158 -17
  124. package/src/session-assistant-usage.ts +119 -0
  125. package/src/session-opening-materials.ts +1 -1
  126. package/src/shape-unreadable-failure.ts +49 -0
  127. package/src/submission-errors.ts +3 -0
  128. package/dist/evidence-child-executor.js +0 -829
  129. package/src/evidence-child-executor.ts +0 -1140
@@ -92,6 +92,16 @@ import {
92
92
  NAVIGATOR_TOOL_SPEC,
93
93
  type NavigatorRuntimeDependencies,
94
94
  } from "./navigator-role.ts";
95
+ import {
96
+ AUDITOR_TOOL_SPEC,
97
+ type AuditorRuntimeDependencies,
98
+ } from "./auditor-role.ts";
99
+ import { AUDITOR_ACCEPTED_TEXT } from "./package-contracts/auditor-output.ts";
100
+ import {
101
+ EVIDENCE_CHILD_TOOL_SPEC,
102
+ type EvidenceChildRuntimeDependencies,
103
+ } from "./evidence-child-role.ts";
104
+ import { EVIDENCE_CHILD_ACCEPTED_TEXT } from "./package-contracts/evidence-child-output.ts";
95
105
  import { GATEKEEPER_ACCEPTED_TEXT } from "./package-contracts/gatekeeper-output.ts";
96
106
  import { NAVIGATOR_ACCEPTED_TEXT } from "./package-contracts/navigator-output.ts";
97
107
  import { decorateSettlementWithNavigation, formatNavigatorReport, NAVIGATOR_EVENT_TYPE, navigatorSubjectKey, navigatorUnavailableError, subjectPath, type NavigatorAttendance, type NavigatorAttendanceOptions, type NavigatorEvent, type NavigatorPhase, type NavigatorReport, type NavigatorSettlement, type NavigatorSubjectProvenance, type NavigatorTargetRole, type NavigatorWorkContext } from "./navigator-attendance.ts";
@@ -349,14 +359,11 @@ export {
349
359
  writeToolExecutionObservationRecord,
350
360
  } from "./tool-execution-observation.ts";
351
361
  export type { ToolExecutionObservationRecord, ToolExecutionObservationWriter } from "./tool-execution-observation.ts";
352
- export { executeAuditorChild } from "./evidence-child-executor.ts";
353
- export type { AuditorDecisionTool } from "./evidence-child-executor.ts";
354
362
  export {
355
363
  NOTARY_OUTPUT_TOOL,
356
364
  INSPECTOR_OUTPUT_TOOL,
357
365
  GatekeeperDecisionError,
358
366
  createGatekeeperOutputTool,
359
- createOfficerDecisionTool,
360
367
  runGatekeeper,
361
368
  } from "./gatekeeper-role.ts";
362
369
  export type { GatekeeperResult, GatekeeperSubject, GatekeeperNonPassResult, RunGatekeeperOptions } from "./gatekeeper-role.ts";
@@ -397,7 +404,13 @@ export { fixerPrerequisiteSchema, fixerPrerequisitesSchema, parseFixerPrerequisi
397
404
  export type { FixerInvocationInput, FixerPrerequisite } from "./package-contracts/fixer-packet.ts";
398
405
  export { AUDIT_ESCALATION_KIND, buildAuditEscalationResult, disposeComplianceDecision, isAuditEscalationResult, projectAuditEscalation } from "./audit-escalation.ts";
399
406
  export type { AuditEscalationResult, AuditEscalationToolResult, ComplianceDecisionHandlers } from "./audit-escalation.ts";
400
- export { AUDITOR_SOUL_ROLES, loadAuditorSoul } from "./auditor-soul.ts";
407
+ export {
408
+ AUDITOR_SOUL_ROLES,
409
+ AK_ROLE_AUDITOR_SUBJECT_ENV,
410
+ loadAuditorSoul,
411
+ loadAuditorSoulFromSubjectInput,
412
+ resolveAuditorSubject,
413
+ } from "./auditor-soul.ts";
401
414
  export type { AuditorSoulRole } from "./auditor-soul.ts";
402
415
  export { JUDGE_AUDIT_TOOL_NAME, SOUL_AUDIT_TOOL_NAME, createPiJudgeAuditor } from "./judge-auditor.ts";
403
416
  export { DOCTOR_AUDIT_TOOL_NAME, createPiDoctorAuditor } from "./doctor-auditor.ts";
@@ -461,6 +474,8 @@ type ActivationRuntime = {
461
474
  inspector: { activate(): Promise<void> };
462
475
  gatekeeper: { activate(): Promise<void> };
463
476
  navigator: { activate(): Promise<void> };
477
+ auditor: { activate(): Promise<void> };
478
+ evidenceChild: { activate(): Promise<void> };
464
479
  diarist: { activate(): Promise<void> };
465
480
  merger(): Promise<void>;
466
481
  };
@@ -510,6 +525,8 @@ function activationStage(role: PackagedRole, runtime: ActivationRuntime): { id:
510
525
  case "inspector": return { id: "load-and-install", run: async () => runtime.inspector.activate() };
511
526
  case "gatekeeper": return { id: "load-and-install", run: async () => runtime.gatekeeper.activate() };
512
527
  case "navigator": return { id: "load-and-install", run: async () => runtime.navigator.activate() };
528
+ case "auditor": return { id: "load-and-install", run: async () => runtime.auditor.activate() };
529
+ case "evidence-child": return { id: "load-and-install", run: async () => runtime.evidenceChild.activate() };
513
530
  case "diarist": return { id: "load-and-install", run: async () => runtime.diarist.activate() };
514
531
  case "merger": return { id: "prepare-git-and-install", run: async () => runtime.merger() };
515
532
  }
@@ -618,6 +635,8 @@ export type RoleRuntimeDependencies = {
618
635
  loadInspectorSoul?(): Promise<string>;
619
636
  loadGatekeeperSoul?(): Promise<string>;
620
637
  loadNavigatorSoul?(): Promise<string>;
638
+ loadAuditorSoul?(): Promise<string>;
639
+ loadEvidenceChildSoul?(): Promise<string>;
621
640
  loadDiaristSoul?(): Promise<string>;
622
641
  loadDoctorCase?(path: string): Promise<import("./doctor-contracts.ts").DoctorCase>;
623
642
  loadMergerSoul?(): Promise<string>;
@@ -879,6 +898,41 @@ export function createNavigatorRoleRuntime(
879
898
  );
880
899
  }
881
900
 
901
+ /** #675: public 审刑院 seat on the shared filed-officer envelope. */
902
+ export function createAuditorRoleRuntime(
903
+ roleHost: RoleHost,
904
+ dependencies: AuditorRuntimeDependencies,
905
+ ) {
906
+ const base = createFiledOfficerRuntime(
907
+ roleHost,
908
+ {
909
+ role: "auditor",
910
+ tool: AUDITOR_TOOL_SPEC,
911
+ acceptedText: AUDITOR_ACCEPTED_TEXT,
912
+ soulTag: "auditor",
913
+ },
914
+ dependencies,
915
+ );
916
+ return {
917
+ async activate() {
918
+ await base.activate();
919
+ // Same tools whether nested or direct (#675): dossier tool always registered.
920
+ // Source run: only the shared --source-run input face (never own-run fallback).
921
+ const { createAuditorDossierTool, AUDITOR_DOSSIER_TOOL_NAME } =
922
+ await import("./auditor-dossier-tool.ts");
923
+ const { AK_ROLE_AUDITOR_SOURCE_RUN_ENV } = await import("./auditor-soul.ts");
924
+ const already = roleHost.getAllTools().some((tool) => tool.name === AUDITOR_DOSSIER_TOOL_NAME);
925
+ if (already) return;
926
+ const sourceRun =
927
+ typeof process.env[AK_ROLE_AUDITOR_SOURCE_RUN_ENV] === "string"
928
+ && process.env[AK_ROLE_AUDITOR_SOURCE_RUN_ENV].trim() !== ""
929
+ ? process.env[AK_ROLE_AUDITOR_SOURCE_RUN_ENV].trim()
930
+ : undefined;
931
+ roleHost.registerTool(createAuditorDossierTool(sourceRun) as never);
932
+ },
933
+ };
934
+ }
935
+
882
936
  /**
883
937
  * #708: 起居郎 public seat on the shared filed-officer envelope.
884
938
  * Semantic collection happened in this role's own turn; the accept hook runs the
@@ -946,6 +1000,23 @@ function loadFrozenDiaristCatalog(
946
1000
  return loadDiaristSourceCatalog(raw);
947
1001
  }
948
1002
 
1003
+ /** #675: public evidence-child seat on the shared filed-officer envelope. */
1004
+ export function createEvidenceChildRoleRuntime(
1005
+ roleHost: RoleHost,
1006
+ dependencies: EvidenceChildRuntimeDependencies,
1007
+ ) {
1008
+ return createFiledOfficerRuntime(
1009
+ roleHost,
1010
+ {
1011
+ role: "evidence-child",
1012
+ tool: EVIDENCE_CHILD_TOOL_SPEC,
1013
+ acceptedText: EVIDENCE_CHILD_ACCEPTED_TEXT,
1014
+ soulTag: "evidence-child",
1015
+ },
1016
+ dependencies,
1017
+ );
1018
+ }
1019
+
949
1020
  export function createCountersignRoleRuntime(
950
1021
  roleHost: RoleHost,
951
1022
  dependencies: CountersignRuntimeDependencies,
@@ -1027,6 +1098,9 @@ export function createRoleRuntimeExtension(
1027
1098
  // terminating-tool rejections and mechanical delivery requests share two turns.
1028
1099
  let receiptDelivery = createReceiptDeliveryPolicy();
1029
1100
  let noReceiptRecorded = false;
1101
+ // Public-run fetch observation (in-process-session statusAwareFetch face).
1102
+ let priorFetch: typeof globalThis.fetch | undefined;
1103
+ let fetchWrapped = false;
1030
1104
  const settleNavigatorProjection = async (settlement: NavigatorSettlement | undefined) => {
1031
1105
  const attendance = navigatorAttendance;
1032
1106
  if (settlement === undefined || attendance === undefined) return;
@@ -1037,6 +1111,9 @@ export function createRoleRuntimeExtension(
1037
1111
  return;
1038
1112
  }
1039
1113
  const settlePromise = attendance.settle(settlement);
1114
+ // Attach catch immediately so a late rejection after grace timeout cannot
1115
+ // surface as unhandledRejection / stale-ctx after session dispose (#675).
1116
+ void settlePromise.catch(() => undefined);
1040
1117
  const raced = await raceNavigatorGrace(settlePromise, NAVIGATOR_POST_ROLE_GRACE_MS);
1041
1118
  if (raced.status !== "timeout") return;
1042
1119
  if (pendingNavigatorPresentation === undefined) {
@@ -1063,7 +1140,6 @@ export function createRoleRuntimeExtension(
1063
1140
  pendingNavigatorPresentation = { event, report };
1064
1141
  }
1065
1142
  await attendance.dispose();
1066
- void settlePromise.catch(() => undefined);
1067
1143
  })();
1068
1144
  pendingNavigatorSettlement = pending;
1069
1145
  await pending;
@@ -1290,6 +1366,11 @@ export function createRoleRuntimeExtension(
1290
1366
  roleHost.on("session_shutdown", async () => {
1291
1367
  // #351: stop OAuth keepalive first so shutdown yields zero further ticks.
1292
1368
  envelopeHost.stopKeepalive();
1369
+ if (fetchWrapped && priorFetch !== undefined) {
1370
+ globalThis.fetch = priorFetch;
1371
+ priorFetch = undefined;
1372
+ fetchWrapped = false;
1373
+ }
1293
1374
  // Flush any still-pending affirmative attendance before teardown. Accepted
1294
1375
  // grace-timeout paths normally emit on agent_settled; abort can skip that hook.
1295
1376
  const presentation = pendingNavigatorPresentation;
@@ -1458,6 +1539,18 @@ export function createRoleRuntimeExtension(
1458
1539
  return dependencies.loadNavigatorSoul();
1459
1540
  },
1460
1541
  });
1542
+ const auditor = createAuditorRoleRuntime(roleHost, {
1543
+ async loadSoul() {
1544
+ if (!dependencies.loadAuditorSoul) throw new Error("Auditor runtime dependencies are not configured");
1545
+ return dependencies.loadAuditorSoul();
1546
+ },
1547
+ });
1548
+ const evidenceChild = createEvidenceChildRoleRuntime(roleHost, {
1549
+ async loadSoul() {
1550
+ if (!dependencies.loadEvidenceChildSoul) throw new Error("Evidence-child runtime dependencies are not configured");
1551
+ return dependencies.loadEvidenceChildSoul();
1552
+ },
1553
+ });
1461
1554
  const diarist = createDiaristRoleRuntime(
1462
1555
  roleHost,
1463
1556
  {
@@ -1553,20 +1646,19 @@ export function createRoleRuntimeExtension(
1553
1646
  });
1554
1647
 
1555
1648
  // Public Role run: record typed non-success HTTP for error evidence + v1 resume.
1556
- // 2xx clears prior observation (see recordTypedProviderHttpStatus). Non-success
1557
- // write failure must surface — never silently drop authorized error evidence.
1558
- roleHost.on("after_provider_response", async (event, ctx) => {
1649
+ // Same observation owner as in-process-session statusAwareFetch → typed-provider-http
1650
+ // sidecar (settlement already merges observation.httpStatus into knownFailure).
1651
+ // after_provider_response covers the success-path onResponse face; fetch wrap covers
1652
+ // non-2xx Responses where openai-completions throws before onResponse (#675).
1653
+ const recordHttpObservation = async (
1654
+ status: number,
1655
+ provider: string,
1656
+ ctx: HostContext,
1657
+ ): Promise<void> => {
1559
1658
  const runDir = process.env.AK_ROLE_RUN_DIR;
1560
1659
  if (typeof runDir !== "string" || runDir.trim() === "") return;
1561
- const provider = ctx.model?.provider;
1562
- if (typeof provider !== "string" || provider.trim() === "") return;
1563
- const status = event.status;
1564
- if (typeof status !== "number") return;
1565
1660
  try {
1566
- await recordTypedProviderHttpStatus(runDir, {
1567
- httpStatus: status,
1568
- provider,
1569
- });
1661
+ await recordTypedProviderHttpStatus(runDir, { httpStatus: status, provider });
1570
1662
  } catch (error) {
1571
1663
  if (
1572
1664
  status >= 200 &&
@@ -1579,9 +1671,49 @@ export function createRoleRuntimeExtension(
1579
1671
  }
1580
1672
  failInfrastructure(error, ctx);
1581
1673
  }
1674
+ };
1675
+ roleHost.on("after_provider_response", async (event, ctx) => {
1676
+ const status = event.status;
1677
+ if (typeof status !== "number") return;
1678
+ const fromCtx = ctx.model?.provider;
1679
+ const provider =
1680
+ typeof fromCtx === "string" && fromCtx.trim() !== ""
1681
+ ? fromCtx
1682
+ : "unknown";
1683
+ await recordHttpObservation(status, provider, ctx);
1582
1684
  });
1583
1685
 
1584
1686
  roleHost.on("session_start", async (event, ctx) => {
1687
+ // Scope fetch observation to this public run (in-process-session statusAwareFetch face).
1688
+ if (!fetchWrapped && typeof globalThis.fetch === "function") {
1689
+ priorFetch = globalThis.fetch.bind(globalThis);
1690
+ const underlying = priorFetch;
1691
+ globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
1692
+ const response = await underlying(input, init);
1693
+ const runDir = process.env.AK_ROLE_RUN_DIR;
1694
+ if (
1695
+ typeof runDir === "string"
1696
+ && runDir.trim() !== ""
1697
+ && typeof response?.status === "number"
1698
+ && (response.status < 200 || response.status >= 300)
1699
+ ) {
1700
+ const provider =
1701
+ typeof ctx.model?.provider === "string" && ctx.model.provider.trim() !== ""
1702
+ ? ctx.model.provider
1703
+ : "unknown";
1704
+ try {
1705
+ await recordTypedProviderHttpStatus(runDir, {
1706
+ httpStatus: response.status,
1707
+ provider,
1708
+ });
1709
+ } catch {
1710
+ // Observation must not break the provider stream (same as in-process-session).
1711
+ }
1712
+ }
1713
+ return response;
1714
+ }) as typeof globalThis.fetch;
1715
+ fetchWrapped = true;
1716
+ }
1585
1717
  admitted = false;
1586
1718
  selectedRole = undefined;
1587
1719
  activeReviewerParent = undefined;
@@ -1634,6 +1766,8 @@ export function createRoleRuntimeExtension(
1634
1766
  inspector,
1635
1767
  gatekeeper,
1636
1768
  navigator,
1769
+ auditor,
1770
+ evidenceChild,
1637
1771
  diarist,
1638
1772
  merger: async () => {
1639
1773
  if (dependencies.mergerGitState === undefined) {
@@ -1653,7 +1787,14 @@ export function createRoleRuntimeExtension(
1653
1787
  const ledgerHome = resolveActivationLedgerHomeForPath(sessionFile);
1654
1788
  const session = durableSessionPointer(ctx.sessionManager);
1655
1789
 
1656
- if (dependencies.createNavigatorAttendance !== undefined) {
1790
+ // Same public activation face for every role (#675 / owner 09-06): no nested-env
1791
+ // skip, no work-role whitelist. Attendance attaches when the envelope supplies it.
1792
+ // Navigator seat never re-attaches itself — prepare turns already ARE the navigator
1793
+ // public activation (prevents summonPublicRole navigator ↔ attendance recursion).
1794
+ if (
1795
+ dependencies.createNavigatorAttendance !== undefined
1796
+ && entry.role !== "navigator"
1797
+ ) {
1657
1798
  let work: NavigatorWorkContext;
1658
1799
  let contextError: unknown;
1659
1800
  if (dependencies.loadNavigatorWorkContext === undefined) {
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Sum assistant-message usage rows from a session.jsonl (nested public summons).
3
+ * Accumulates real cost when rows carry it; never invents cost zeros (#675).
4
+ */
5
+ import type { Usage } from "@earendil-works/pi-ai";
6
+ import { join } from "node:path";
7
+
8
+ import type { PublicSummonResult } from "./public-role-summons.ts";
9
+
10
+ export async function readAssistantUsageFromSessionFile(
11
+ sessionFile: string,
12
+ ): Promise<Usage | undefined> {
13
+ const { readFile } = await import("node:fs/promises");
14
+ let text: string;
15
+ try {
16
+ text = await readFile(sessionFile, "utf8");
17
+ } catch (error) {
18
+ if ((error as NodeJS.ErrnoException)?.code === "ENOENT") return undefined;
19
+ throw error;
20
+ }
21
+ let rows: Array<{ type?: string; message?: { role?: string; usage?: Usage } }>;
22
+ try {
23
+ rows = text
24
+ .trim()
25
+ .split("\n")
26
+ .filter(Boolean)
27
+ .map((line) => JSON.parse(line) as {
28
+ type?: string;
29
+ message?: { role?: string; usage?: Usage };
30
+ });
31
+ } catch (error) {
32
+ throw new Error(
33
+ `session usage parse failed (${sessionFile}): ${error instanceof Error ? error.message : String(error)}`,
34
+ { cause: error },
35
+ );
36
+ }
37
+ let total = 0;
38
+ let input = 0;
39
+ let output = 0;
40
+ let cacheRead = 0;
41
+ let cacheWrite = 0;
42
+ let costInput = 0;
43
+ let costOutput = 0;
44
+ let costCacheRead = 0;
45
+ let costCacheWrite = 0;
46
+ let costTotal = 0;
47
+ let sawCost = false;
48
+ for (const row of rows) {
49
+ if (row.type !== "message" || row.message?.role !== "assistant") continue;
50
+ const usage = row.message.usage;
51
+ if (usage === undefined) continue;
52
+ total += usage.totalTokens ?? 0;
53
+ input += usage.input ?? 0;
54
+ output += usage.output ?? 0;
55
+ cacheRead += usage.cacheRead ?? 0;
56
+ cacheWrite += usage.cacheWrite ?? 0;
57
+ if (usage.cost !== undefined) {
58
+ sawCost = true;
59
+ costInput += usage.cost.input ?? 0;
60
+ costOutput += usage.cost.output ?? 0;
61
+ costCacheRead += usage.cost.cacheRead ?? 0;
62
+ costCacheWrite += usage.cost.cacheWrite ?? 0;
63
+ costTotal += usage.cost.total ?? 0;
64
+ }
65
+ }
66
+ if (total <= 0 && input <= 0 && output <= 0) return undefined;
67
+ const base = {
68
+ input,
69
+ output,
70
+ cacheRead,
71
+ cacheWrite,
72
+ totalTokens: total > 0 ? total : input + output + cacheRead + cacheWrite,
73
+ };
74
+ // Cost only when session rows carried it — unknown stays absent (not zero).
75
+ if (!sawCost) return base as Usage;
76
+ return {
77
+ ...base,
78
+ cost: {
79
+ input: costInput,
80
+ output: costOutput,
81
+ cacheRead: costCacheRead,
82
+ cacheWrite: costCacheWrite,
83
+ total: costTotal,
84
+ },
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Resolve the independent role run's session.jsonl from a public summon result.
90
+ * Prefers explicit runDirectory, then terminal artifacts, then no_receipt runPointer.
91
+ */
92
+ export function sessionFileFromPublicSummon(
93
+ summoned: PublicSummonResult,
94
+ ): string | undefined {
95
+ if (typeof summoned.runDirectory === "string" && summoned.runDirectory.trim() !== "") {
96
+ return join(summoned.runDirectory, "session", "session.jsonl");
97
+ }
98
+ const fromArtifacts = summoned.terminal?.artifacts
99
+ ?.map((a) => (a as { path?: string }).path)
100
+ .find((p): p is string => typeof p === "string" && p.endsWith("session.jsonl"));
101
+ if (fromArtifacts !== undefined) return fromArtifacts;
102
+ const outcome = summoned.terminal?.roleOutcome;
103
+ if (outcome === undefined) return undefined;
104
+ const facts = (outcome as { decisiveFacts?: Record<string, unknown> }).decisiveFacts;
105
+ const pointer = facts?.runPointer;
106
+ if (typeof pointer === "string" && pointer.trim() !== "") {
107
+ return join(pointer, "session", "session.jsonl");
108
+ }
109
+ return undefined;
110
+ }
111
+
112
+ /** Shared usage projection for nested public summons — undefined when unknown. */
113
+ export async function usageFromPublicSummon(
114
+ summoned: PublicSummonResult,
115
+ ): Promise<Usage | undefined> {
116
+ const sessionFile = sessionFileFromPublicSummon(summoned);
117
+ if (sessionFile === undefined) return undefined;
118
+ return readAssistantUsageFromSessionFile(sessionFile);
119
+ }
@@ -3,7 +3,7 @@
3
3
  * errors. Main-role materials derive from PUBLIC_ROLE_RECORDS (including
4
4
  * gatekeeper and navigator). Province map reuses that public gatekeeper record
5
5
  * plus officer material constants (notary shared). Auditor composition stays in
6
- * loadAuditorSoul; loaders share joinPackageMaterials.
6
+ * loadAuditorSoul(subject); loaders share joinPackageMaterials.
7
7
  */
8
8
  import { existsSync } from "node:fs";
9
9
  import { readFile } from "node:fs/promises";
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Single authoritative shape-unreadable marker (#675 / ADR 0055).
3
+ * Settlement stamps it; consumers only read it — never re-derive from cause=output.
4
+ */
5
+
6
+ export const SHAPE_UNREADABLE_KEY = "shapeUnreadable" as const;
7
+
8
+ export type ShapeUnreadableStamp = {
9
+ readonly [SHAPE_UNREADABLE_KEY]: true;
10
+ readonly candidate: unknown;
11
+ readonly acceptedReceipt: false;
12
+ };
13
+
14
+ function isRecord(value: unknown): value is Record<string, unknown> {
15
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16
+ }
17
+
18
+ /** Settlement seat path: stamp retained candidate as shape-unreadable (not infrastructure). */
19
+ export function stampShapeUnreadableDetails(candidate: unknown): ShapeUnreadableStamp {
20
+ return {
21
+ [SHAPE_UNREADABLE_KEY]: true,
22
+ candidate,
23
+ acceptedReceipt: false,
24
+ };
25
+ }
26
+
27
+ /**
28
+ * Marker presence, decided independently of the candidate value: a stamped
29
+ * `undefined` candidate is a shape-unreadable decision, not an absent marker.
30
+ */
31
+ export type RetainedShapeUnreadable = { readonly candidate: unknown };
32
+
33
+ /**
34
+ * Read the settlement-stamped shape candidate from a failure decisiveFacts payload.
35
+ * Looks at root and secondaryEvidence only for the typed marker — no cause inference.
36
+ */
37
+ export function retainedShapeUnreadable(decisiveFacts: unknown): RetainedShapeUnreadable | undefined {
38
+ if (!isRecord(decisiveFacts)) return undefined;
39
+ if (decisiveFacts[SHAPE_UNREADABLE_KEY] === true) {
40
+ return { candidate: decisiveFacts.candidate };
41
+ }
42
+ const secondary = isRecord(decisiveFacts.secondaryEvidence)
43
+ ? decisiveFacts.secondaryEvidence
44
+ : undefined;
45
+ if (secondary !== undefined && secondary[SHAPE_UNREADABLE_KEY] === true) {
46
+ return { candidate: secondary.candidate };
47
+ }
48
+ return undefined;
49
+ }
@@ -5,6 +5,9 @@ function gatekeeperNonPassMessage(result: GatekeeperNonPassResult): string {
5
5
  const findings = result.findings.length === 0 ? "(无 findings)" : result.findings.join("; ");
6
6
  return `门下省打回重写,findings:${findings}`;
7
7
  }
8
+ if (result.status === "unreadable") {
9
+ return `门下省官回执形状不可读(${result.officer}):${result.reason}`;
10
+ }
8
11
  return `门下省 ${result.status}(${result.stage}):${result.reason}`;
9
12
  }
10
13