@akagilnc/pi-workflow-roles 0.1.4956 → 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.
- package/README.md +1 -1
- package/README.zh-CN.md +4 -4
- package/dist/acp-host/production-host.js +140 -265
- package/dist/audit-escalation.js +4 -3
- package/dist/compliance-transport.js +6 -11
- package/dist/doctor-contracts.js +0 -2
- package/dist/gatekeeper-role.js +3 -1
- package/dist/headless-host/production-host.js +140 -265
- package/dist/migrate-book-topology.js +1 -16
- package/dist/packaged-role-registry.js +0 -24
- package/dist/public-cli/main.js +12 -28
- package/dist/public-cli/settlement.js +5 -1
- package/dist/public-cli/terminal.js +2 -2
- package/dist/secretariat-contracts.js +1 -3
- package/dist/submission-ledger.js +9 -0
- package/package.json +1 -1
- package/src/audit-escalation.ts +5 -4
- package/src/collector-role.ts +1 -5
- package/src/compliance-transport.ts +10 -12
- package/src/doctor-auditor.ts +2 -0
- package/src/doctor-contracts.ts +0 -1
- package/src/doctor-role.ts +4 -4
- package/src/gatekeeper-pass-envelope.ts +7 -10
- package/src/gatekeeper-role.ts +3 -1
- package/src/judge-role.ts +45 -31
- package/src/merger-role.ts +1 -2
- package/src/notary-role.ts +1 -2
- package/src/packaged-role-registry.ts +0 -25
- package/src/public-cli/settlement.ts +6 -1
- package/src/public-cli/terminal.ts +2 -2
- package/src/reviewer-role.ts +1 -2
- package/src/role-runtime.ts +59 -156
- package/src/secretariat-contracts.ts +0 -2
- package/src/secretariat-role.ts +3 -162
- package/src/submission-ledger.ts +13 -0
- package/src/worker-role.ts +42 -24
package/src/role-runtime.ts
CHANGED
|
@@ -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,
|
|
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:
|
|
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
|
|
961
|
-
* Back to secretariat itself — 给事中 is not summoned
|
|
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
|
-
*
|
|
968
|
-
*
|
|
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(
|
|
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;
|
|
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
|
-
//
|
|
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
|
|
1023
|
+
return pass;
|
|
1044
1024
|
} catch (error) {
|
|
1045
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
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
|
package/src/secretariat-role.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Secretariat (中书省) label
|
|
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
|
|
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
|
-
}
|
package/src/submission-ledger.ts
CHANGED
|
@@ -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;
|