@akagilnc/pi-workflow-roles 0.1.4951 → 0.1.4977

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.
@@ -1,6 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { readFileSync, writeSync } from "node:fs";
3
3
  import { join } from "node:path";
4
+ import { readableGateItem } from "./readable-gate-item.ts";
4
5
  import {
5
6
  ExplicitInternalActivationError,
6
7
 
@@ -37,8 +38,6 @@ import {
37
38
  } from "./engine-detour.ts";
38
39
  import { engineSessionMaterialFromOptions } from "./package-resources/engine-material.ts";
39
40
  import { registerEngineDetourTool } from "./engine-detour-tool.ts";
40
- import { readableGateItem } from "./readable-gate-item.ts";
41
- import { runIdFromRunDirectory } from "./run-terminal-artifacts.ts";
42
41
  import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_PROMPT } from "./receipt-delivery-policy.ts";
43
42
  import type { AnyCanonicalSkillBinding } from "./canonical-skill-binding.ts";
44
43
  import type { CollectorClock } from "./collector-evidence.ts";
@@ -78,15 +77,9 @@ import {
78
77
  } from "./diarist-role.ts";
79
78
  import {
80
79
  SECRETARIAT_OUTPUT_TOOL_SPEC,
81
- SECRETARIAT_SUMMON_COUNTERSIGN_TOOL_SPEC,
82
- projectSecretariatSummonResult,
83
80
  type SecretariatRuntimeDependencies,
84
- type SecretariatSummonCountersignParameters,
85
81
  } from "./secretariat-role.ts";
86
- import {
87
- SECRETARIAT_OUTPUT_TOOL_NAME,
88
- SECRETARIAT_SUMMON_COUNTERSIGN_TOOL_NAME,
89
- } from "./secretariat-contracts.ts";
82
+ import { SECRETARIAT_OUTPUT_TOOL_NAME } from "./secretariat-contracts.ts";
90
83
  import {
91
84
  projectDiaristSessions,
92
85
  } from "./diarist-contracts.ts";
@@ -116,7 +109,7 @@ import {
116
109
  } from "./navigator-invocation-identity.ts";
117
110
  import { recordTypedProviderHttpStatus } from "./typed-provider-http.ts";
118
111
  import { NAVIGATOR_POST_ROLE_GRACE_MS, raceNavigatorGrace } from "./public-cli/settlement.ts";
119
- import { PACKAGED_ROLE_REGISTRY, isOfficerReviewSeat, packagedRoleAcceptedText, packagedRoleActivationFlags, packagedRoleInputFlag, packagedRoleMetadata, packagedRoleOutputTool, packagedRolePhaseFlag, type PackagedRole } from "./packaged-role-registry.ts";
112
+ import { PACKAGED_ROLE_REGISTRY, isOfficerReviewSeat, packagedRoleActivationFlags, packagedRoleInputFlag, packagedRoleMetadata, packagedRoleOutputTool, packagedRolePhaseFlag, type PackagedRole } from "./packaged-role-registry.ts";
120
113
  import { isAuditEscalationProjection } from "./audit-escalation.ts";
121
114
  import {
122
115
  createJudgeRoleRuntime,
@@ -513,7 +506,7 @@ export type RoleRuntimeDependencies = {
513
506
  loadNotarySourceRun?(path: string): Promise<import("./notary-contracts.ts").NotarySourceRunLocator>;
514
507
  loadDoctorCase?(path: string): Promise<import("./doctor-contracts.ts").DoctorCase>;
515
508
  loadMergerInput?(path: string): Promise<unknown>;
516
- auditDoctorCompliance?(options: { context: HostContext; signal?: AbortSignal }): Promise<ComplianceDecision>;
509
+ auditDoctorCompliance?(options: { context: HostContext; signal?: AbortSignal; submission: unknown }): Promise<ComplianceDecision>;
517
510
  createCollectorClock?(): CollectorClock;
518
511
  createNavigatorAttendance?(options: { context: HostContext; role: string; phase: NavigatorPhase; subjectKey: string; subject: string; authority: string; contextError?: unknown; invocationId: string; onEvent: (event: import("./navigator-attendance.ts").NavigatorEvent, report: import("./navigator-attendance.ts").NavigatorReport) => void | Promise<void> }): NavigatorAttendanceDependency | Promise<NavigatorAttendanceDependency>;
519
512
  loadNavigatorWorkContext?(options: { context: HostContext; role: string; phase: NavigatorPhase; getFlag?: (name: string) => unknown }): Promise<NavigatorWorkContext>;
@@ -646,6 +639,7 @@ function createFiledOfficerRuntime(
646
639
  tool: { name: string; label: string; description: string; promptSnippet: string; parameters: unknown };
647
640
  soulTag: string;
648
641
  beforeAccept?: FiledOfficerBeforeAccept;
642
+ relayGatePass?: true;
649
643
  },
650
644
  dependencies: { loadSoul(): Promise<string> },
651
645
  ) {
@@ -670,11 +664,17 @@ function createFiledOfficerRuntime(
670
664
  spec.beforeAccept === undefined
671
665
  ? undefined
672
666
  : await spec.beforeAccept({ toolCallId, parameters, signal, ctx });
667
+ if (isAuditEscalationProjection(projected)) {
668
+ return { content: [], details: projected, terminate: true as const };
669
+ }
670
+ const pass = spec.relayGatePass === true && projected !== null && typeof projected === "object" && "officer" in projected && "receipt" in projected
671
+ ? projected
672
+ : undefined;
673
673
  // Accept-as-is + terminate only. Shape is not an admission gate
674
674
  // (第 0 条 / ADR 0055); sole-final barrier is ledger-owned (#575).
675
675
  return {
676
- content: [{ type: "text" as const, text: packagedRoleAcceptedText(spec.role) }],
677
- details: projected === undefined ? parameters : projected,
676
+ content: pass === undefined || pass.receipt === undefined ? [] : [{ type: "text" as const, text: readableGateItem(pass.receipt) }],
677
+ details: pass === undefined ? (projected === undefined ? parameters : projected) : parameters,
678
678
  terminate: true as const,
679
679
  };
680
680
  },
@@ -947,50 +947,30 @@ const COUNTERSIGN_QUEUE_STATUSES = new Set(["converged", "continue", "escalate"]
947
947
  const COUNTERSIGN_STATUS_REASK =
948
948
  "countersignStatus 不是 converged、continue、escalate 三态之一。请重新交卷,status 写明其一。" as const;
949
949
 
950
- /**
951
- * #969 authorized non-pi hosts: Secretariat converged is a candidate ticket —
952
- * AK-side submission gate summons 给事中. pi keeps mid-turn summon tool only.
953
- */
954
- const SECRETARIAT_SUBMISSION_GATE_HOSTS = new Set(["codex", "claude", "grok-build"]);
955
-
956
- /** Secretariat status words the non-pi submission gate reads (#969). */
950
+ /** Secretariat status words the submission gate reads. */
957
951
  const SECRETARIAT_QUEUE_STATUSES = new Set(["converged", "escalate"]);
958
952
 
959
953
  /**
960
- * Plain-language re-ask when non-pi secretariatStatus is not a known queue word.
961
- * Back to secretariat itself — 给事中 is not summoned (#969 / ADR 0055).
954
+ * Plain-language re-ask when secretariatStatus is not a known queue word.
955
+ * Back to secretariat itself — 给事中 is not summoned for escalation.
962
956
  */
963
957
  const SECRETARIAT_STATUS_REASK =
964
958
  "secretariatStatus 不是 converged、escalate 之一。请重新交卷,status 写明其一。" as const;
965
959
 
966
960
  /**
967
- * #924 Secretariat on the shared filed-officer envelope + non-terminating
968
- * summon-countersign tool (auditor dossier extension pattern).
969
- * Nested countersign lifecycle stays on summonPublicRole (ADR 0018).
970
- * #924 / 第 0 条 (pi): output tool records the receipt as submitted — no status
971
- * shape/value reject or reask; legality is content-layer / downstream.
972
- * #969 (codex/claude/grok-build): converged arms 既有交卷闸 → 给事中; escalate
973
- * skips the gate; other values reask secretariat (ADR 0055).
961
+ * Secretariat output is terminal; converged submissions enter the shared
962
+ * submission gate on every host that mounts gatekeeper actions.
974
963
  */
975
964
  export function createSecretariatRoleRuntime(
976
965
  roleHost: RoleHost,
977
966
  dependencies: SecretariatRuntimeDependencies,
978
967
  hostActions?: import("./host-contracts.ts").HostGatekeeperActions,
979
968
  ) {
980
- let parentInstruction = "";
981
- // #969: non-pi submission gate only — pi mid-turn summon path stays untouched.
982
969
  const beforeAccept: FiledOfficerBeforeAccept | undefined =
983
970
  hostActions !== undefined
984
971
  ? async ({ toolCallId, parameters, signal, ctx }) => {
985
- const host =
986
- typeof ctx.host === "string" && ctx.host.trim() !== ""
987
- ? ctx.host.trim()
988
- : undefined;
989
- if (host === undefined || !SECRETARIAT_SUBMISSION_GATE_HOSTS.has(host)) {
990
- return undefined;
991
- }
992
972
  if (roleHost.requireGatekeeperPass === undefined) {
993
- const error = new Error(`host ${host} cannot mount the secretariat submission gate`);
973
+ const error = new Error("host cannot mount the secretariat submission gate");
994
974
  error.name = "InfrastructureFailure";
995
975
  return hostActions.failInfrastructure(error, ctx, toolCallId);
996
976
  }
@@ -1006,7 +986,7 @@ export function createSecretariatRoleRuntime(
1006
986
  throw new ParentQueueReaskError(SECRETARIAT_STATUS_REASK);
1007
987
  }
1008
988
  if (status === "escalate") {
1009
- // Parent escalate → throw to caller as-is; 给事中 does not attend (#969).
989
+ // Parent escalate → throw to caller as-is; countersign does not attend.
1010
990
  return undefined;
1011
991
  }
1012
992
  try {
@@ -1019,7 +999,7 @@ export function createSecretariatRoleRuntime(
1019
999
  // #879: this-turn typed payload — identity-bound at submit site.
1020
1000
  submission: parameters,
1021
1001
  });
1022
- // #969: book 给事中 署 snapshot for seat settlement projection (receipt + runId).
1002
+ // Book 给事中 署 snapshot for seat settlement projection (receipt + runId).
1023
1003
  // Ledger accepted stays LLM params (#836); public terminal reads this entry.
1024
1004
  if (
1025
1005
  pass !== undefined
@@ -1040,9 +1020,9 @@ export function createSecretariatRoleRuntime(
1040
1020
  ...(runId === undefined ? {} : { runId }),
1041
1021
  });
1042
1022
  }
1043
- return undefined;
1023
+ return pass;
1044
1024
  } catch (error) {
1045
- // #969: 给事中上呈 ends parent with officer receipt (no retry / 不擅改).
1025
+ // 给事中上呈 ends parent with officer receipt (no retry / 不擅改).
1046
1026
  if (
1047
1027
  error instanceof GatekeeperDecisionError
1048
1028
  && error.result.status === "escalate"
@@ -1056,7 +1036,7 @@ export function createSecretariatRoleRuntime(
1056
1036
  typeof error.result.runId === "string" && error.result.runId.trim() !== ""
1057
1037
  ? error.result.runId
1058
1038
  : undefined;
1059
- // Durable on every host: envelope persists custom entries only
1039
+ // Envelope persists custom entries only
1060
1040
  // (toolResult rows are memory-only on headless/ACP — #617/#959).
1061
1041
  const {
1062
1042
  SECRETARIAT_GATE_OFFICER_ENTRY_TYPE,
@@ -1087,116 +1067,24 @@ export function createSecretariatRoleRuntime(
1087
1067
  roleHost,
1088
1068
  {
1089
1069
  role: "secretariat",
1070
+ relayGatePass: true,
1090
1071
  tool: SECRETARIAT_OUTPUT_TOOL_SPEC,
1091
1072
  soulTag: "secretariat",
1092
1073
  ...(beforeAccept === undefined ? {} : { beforeAccept }),
1093
1074
  },
1094
1075
  dependencies,
1095
1076
  );
1096
- let summonRegistered = false;
1097
1077
  return {
1098
1078
  async activate() {
1099
1079
  await base.activate();
1100
- if (!summonRegistered) {
1101
- summonRegistered = true;
1102
- // Capture parent prompt for default summon instruction (envelope already
1103
- // owns soul inject; this handler only records the assignment text).
1104
- roleHost.on("before_agent_start", (event) => {
1105
- if (typeof event.prompt === "string" && event.prompt.trim() !== "") {
1106
- parentInstruction = event.prompt;
1107
- }
1108
- });
1109
- roleHost.registerTool({
1110
- name: SECRETARIAT_SUMMON_COUNTERSIGN_TOOL_NAME,
1111
- label: SECRETARIAT_SUMMON_COUNTERSIGN_TOOL_SPEC.label,
1112
- description: SECRETARIAT_SUMMON_COUNTERSIGN_TOOL_SPEC.description,
1113
- promptSnippet: SECRETARIAT_SUMMON_COUNTERSIGN_TOOL_SPEC.promptSnippet,
1114
- parameters: SECRETARIAT_SUMMON_COUNTERSIGN_TOOL_SPEC.parameters as never,
1115
- async execute(
1116
- _toolCallId,
1117
- parameters: SecretariatSummonCountersignParameters,
1118
- signal,
1119
- _onUpdate,
1120
- ctx,
1121
- ): Promise<HostToolResult<unknown>> {
1122
- const fromArgs =
1123
- typeof parameters?.instruction === "string"
1124
- ? parameters.instruction.trim()
1125
- : "";
1126
- // #924: instruction comes from tool args or parent prompt only — no
1127
- // unauthored default summons prose (票号写在传召里; no fabricated fallback).
1128
- const instruction =
1129
- fromArgs !== "" ? fromArgs : parentInstruction.trim();
1130
- const correlationId = (() => {
1131
- const runDirectory = runDirectoryFromHostContext(ctx);
1132
- if (runDirectory === undefined) return undefined;
1133
- return runIdFromRunDirectory(runDirectory);
1134
- })();
1135
- const summon = dependencies.summonCountersign;
1136
- const summoned = summon === undefined
1137
- ? await (async () => {
1138
- // #987 Result 7 / #747: mid-turn summon resumes by parentRunPath
1139
- // (secretariat run dir), same key as summonGateOfficer countersign.
1140
- // Board ticket is bind-only — never the resume lookup.
1141
- const coordinates = readRoleRunCoordinates(ctx, "secretariat summons");
1142
- const { summonPublicRole } = await import("./public-role-summons.ts");
1143
- const { readBoardTicketNumber } = await import("./run-ticket-number.ts");
1144
- const parentTicket = await readBoardTicketNumber(coordinates.runDirectory);
1145
- return summonPublicRole({
1146
- role: "countersign",
1147
- argv: ["--project", coordinates.projectRoot, "--", instruction],
1148
- cwd: coordinates.projectRoot,
1149
- home: coordinates.home,
1150
- parentRunPath: coordinates.runDirectory,
1151
- ...(parentTicket === undefined
1152
- ? {}
1153
- : { boundTicketNumber: parentTicket }),
1154
- ...(signal === undefined ? {} : { signal }),
1155
- ...(correlationId === undefined ? {} : { correlationId }),
1156
- ...(dependencies.packageRoot === undefined
1157
- ? {}
1158
- : { packageRoot: dependencies.packageRoot }),
1159
- ...(dependencies.hostAdapters === undefined
1160
- ? {}
1161
- : { hostAdapters: dependencies.hostAdapters }),
1162
- });
1163
- })()
1164
- : await summon({
1165
- instruction,
1166
- cwd: ctx.cwd,
1167
- ...(signal === undefined ? {} : { signal }),
1168
- ...(correlationId === undefined ? {} : { correlationId }),
1169
- ...(dependencies.home === undefined ? {} : { home: dependencies.home }),
1170
- ...(dependencies.packageRoot === undefined
1171
- ? {}
1172
- : { packageRoot: dependencies.packageRoot }),
1173
- });
1174
- const details = projectSecretariatSummonResult(summoned);
1175
- // #953 / #775: parent-visible text from typed details via readableGateItem
1176
- // sole source — payloads + receipt (latest) already carry 传话 facts.
1177
- return {
1178
- content: [
1179
- {
1180
- type: "text" as const,
1181
- text: readableGateItem(details),
1182
- },
1183
- ],
1184
- details,
1185
- };
1186
- },
1187
- });
1188
- }
1189
- const packageRequired = [
1190
- SECRETARIAT_OUTPUT_TOOL_NAME,
1191
- SECRETARIAT_SUMMON_COUNTERSIGN_TOOL_NAME,
1192
- ] as const;
1080
+ const packageRequired = [SECRETARIAT_OUTPUT_TOOL_NAME] as const;
1193
1081
  const all = roleHost.getAllTools().map((tool) => tool.name);
1194
1082
  for (const name of packageRequired) {
1195
1083
  if (all.filter((item) => item === name).length !== 1) {
1196
1084
  throw new Error(`secretariat required tool collision or missing: ${name}`);
1197
1085
  }
1198
1086
  }
1199
- // Host-neutral minimum package surface (#924 G6): declare package tools via
1087
+ // Host-neutral minimum package surface: declare package tools via
1200
1088
  // setActiveTools without hardcoding host builtin names. Preserve any host
1201
1089
  // surface already visible on getAllTools/getActiveTools so body rewrite
1202
1090
  // (public gh path) stays reachable on hosts that expose it.
@@ -1251,21 +1139,30 @@ export function createCountersignRoleRuntime(
1251
1139
  error.name = "InfrastructureFailure";
1252
1140
  return hostActions.failInfrastructure(error, ctx, toolCallId);
1253
1141
  }
1254
- await roleHost.requireGatekeeperPass({
1255
- context: ctx,
1256
- subject: { kind: "countersign_verdict" },
1257
- ...(signal === undefined ? {} : { signal }),
1258
- hostActions,
1259
- toolCallId,
1260
- // #879: this-turn typed payload — identity-bound at submit site.
1261
- submission: parameters,
1262
- });
1142
+ try {
1143
+ return await roleHost.requireGatekeeperPass({
1144
+ context: ctx,
1145
+ subject: { kind: "countersign_verdict" },
1146
+ ...(signal === undefined ? {} : { signal }),
1147
+ hostActions,
1148
+ toolCallId,
1149
+ // #879: this-turn typed payload — identity-bound at submit site.
1150
+ submission: parameters,
1151
+ });
1152
+ } catch (error) {
1153
+ if (error instanceof GatekeeperDecisionError && error.result.status === "escalate") {
1154
+ const { buildAuditEscalationResult } = await import("./audit-escalation.ts");
1155
+ return buildAuditEscalationResult({ status: "escalate", officer: "notary", conflicts: error.result.receipt }, parameters);
1156
+ }
1157
+ throw error;
1158
+ }
1263
1159
  }
1264
1160
  : undefined;
1265
1161
  return createFiledOfficerRuntime(
1266
1162
  roleHost,
1267
1163
  {
1268
1164
  role: "countersign",
1165
+ relayGatePass: true,
1269
1166
  tool: COUNTERSIGN_TOOL_SPEC,
1270
1167
  soulTag: "countersign",
1271
1168
  ...(beforeAccept === undefined ? {} : { beforeAccept }),
@@ -1357,6 +1254,7 @@ export function createRoleRuntimeExtension(
1357
1254
  const pendingInfrastructureFailures = new Map<string, PendingInfrastructureFailure>();
1358
1255
  // Envelope-owned execute→tool_result bridge for submission non-pass (ADR 0018 / #525).
1359
1256
  const pendingSubmissionNonPassByToolCallId = new Map<string, SubmissionNonPassResult>();
1257
+ const pendingPriorGatePassByToolCallId = new Map<string, unknown>();
1360
1258
  let engineDetourRegistered = false;
1361
1259
  // #288 primary-session thin adapter. The policy is the sole budget owner;
1362
1260
  // terminating-tool rejections and mechanical delivery requests share two turns.
@@ -1600,6 +1498,11 @@ export function createRoleRuntimeExtension(
1600
1498
  return { readingMaterial: caseDossier };
1601
1499
  });
1602
1500
  roleHost.on("tool_result", async (event) => {
1501
+ const priorGatePass = pendingPriorGatePassByToolCallId.get(event.toolCallId);
1502
+ pendingPriorGatePassByToolCallId.delete(event.toolCallId);
1503
+ const priorPassContent = priorGatePass !== undefined && event.isError
1504
+ ? [{ type: "text" as const, text: readableGateItem(priorGatePass) }, ...(event.content ?? [])]
1505
+ : undefined;
1603
1506
  const role = selectedRole;
1604
1507
  if (role === undefined) return;
1605
1508
  // #676 E / J1: collector operational bookkeeping on the shared tool_result seam.
@@ -1634,15 +1537,16 @@ export function createRoleRuntimeExtension(
1634
1537
  // Persist typed infrastructure-failure fact onto the role session toolResult so
1635
1538
  // exact-session restart shares the same durable completion classification.
1636
1539
  if (infrastructureDetails !== undefined) {
1637
- return { isError: true };
1540
+ return { isError: true, ...(priorPassContent === undefined ? {} : { content: priorPassContent }) };
1638
1541
  }
1639
1542
  // Submission non-pass: throw kept message text for the model; project the
1640
1543
  // envelope-bound structured result onto session details at this tool_result seam.
1641
1544
  const submissionNonPass = pendingSubmissionNonPassByToolCallId.get(event.toolCallId);
1642
1545
  if (submissionNonPass !== undefined) {
1643
1546
  pendingSubmissionNonPassByToolCallId.delete(event.toolCallId);
1644
- return { details: submissionNonPass, isError: true };
1547
+ return { details: submissionNonPass, isError: true, ...(priorPassContent === undefined ? {} : { content: priorPassContent }) };
1645
1548
  }
1549
+ if (priorPassContent !== undefined) return { content: priorPassContent, isError: true };
1646
1550
  });
1647
1551
  // Queue receipt delivery before `agent_settled`: that event means Pi has
1648
1552
  // already decided no queued continuation will run, so a triggerTurn there is
@@ -1805,6 +1709,7 @@ export function createRoleRuntimeExtension(
1805
1709
  disposeNavigatorAttendanceNonBlocking(attendanceToDispose);
1806
1710
  pendingInfrastructureFailures.clear();
1807
1711
  pendingSubmissionNonPassByToolCallId.clear();
1712
+ pendingPriorGatePassByToolCallId.clear();
1808
1713
  observationFace.reset();
1809
1714
  });
1810
1715
 
@@ -1818,6 +1723,9 @@ export function createRoleRuntimeExtension(
1818
1723
  bindSubmissionNonPass(toolCallId: string, result: SubmissionNonPassResult): void {
1819
1724
  pendingSubmissionNonPassByToolCallId.set(toolCallId, result);
1820
1725
  },
1726
+ bindPriorGatePass(toolCallId: string, receipt: unknown): void {
1727
+ pendingPriorGatePassByToolCallId.set(toolCallId, receipt);
1728
+ },
1821
1729
  };
1822
1730
  const requireRoleSoul = (role: PackagedRole): Promise<string> => dependencies.loadRoleSoul(role);
1823
1731
  const judge = createJudgeRoleRuntime(
@@ -1916,12 +1824,6 @@ export function createRoleRuntimeExtension(
1916
1824
  );
1917
1825
  const secretariat = createSecretariatRoleRuntime(roleHost, {
1918
1826
  loadSoul: () => requireRoleSoul("secretariat"),
1919
- ...(dependencies.packageRoot === undefined
1920
- ? {}
1921
- : { packageRoot: dependencies.packageRoot }),
1922
- ...(dependencies.hostAdapters === undefined
1923
- ? {}
1924
- : { hostAdapters: dependencies.hostAdapters }),
1925
1827
  }, hostActions);
1926
1828
  const merger = createMergerRoleRuntime(roleHost, {
1927
1829
  loadSoul: () => requireRoleSoul("merger"),
@@ -2171,6 +2073,7 @@ export function createRoleRuntimeExtension(
2171
2073
  pendingNavigatorSettlement = undefined;
2172
2074
  pendingInfrastructureFailures.clear();
2173
2075
  pendingSubmissionNonPassByToolCallId.clear();
2076
+ pendingPriorGatePassByToolCallId.clear();
2174
2077
  navigatorWorkContext = undefined;
2175
2078
  // #351: OAuth keepalive is orthogonal to --ak-role; start before role early-return
2176
2079
  // so role-less sessions (and reload after shutdown stop) still keep tokens alive.
@@ -8,8 +8,6 @@
8
8
  */
9
9
 
10
10
  export const SECRETARIAT_OUTPUT_TOOL_NAME = "ak_secretariat_output";
11
- export const SECRETARIAT_SUMMON_COUNTERSIGN_TOOL_NAME =
12
- "ak_secretariat_summon_countersign";
13
11
  /**
14
12
  * #969 durable custom entry: 给事中 terminal (署|上呈) for seat settlement projection.
15
13
  * Carries original receipt bytes + nested runId. Envelope persists custom entries
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Secretariat (中书省) label, soul shape, decision-tool specs, result projection.
2
+ * Secretariat (中书省) label and terminating decision-tool spec.
3
3
  * Lifecycle assembly (activate, register, prompt inject, inventory) lives on the
4
4
  * shared envelope — src/role-runtime.ts (ADR 0018 / #924).
5
5
  */
@@ -7,19 +7,9 @@ import type { Static } from "typebox";
7
7
  import { Type } from "typebox";
8
8
 
9
9
  import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
10
- import type { NamedRoleTurnHostAdapter } from "./public-cli/role-turn-host-resolution.ts";
11
- import type { PublicSummonResult } from "./public-role-summons.ts";
12
- import {
13
- SECRETARIAT_OUTPUT_TOOL_NAME,
14
- SECRETARIAT_SUMMON_COUNTERSIGN_TOOL_NAME,
15
- type SecretariatVerdict,
16
- } from "./secretariat-contracts.ts";
17
- import { runIdFromRunDirectory } from "./run-terminal-artifacts.ts";
10
+ import { SECRETARIAT_OUTPUT_TOOL_NAME, type SecretariatVerdict } from "./secretariat-contracts.ts";
18
11
 
19
- export {
20
- SECRETARIAT_OUTPUT_TOOL_NAME,
21
- SECRETARIAT_SUMMON_COUNTERSIGN_TOOL_NAME,
22
- } from "./secretariat-contracts.ts";
12
+ export { SECRETARIAT_OUTPUT_TOOL_NAME } from "./secretariat-contracts.ts";
23
13
  export type { SecretariatVerdict };
24
14
 
25
15
  /** 中书省终局回执形状;形状指引,非 schema 闸。 */
@@ -51,23 +41,6 @@ export const secretariatVerdictSchema = withTerminatingOutputDeclarations(
51
41
 
52
42
  export type SecretariatVerdictParameters = Static<typeof secretariatVerdictSchema>;
53
43
 
54
- /** 传召给事中参数;形状指引,非 schema 闸。 */
55
- export const secretariatSummonCountersignSchema = Type.Object(
56
- {
57
- instruction: Type.Optional(
58
- Type.String({
59
- description: "传召散文;票号写在其中。空则沿用本局指令。",
60
- }),
61
- ),
62
- },
63
- { additionalProperties: true },
64
- );
65
- (secretariatSummonCountersignSchema as unknown as { required: string[] }).required = [];
66
-
67
- export type SecretariatSummonCountersignParameters = Static<
68
- typeof secretariatSummonCountersignSchema
69
- >;
70
-
71
44
  export const SECRETARIAT_OUTPUT_TOOL_SPEC = {
72
45
  name: SECRETARIAT_OUTPUT_TOOL_NAME,
73
46
  label: "中书省输出",
@@ -76,138 +49,6 @@ export const SECRETARIAT_OUTPUT_TOOL_SPEC = {
76
49
  parameters: secretariatVerdictSchema,
77
50
  } as const;
78
51
 
79
- export const SECRETARIAT_SUMMON_COUNTERSIGN_TOOL_SPEC = {
80
- name: SECRETARIAT_SUMMON_COUNTERSIGN_TOOL_NAME,
81
- label: "传召给事中",
82
- description: "经共享执行接缝传召给事中审票;封驳后续同一 run 再送。",
83
- promptSnippet: "传召给事中",
84
- parameters: secretariatSummonCountersignSchema,
85
- } as const;
86
-
87
- export type SecretariatSummonCountersign = (input: {
88
- readonly instruction: string;
89
- readonly cwd: string;
90
- readonly signal?: AbortSignal;
91
- readonly correlationId?: string;
92
- readonly home?: string;
93
- readonly packageRoot?: string;
94
- }) => Promise<PublicSummonResult>;
95
-
96
52
  export type SecretariatRuntimeDependencies = {
97
53
  loadSoul(): Promise<string>;
98
- /** Package root for nested public summons. */
99
- packageRoot?: string;
100
- /**
101
- * Nested countersign summon. Production uses shared summonPublicRole;
102
- * tests may inject a tracer. Prefer hostAdapters on the default path.
103
- */
104
- summonCountersign?: SecretariatSummonCountersign;
105
- /** Optional home override for nested summons (tests). */
106
- home?: string;
107
- /**
108
- * Composition-root adapter table forwarded by the default summonPublicRole
109
- * path (tests). Production leaves unset.
110
- */
111
- hostAdapters?: readonly NamedRoleTurnHostAdapter[];
112
54
  };
113
-
114
- function latestObjectPayload(
115
- payloads: readonly unknown[] | undefined,
116
- ): Record<string, unknown> | undefined {
117
- if (payloads === undefined || payloads.length === 0) return undefined;
118
- const latest = payloads[payloads.length - 1];
119
- if (latest === null || typeof latest !== "object" || Array.isArray(latest)) {
120
- return undefined;
121
- }
122
- return latest as Record<string, unknown>;
123
- }
124
-
125
- /**
126
- * Project nested countersign PublicSummonResult onto tool details.
127
- * Authority: keep typed terminal kind + payloads/diagnostic intact (gatekeeper
128
- * projectOfficerTerminal precedent / ADR 0052 / 失败诚实). No content gate.
129
- * Parent-visible text uses this details object via readableGateItem directly
130
- * (#953 — no second isomorphic projection).
131
- */
132
- export function projectSecretariatSummonResult(
133
- summoned: PublicSummonResult,
134
- ): Record<string, unknown> {
135
- const terminal = summoned.terminal;
136
- const roleOutcome = terminal?.roleOutcome;
137
- const runDirectory = summoned.runDirectory;
138
- const runId =
139
- typeof runDirectory === "string" && runDirectory.trim() !== ""
140
- ? runIdFromRunDirectory(runDirectory)
141
- : undefined;
142
- const base: Record<string, unknown> = {
143
- exitCode: summoned.exitCode,
144
- ...(runId === undefined ? {} : { runId }),
145
- ...(runDirectory === undefined ? {} : { runDirectory }),
146
- ...(summoned.stderr === undefined || summoned.stderr === ""
147
- ? {}
148
- : { stderr: summoned.stderr }),
149
- };
150
-
151
- if (roleOutcome === undefined) {
152
- return { ...base, outcomeKind: "no_terminal" };
153
- }
154
-
155
- if (roleOutcome.kind === "accepted" || roleOutcome.kind === "audit_escalation") {
156
- const payloads = Array.isArray(roleOutcome.payloads) ? roleOutcome.payloads : undefined;
157
- const latest = latestObjectPayload(payloads);
158
- const countersignStatus =
159
- latest !== undefined && typeof latest.countersignStatus === "string"
160
- ? latest.countersignStatus
161
- : undefined;
162
- return {
163
- ...base,
164
- outcomeKind: roleOutcome.kind,
165
- // audit_escalation carries typed status; accepted may carry fixture/compat status.
166
- ...("status" in roleOutcome &&
167
- typeof roleOutcome.status === "string" &&
168
- roleOutcome.status.length > 0
169
- ? { status: roleOutcome.status }
170
- : {}),
171
- ...(countersignStatus === undefined ? {} : { countersignStatus }),
172
- ...(latest === undefined ? {} : { receipt: latest }),
173
- ...(payloads === undefined ? {} : { payloads }),
174
- ...(roleOutcome.decisiveFacts === undefined
175
- ? {}
176
- : { decisiveFacts: roleOutcome.decisiveFacts }),
177
- };
178
- }
179
-
180
- if (roleOutcome.kind === "failure") {
181
- // #953: current failure face is diagnostic/cause/decisiveFacts only.
182
- // Historical receipts ride terminal.submissions — never as receipt/payloads.
183
- const submissions = Array.isArray(terminal?.submissions)
184
- ? terminal.submissions
185
- : undefined;
186
- return {
187
- ...base,
188
- outcomeKind: "failure",
189
- diagnostic: roleOutcome.diagnostic,
190
- ...(roleOutcome.cause === undefined ? {} : { cause: roleOutcome.cause }),
191
- ...(roleOutcome.decisiveFacts === undefined
192
- ? {}
193
- : { decisiveFacts: roleOutcome.decisiveFacts }),
194
- ...(submissions === undefined || submissions.length === 0
195
- ? {}
196
- : { submissions }),
197
- };
198
- }
199
-
200
- // no_receipt (and any future kind still carries decisiveFacts when present)
201
- return {
202
- ...base,
203
- outcomeKind: roleOutcome.kind,
204
- ...("status" in roleOutcome &&
205
- typeof roleOutcome.status === "string" &&
206
- roleOutcome.status.length > 0
207
- ? { status: roleOutcome.status }
208
- : {}),
209
- ...("decisiveFacts" in roleOutcome && roleOutcome.decisiveFacts !== undefined
210
- ? { decisiveFacts: roleOutcome.decisiveFacts }
211
- : {}),
212
- };
213
- }
@@ -50,6 +50,8 @@ export type SubmissionLedgerEvent =
50
50
  readonly role?: TerminalRoleName;
51
51
  /** Raw LLM params — bounce/infra/audit still keep the original words (#836). */
52
52
  readonly accepted?: unknown;
53
+ /** Audit-owned escalation verdict, separate from the original role submission. */
54
+ readonly auditReceipt?: unknown;
53
55
  }
54
56
  | {
55
57
  readonly type: "sealed";
@@ -102,6 +104,7 @@ export type RecordedSubmissionRow = {
102
104
  */
103
105
  readonly kind: "accepted" | "audit-escalation" | "correctable-rejection" | "infrastructure" | "candidate";
104
106
  readonly accepted: unknown;
107
+ readonly auditReceipt?: unknown;
105
108
  /** Present when the ledger row names the tool call — used to dedupe candidate+outcome. */
106
109
  readonly toolCallId?: string;
107
110
  };
@@ -281,6 +284,7 @@ function rowFromPayload(
281
284
  accepted?: unknown;
282
285
  params?: unknown;
283
286
  toolCallId?: unknown;
287
+ auditReceipt?: unknown;
284
288
  },
285
289
  accepted: unknown,
286
290
  roleFallback?: TerminalRoleName,
@@ -290,6 +294,7 @@ function rowFromPayload(
290
294
  ...(role === undefined ? {} : { role }),
291
295
  kind,
292
296
  accepted,
297
+ ...(Object.hasOwn(payload, "auditReceipt") ? { auditReceipt: payload.auditReceipt } : {}),
293
298
  ...(typeof payload.toolCallId === "string" && payload.toolCallId.length > 0
294
299
  ? { toolCallId: payload.toolCallId }
295
300
  : {}),
@@ -701,6 +706,11 @@ export function createSubmissionLedgerHost(
701
706
  // #836: ledger authority is the LLM tool-call params as-is (角色原话), never result.details.
702
707
  // Machine facts on result.details stay on the tool-result face returned to the model.
703
708
  if (isAuditEscalationProjection(result.details) || isAuditEscalationProjection(params)) {
709
+ const audit = isAuditEscalationProjection(result.details)
710
+ && typeof result.details.audit === "object"
711
+ && result.details.audit !== null
712
+ ? result.details.audit as Record<string, unknown>
713
+ : undefined;
704
714
  const closed: ClosedSubmission = {
705
715
  role,
706
716
  kind: "audit_escalation",
@@ -713,6 +723,9 @@ export function createSubmissionLedgerHost(
713
723
  outcome: "audit-escalation",
714
724
  role,
715
725
  accepted: params,
726
+ ...(audit !== undefined && (Object.hasOwn(audit, "receipt") || Object.hasOwn(audit, "conflicts"))
727
+ ? { auditReceipt: Object.hasOwn(audit, "receipt") ? audit.receipt : audit.conflicts }
728
+ : {}),
716
729
  });
717
730
  await projectClosure(closed, context);
718
731
  return result;