@akagilnc/pi-workflow-roles 0.1.4021 → 0.1.4062
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/dist/acp-host/production-host.js +682 -355
- package/dist/doctor-auditor.js +1 -1
- package/dist/dossier-resolution.js +21 -8
- package/dist/gatekeeper-role.js +34 -27
- package/dist/headless-host/description.js +7 -8
- package/dist/headless-host/production-host.js +709 -369
- package/dist/host-contracts.js +16 -0
- package/dist/inspector-contracts.js +8 -0
- package/dist/navigator-attendance.js +2 -0
- package/dist/navigator-public-session.js +18 -34
- package/dist/navigator-session-contracts.js +19 -0
- package/dist/packaged-role-registry.js +1 -1
- package/dist/pi/known-failure.js +3 -4
- package/dist/pi/role-turn-host.js +43 -13
- package/dist/public-cli/auto-resume.js +20 -7
- package/dist/public-cli/case-dossier-delivery.js +60 -1
- package/dist/public-cli/diarist-run.js +9 -23
- package/dist/public-cli/inspector-run.js +17 -3
- package/dist/public-cli/instruction-seat-run.js +12 -7
- package/dist/public-cli/judge-run.js +3 -1
- package/dist/public-cli/main.js +440 -222
- package/dist/public-cli/notary-run.js +7 -4
- package/dist/public-cli/post-admission.js +66 -59
- package/dist/public-cli/run-lifecycle.js +3 -0
- package/dist/public-cli/settlement.js +78 -58
- package/dist/public-cli/terminal.js +1 -11
- package/dist/public-role-summons.js +8 -0
- package/dist/submission-ledger.js +139 -19
- package/dist/user-dialogue-stdin.js +33 -0
- package/extensions/role-runtime.ts +1 -0
- package/package.json +1 -1
- package/resources/836-deleted-machine-instruction-inventory.md +1 -1
- package/src/doctor-auditor.ts +1 -1
- package/src/dossier-resolution.ts +26 -8
- package/src/external-host-turn-loop.ts +9 -0
- package/src/gatekeeper-pass-envelope.ts +6 -0
- package/src/gatekeeper-role.ts +47 -28
- package/src/headless-host/description.ts +8 -11
- package/src/headless-host/role-turn-host.ts +20 -5
- package/src/host-contracts.ts +30 -7
- package/src/inspector-contracts.ts +7 -0
- package/src/judge-role.ts +4 -0
- package/src/navigator-attendance.ts +2 -0
- package/src/navigator-public-session.ts +19 -55
- package/src/navigator-session-contracts.ts +39 -0
- package/src/navigator-work-context.ts +3 -4
- package/src/notary-role.ts +1 -0
- package/src/packaged-role-registry.ts +1 -1
- package/src/pi/adapter.ts +19 -4
- package/src/pi/known-failure.ts +3 -4
- package/src/pi/role-turn-host.ts +48 -14
- package/src/public-cli/auto-resume.ts +57 -20
- package/src/public-cli/case-dossier-delivery.ts +86 -1
- package/src/public-cli/cli.ts +2 -2
- package/src/public-cli/collector-run.ts +3 -1
- package/src/public-cli/countersign-run.ts +34 -27
- package/src/public-cli/diarist-run.ts +10 -27
- package/src/public-cli/inspector-run.ts +26 -2
- package/src/public-cli/instruction-seat-run.ts +20 -10
- package/src/public-cli/invocation.ts +2 -0
- package/src/public-cli/judge-run.ts +3 -1
- package/src/public-cli/notary-run.ts +13 -4
- package/src/public-cli/post-admission.ts +74 -65
- package/src/public-cli/reviewer-run.ts +3 -1
- package/src/public-cli/run-lifecycle.ts +3 -0
- package/src/public-cli/settlement.ts +105 -80
- package/src/public-cli/terminal.ts +7 -16
- package/src/public-role-summons.ts +31 -7
- package/src/role-envelope.ts +9 -30
- package/src/role-runtime-dependencies.ts +1 -0
- package/src/role-runtime.ts +95 -19
- package/src/submission-ledger.ts +195 -26
- package/src/user-dialogue-stdin.ts +37 -0
- package/src/worker-role.ts +4 -0
package/src/role-envelope.ts
CHANGED
|
@@ -172,6 +172,8 @@ export async function prepareRoleEnvelope(options: {
|
|
|
172
172
|
cwd: request.cwd,
|
|
173
173
|
mode: "print",
|
|
174
174
|
model: request.model === undefined ? undefined : { provider: request.model.provider },
|
|
175
|
+
runDirectory: request.runDirectory,
|
|
176
|
+
...(request.courtAttemptId === undefined ? {} : { courtAttemptId: request.courtAttemptId }),
|
|
175
177
|
sessionManager: {
|
|
176
178
|
getLeafEntry: () => sessionEntries.at(-1) as ReturnType<HostContext["sessionManager"]["getLeafEntry"]>,
|
|
177
179
|
getLeafId: () => runId,
|
|
@@ -239,6 +241,7 @@ export async function prepareRoleEnvelope(options: {
|
|
|
239
241
|
bindSubmissionNonPass: options.hostActions.bindSubmissionNonPass,
|
|
240
242
|
},
|
|
241
243
|
toolCallId: options.toolCallId,
|
|
244
|
+
...(options.submission === undefined ? {} : { submission: options.submission }),
|
|
242
245
|
});
|
|
243
246
|
},
|
|
244
247
|
on(...registration: HostEventRegistration) {
|
|
@@ -527,22 +530,8 @@ export async function prepareRoleEnvelope(options: {
|
|
|
527
530
|
const relay = fileURLToPath(new URL("./mcp-relay.mjs", import.meta.url));
|
|
528
531
|
await listen(server, options.socketPath);
|
|
529
532
|
let disposed = false;
|
|
530
|
-
//
|
|
531
|
-
//
|
|
532
|
-
// identity sees the same signals. Engine axis is request-scoped via RoleHost
|
|
533
|
-
// flag — never process.env (#818 P1). Run-dir may wait
|
|
534
|
-
// until prepare succeeds. Dispose restores including unset.
|
|
535
|
-
let priorAkRoleRunDir: string | undefined;
|
|
536
|
-
let priorAkRoleCourtAttempt: string | undefined;
|
|
537
|
-
let runDirInjected = false;
|
|
538
|
-
const restoreAkRoleRunEnv = (): void => {
|
|
539
|
-
if (!runDirInjected) return;
|
|
540
|
-
runDirInjected = false;
|
|
541
|
-
if (priorAkRoleRunDir === undefined) delete process.env.AK_ROLE_RUN_DIR;
|
|
542
|
-
else process.env.AK_ROLE_RUN_DIR = priorAkRoleRunDir;
|
|
543
|
-
if (priorAkRoleCourtAttempt === undefined) delete process.env.AK_ROLE_COURT_ATTEMPT;
|
|
544
|
-
else process.env.AK_ROLE_COURT_ATTEMPT = priorAkRoleCourtAttempt;
|
|
545
|
-
};
|
|
533
|
+
// Per-turn run/court identity lives on HostContext (request-scoped). Engine
|
|
534
|
+
// axis is already a RoleHost flag — never process.env (#818 P1 / #879).
|
|
546
535
|
const dispose = async (): Promise<void> => {
|
|
547
536
|
if (disposed) return;
|
|
548
537
|
disposed = true;
|
|
@@ -552,11 +541,6 @@ export async function prepareRoleEnvelope(options: {
|
|
|
552
541
|
} catch (error) {
|
|
553
542
|
cleanupFailures.push(error);
|
|
554
543
|
}
|
|
555
|
-
try {
|
|
556
|
-
restoreAkRoleRunEnv();
|
|
557
|
-
} catch (error) {
|
|
558
|
-
cleanupFailures.push(error);
|
|
559
|
-
}
|
|
560
544
|
try {
|
|
561
545
|
const closeAll = (server as unknown as { closeAllConnections?: () => void }).closeAllConnections;
|
|
562
546
|
if (typeof closeAll === "function") closeAll.call(server);
|
|
@@ -640,6 +624,8 @@ export async function prepareRoleEnvelope(options: {
|
|
|
640
624
|
}
|
|
641
625
|
// Method notes only here — role before_agent_start injects soul once.
|
|
642
626
|
// Preloading session materials duplicated soul under the role tag (#632).
|
|
627
|
+
// #879: case-dossier owner reads runDirectory from this turn's HostContext.
|
|
628
|
+
|
|
643
629
|
const methodPrompt = (await Promise.all(request.methods.map(({ path }) => readFile(path, "utf8")))).join("\n\n");
|
|
644
630
|
const promptResults = await emit("before_agent_start", {
|
|
645
631
|
prompt,
|
|
@@ -652,8 +638,8 @@ export async function prepareRoleEnvelope(options: {
|
|
|
652
638
|
return [value.systemPrompt];
|
|
653
639
|
});
|
|
654
640
|
const systemPromptBody = systemPromptParts.length > 0 ? systemPromptParts.join("\n\n") : methodPrompt;
|
|
655
|
-
// Typed reading materials from agent-start handlers (
|
|
656
|
-
// Folded into
|
|
641
|
+
// Typed reading materials from agent-start handlers (incl. single shared
|
|
642
|
+
// case-dossier owner). Folded into provider-visible systemPrompt at send.
|
|
657
643
|
const readingMaterials: unknown[] = [];
|
|
658
644
|
for (const value of promptResults) {
|
|
659
645
|
if (typeof value !== "object" || value === null) continue;
|
|
@@ -662,13 +648,6 @@ export async function prepareRoleEnvelope(options: {
|
|
|
662
648
|
if (material !== undefined) readingMaterials.push(material);
|
|
663
649
|
}
|
|
664
650
|
|
|
665
|
-
priorAkRoleRunDir = process.env.AK_ROLE_RUN_DIR;
|
|
666
|
-
priorAkRoleCourtAttempt = process.env.AK_ROLE_COURT_ATTEMPT;
|
|
667
|
-
process.env.AK_ROLE_RUN_DIR = request.runDirectory;
|
|
668
|
-
if (request.courtAttemptId === undefined) delete process.env.AK_ROLE_COURT_ATTEMPT;
|
|
669
|
-
else process.env.AK_ROLE_COURT_ATTEMPT = request.courtAttemptId;
|
|
670
|
-
runDirInjected = true;
|
|
671
|
-
|
|
672
651
|
const terminating = tools.get(terminatingToolName);
|
|
673
652
|
if (terminating === undefined) {
|
|
674
653
|
throw new Error(`terminating tool not registered after activation: ${terminatingToolName}`);
|
|
@@ -26,6 +26,7 @@ export function createRoleRuntimeDependencies(packageRoot: string): RoleRuntimeD
|
|
|
26
26
|
const doctorAuditor = createPiDoctorAuditor();
|
|
27
27
|
const navigatorSessionFactory = createNativeNavigatorSessionFactory();
|
|
28
28
|
return {
|
|
29
|
+
packageRoot,
|
|
29
30
|
loadJudgeSoul: () => loadMainRoleSessionMaterials("judge"),
|
|
30
31
|
loadFixerSoul: () => loadMainRoleSessionMaterials("fixer"),
|
|
31
32
|
loadFixPacket: (path) => readFile(path, "utf8"),
|
package/src/role-runtime.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { readFileSync, writeSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import {
|
|
4
4
|
ExplicitInternalActivationError,
|
|
5
|
+
isOfficerReviewSeat,
|
|
6
|
+
runDirectoryFromHostContext,
|
|
5
7
|
type HostContext,
|
|
6
8
|
type HostToolResult,
|
|
7
9
|
type RoleEnvelopeHost,
|
|
@@ -36,7 +38,13 @@ import {
|
|
|
36
38
|
writeToolExecutionObservationRecord,
|
|
37
39
|
type ToolExecutionObservationWriter,
|
|
38
40
|
} from "./tool-execution-observation.ts";
|
|
39
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
ENGINE_DETOUR_TOOL_NAME,
|
|
43
|
+
ENGINE_MODEL_FLAG_NAME,
|
|
44
|
+
resolveEngineModel,
|
|
45
|
+
resolveEngineName,
|
|
46
|
+
} from "./engine-detour.ts";
|
|
47
|
+
import { engineSessionMaterialFromOptions } from "./package-resources/engine-material.ts";
|
|
40
48
|
import { registerEngineDetourTool } from "./engine-detour-tool.ts";
|
|
41
49
|
import { createReceiptDeliveryPolicy, NO_RECEIPT_LIFECYCLE_ENTRY_TYPE, RECEIPT_DELIVERY_PROMPT } from "./receipt-delivery-policy.ts";
|
|
42
50
|
import type { AnyCanonicalSkillBinding } from "./canonical-skill-binding.ts";
|
|
@@ -71,7 +79,7 @@ import {
|
|
|
71
79
|
INSPECTOR_TOOL_SPEC,
|
|
72
80
|
type InspectorRuntimeDependencies,
|
|
73
81
|
} from "./inspector-role.ts";
|
|
74
|
-
import { INSPECTOR_ACCEPTED_TEXT } from "./inspector-contracts.ts";
|
|
82
|
+
import { INSPECTOR_ACCEPTED_TEXT, INSPECTOR_SOURCE_RUN_FLAG } from "./inspector-contracts.ts";
|
|
75
83
|
import {
|
|
76
84
|
DIARIST_TOOL_SPEC,
|
|
77
85
|
type DiaristRuntimeDependencies,
|
|
@@ -566,6 +574,8 @@ type NavigatorAttendanceDependency = Omit<NavigatorAttendance, "knownRoutePlaybo
|
|
|
566
574
|
Partial<Pick<NavigatorAttendance, "knownRoutePlaybookReadFailure">>;
|
|
567
575
|
|
|
568
576
|
export type RoleRuntimeDependencies = {
|
|
577
|
+
/** Package root for packaged engine-note resolution (#879). */
|
|
578
|
+
packageRoot?: string;
|
|
569
579
|
loadJudgeSoul(): Promise<string>;
|
|
570
580
|
loadFixerSoul?(): Promise<string>;
|
|
571
581
|
loadFixPacket?(path: string): Promise<string>;
|
|
@@ -898,14 +908,14 @@ function readDiaristTicketAssertion(
|
|
|
898
908
|
}
|
|
899
909
|
|
|
900
910
|
/** Run coordinates + optional pre-bound ticket from durable pages (#779). */
|
|
901
|
-
function readDiaristRunCoordinates(): {
|
|
911
|
+
function readDiaristRunCoordinates(ctx: HostContext): {
|
|
902
912
|
readonly runDirectory: string;
|
|
903
913
|
readonly projectRoot: string;
|
|
904
914
|
readonly home: string;
|
|
905
915
|
readonly boundTicketNumber?: number;
|
|
906
916
|
} {
|
|
907
|
-
const runDirectory =
|
|
908
|
-
if (
|
|
917
|
+
const runDirectory = runDirectoryFromHostContext(ctx);
|
|
918
|
+
if (runDirectory === undefined) {
|
|
909
919
|
throw new Error("diarist accept requires AK_ROLE_RUN_DIR");
|
|
910
920
|
}
|
|
911
921
|
const admittedPath = join(runDirectory, "admitted-request.json");
|
|
@@ -948,13 +958,13 @@ export function createDiaristRoleRuntime(
|
|
|
948
958
|
tool: DIARIST_TOOL_SPEC,
|
|
949
959
|
acceptedText: DIARIST_ACCEPTED_TEXT,
|
|
950
960
|
soulTag: "diarist",
|
|
951
|
-
beforeAccept: async ({ parameters }) => {
|
|
961
|
+
beforeAccept: async ({ parameters, ctx }) => {
|
|
952
962
|
const submitted =
|
|
953
963
|
parameters !== null && typeof parameters === "object" && !Array.isArray(parameters)
|
|
954
964
|
? (parameters as Record<string, unknown>)
|
|
955
965
|
: undefined;
|
|
956
966
|
const assertion = readDiaristTicketAssertion(submitted);
|
|
957
|
-
const coords = readDiaristRunCoordinates();
|
|
967
|
+
const coords = readDiaristRunCoordinates(ctx);
|
|
958
968
|
// #836 7.3: pre-bound ticket is material for the LLM, not an override.
|
|
959
969
|
const ticketNumber = assertion.kind === "ticket" ? assertion.ticketNumber : undefined;
|
|
960
970
|
if (ticketNumber !== undefined) {
|
|
@@ -1020,6 +1030,8 @@ export function createCountersignRoleRuntime(
|
|
|
1020
1030
|
...(signal === undefined ? {} : { signal }),
|
|
1021
1031
|
hostActions,
|
|
1022
1032
|
toolCallId,
|
|
1033
|
+
// #879: this-turn typed payload — identity-bound at submit site.
|
|
1034
|
+
submission: parameters,
|
|
1023
1035
|
});
|
|
1024
1036
|
}
|
|
1025
1037
|
: undefined;
|
|
@@ -1057,6 +1069,10 @@ export function createRoleRuntimeExtension(
|
|
|
1057
1069
|
for (const flag of NOTARY_TRANSPORT_FLAGS) {
|
|
1058
1070
|
roleHost.registerFlag(flag.name, flag.definition);
|
|
1059
1071
|
}
|
|
1072
|
+
roleHost.registerFlag(
|
|
1073
|
+
INSPECTOR_SOURCE_RUN_FLAG.name,
|
|
1074
|
+
INSPECTOR_SOURCE_RUN_FLAG.definition,
|
|
1075
|
+
);
|
|
1060
1076
|
for (const flag of GLEANER_LEFT_TRANSPORT_FLAGS) {
|
|
1061
1077
|
roleHost.registerFlag(flag.name, flag.definition);
|
|
1062
1078
|
}
|
|
@@ -1066,6 +1082,13 @@ export function createRoleRuntimeExtension(
|
|
|
1066
1082
|
}
|
|
1067
1083
|
// Station-child identity (#840): omit navigator attendance. One flag.
|
|
1068
1084
|
roleHost.registerFlag(STATION_CHILD_FLAG.name, STATION_CHILD_FLAG.definition);
|
|
1085
|
+
// Register model only. Pi never sets ak-engine — resolveEngineName must
|
|
1086
|
+
// fall through to child-process env. An empty default would block that.
|
|
1087
|
+
roleHost.registerFlag(ENGINE_MODEL_FLAG_NAME, {
|
|
1088
|
+
description: "本次劳务引擎模型",
|
|
1089
|
+
type: "string",
|
|
1090
|
+
default: "",
|
|
1091
|
+
});
|
|
1069
1092
|
|
|
1070
1093
|
let admitted = false;
|
|
1071
1094
|
let selectedRole: string | undefined;
|
|
@@ -1163,6 +1186,7 @@ export function createRoleRuntimeExtension(
|
|
|
1163
1186
|
settleNavigatorProjection,
|
|
1164
1187
|
);
|
|
1165
1188
|
roleHost.on("input", (event) => {
|
|
1189
|
+
const text = event.text;
|
|
1166
1190
|
const role = roleHost.getFlag(ROLE_FLAG.name);
|
|
1167
1191
|
if (role !== undefined && !admitted) return { action: "handled" as const };
|
|
1168
1192
|
// Reviewer: recover original request; Pi argv may already carry native form.
|
|
@@ -1175,14 +1199,19 @@ export function createRoleRuntimeExtension(
|
|
|
1175
1199
|
reviewerOriginalRequest =
|
|
1176
1200
|
roleHost.capabilities?.skillOriginalRequest?.(
|
|
1177
1201
|
activeReviewerParent.skillBinding.name,
|
|
1178
|
-
|
|
1179
|
-
) ??
|
|
1180
|
-
return
|
|
1202
|
+
text,
|
|
1203
|
+
) ?? text;
|
|
1204
|
+
return text === event.text
|
|
1205
|
+
? { action: "continue" as const }
|
|
1206
|
+
: { action: "transform" as const, text };
|
|
1181
1207
|
}
|
|
1182
|
-
return
|
|
1208
|
+
return text === event.text
|
|
1209
|
+
? { action: "continue" as const }
|
|
1210
|
+
: { action: "transform" as const, text };
|
|
1183
1211
|
});
|
|
1184
1212
|
roleHost.on("before_agent_start", async (event, ctx) => {
|
|
1185
1213
|
const role = roleHost.getFlag(ROLE_FLAG.name);
|
|
1214
|
+
const prompt = event.prompt;
|
|
1186
1215
|
if (role === undefined) return;
|
|
1187
1216
|
if (!admitted || selectedRole !== role) {
|
|
1188
1217
|
failInfrastructure(new ActivationBarrierError(role), ctx);
|
|
@@ -1207,7 +1236,7 @@ export function createRoleRuntimeExtension(
|
|
|
1207
1236
|
// output payload (#836 targets the latter): the human's own raw
|
|
1208
1237
|
// prompt bytes, copied verbatim into Navigator's work context when
|
|
1209
1238
|
// nothing else has supplied a subject yet.
|
|
1210
|
-
const subject =
|
|
1239
|
+
const subject = prompt.trim();
|
|
1211
1240
|
if (subject !== "") {
|
|
1212
1241
|
const root = subjectPath(ctx.sessionManager.getSessionDir(), ctx.cwd);
|
|
1213
1242
|
const subjectProvenance = "user_prompt" satisfies NavigatorSubjectProvenance;
|
|
@@ -1231,7 +1260,7 @@ export function createRoleRuntimeExtension(
|
|
|
1231
1260
|
if (!reviewerExpansionCaptured) {
|
|
1232
1261
|
if (reviewerOriginalRequest !== undefined) {
|
|
1233
1262
|
activeReviewerParent.skillBinding.captureExpansion(
|
|
1234
|
-
roleHost.capabilities?.skillExpansion(
|
|
1263
|
+
roleHost.capabilities?.skillExpansion(prompt),
|
|
1235
1264
|
reviewerOriginalRequest,
|
|
1236
1265
|
);
|
|
1237
1266
|
}
|
|
@@ -1255,6 +1284,54 @@ export function createRoleRuntimeExtension(
|
|
|
1255
1284
|
};
|
|
1256
1285
|
}
|
|
1257
1286
|
});
|
|
1287
|
+
// #879: engine coordinates ride readingMaterial only when station-child
|
|
1288
|
+
// officer dialogue replaced the ordinary transport prompt (no double fold).
|
|
1289
|
+
roleHost.on("before_agent_start", () => {
|
|
1290
|
+
const role = selectedRole ?? roleHost.getFlag(ROLE_FLAG.name);
|
|
1291
|
+
if (typeof role !== "string" || !isOfficerReviewSeat(role)) return;
|
|
1292
|
+
if (roleHost.getFlag(STATION_CHILD_FLAG.name) !== true) return;
|
|
1293
|
+
const engine = resolveEngineName((name) => roleHost.getFlag(name));
|
|
1294
|
+
if (engine === undefined || dependencies.packageRoot === undefined) return;
|
|
1295
|
+
const engineModel = resolveEngineModel((name) => roleHost.getFlag(name));
|
|
1296
|
+
const material = engineSessionMaterialFromOptions({
|
|
1297
|
+
engine,
|
|
1298
|
+
...(engineModel === undefined ? {} : { engineModel }),
|
|
1299
|
+
packageRoot: dependencies.packageRoot,
|
|
1300
|
+
});
|
|
1301
|
+
if (material === undefined) return;
|
|
1302
|
+
return {
|
|
1303
|
+
readingMaterial: {
|
|
1304
|
+
kind: "engine-session-material" as const,
|
|
1305
|
+
name: material.name,
|
|
1306
|
+
...(material.model === undefined ? {} : { model: material.model }),
|
|
1307
|
+
...(material.materialPath === undefined ? {} : { materialPath: material.materialPath }),
|
|
1308
|
+
},
|
|
1309
|
+
};
|
|
1310
|
+
});
|
|
1311
|
+
roleHost.on("before_agent_start", () => {
|
|
1312
|
+
const path = roleHost.getFlag(INSPECTOR_SOURCE_RUN_FLAG.name);
|
|
1313
|
+
if (typeof path !== "string" || path.trim() === "") return;
|
|
1314
|
+
return {
|
|
1315
|
+
readingMaterial: {
|
|
1316
|
+
kind: "inspector-parent-binding" as const,
|
|
1317
|
+
sourceRunPath: path,
|
|
1318
|
+
},
|
|
1319
|
+
};
|
|
1320
|
+
});
|
|
1321
|
+
// #879: single shared owner of station-child 0081 case-dossier → readingMaterial fold.
|
|
1322
|
+
// post-admission freezes under run/attachments/case-dossier/; this handler alone
|
|
1323
|
+
// projects it onto the existing agent-start materials face (Pi + envelope collect).
|
|
1324
|
+
// Role modules must not re-read the freeze (ADR 0018 lifecycle; no duplicate fold).
|
|
1325
|
+
roleHost.on("before_agent_start", async (_event, ctx) => {
|
|
1326
|
+
const runDir = runDirectoryFromHostContext(ctx);
|
|
1327
|
+
if (runDir === undefined) return;
|
|
1328
|
+
const { loadCaseDossierReadingMaterial } = await import(
|
|
1329
|
+
"./public-cli/case-dossier-delivery.ts"
|
|
1330
|
+
);
|
|
1331
|
+
const caseDossier = await loadCaseDossierReadingMaterial(runDir);
|
|
1332
|
+
if (caseDossier === undefined) return;
|
|
1333
|
+
return { readingMaterial: caseDossier };
|
|
1334
|
+
});
|
|
1258
1335
|
roleHost.on("tool_result", async (event) => {
|
|
1259
1336
|
const role = selectedRole;
|
|
1260
1337
|
if (role === undefined) return;
|
|
@@ -1341,7 +1418,7 @@ export function createRoleRuntimeExtension(
|
|
|
1341
1418
|
display: false,
|
|
1342
1419
|
}, { triggerTurn: true, deliverAs: "followUp" });
|
|
1343
1420
|
} else if (receiptDelivery.nextAction() === "no-receipt" && !noReceiptRecorded) {
|
|
1344
|
-
const runPointer =
|
|
1421
|
+
const runPointer = runDirectoryFromHostContext(ctx);
|
|
1345
1422
|
if (runPointer !== undefined) {
|
|
1346
1423
|
noReceiptRecorded = true;
|
|
1347
1424
|
const facts = receiptDelivery.facts({ runPointer, attemptPointer: `current:${runPointer}` });
|
|
@@ -1750,8 +1827,8 @@ export function createRoleRuntimeExtension(
|
|
|
1750
1827
|
provider: string,
|
|
1751
1828
|
ctx: HostContext,
|
|
1752
1829
|
): Promise<void> => {
|
|
1753
|
-
const runDir =
|
|
1754
|
-
if (
|
|
1830
|
+
const runDir = runDirectoryFromHostContext(ctx);
|
|
1831
|
+
if (runDir === undefined) return;
|
|
1755
1832
|
try {
|
|
1756
1833
|
await recordTypedProviderHttpStatus(runDir, { httpStatus: status, provider });
|
|
1757
1834
|
} catch (error) {
|
|
@@ -1785,10 +1862,9 @@ export function createRoleRuntimeExtension(
|
|
|
1785
1862
|
const underlying = priorFetch;
|
|
1786
1863
|
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
1787
1864
|
const response = await underlying(input, init);
|
|
1788
|
-
const runDir =
|
|
1865
|
+
const runDir = runDirectoryFromHostContext(ctx);
|
|
1789
1866
|
if (
|
|
1790
|
-
|
|
1791
|
-
&& runDir.trim() !== ""
|
|
1867
|
+
runDir !== undefined
|
|
1792
1868
|
&& typeof response?.status === "number"
|
|
1793
1869
|
&& (response.status < 200 || response.status >= 300)
|
|
1794
1870
|
) {
|
package/src/submission-ledger.ts
CHANGED
|
@@ -2,7 +2,13 @@ import {
|
|
|
2
2
|
resolveActivationLedgerHome,
|
|
3
3
|
tryHomeFromAkRolesPath,
|
|
4
4
|
} from "./activation-ledger-topology.ts";
|
|
5
|
-
import
|
|
5
|
+
import {
|
|
6
|
+
courtAttemptIdFromHostContext,
|
|
7
|
+
runDirectoryFromHostContext,
|
|
8
|
+
type HostContext,
|
|
9
|
+
type HostToolResult,
|
|
10
|
+
type RoleHost,
|
|
11
|
+
} from "./host-contracts.ts";
|
|
6
12
|
import { isAuditEscalationProjection } from "./audit-escalation.ts";
|
|
7
13
|
|
|
8
14
|
|
|
@@ -24,6 +30,8 @@ export type SubmissionLedgerEvent =
|
|
|
24
30
|
readonly toolCallId: string;
|
|
25
31
|
readonly toolName: string;
|
|
26
32
|
readonly sequence: number;
|
|
33
|
+
/** Seat identity — machine fact beside the payload (ADR 0042 / #881). */
|
|
34
|
+
readonly role?: TerminalRoleName;
|
|
27
35
|
/** LLM tool-call params at call time (#836 原话). */
|
|
28
36
|
readonly params?: unknown;
|
|
29
37
|
}
|
|
@@ -55,8 +63,8 @@ export type SubmissionLedgerEvent =
|
|
|
55
63
|
* otherwise session header id. Never a shared "unbound" bucket.
|
|
56
64
|
*/
|
|
57
65
|
function runIdentity(context: HostContext): string {
|
|
58
|
-
const directory =
|
|
59
|
-
if (
|
|
66
|
+
const directory = runDirectoryFromHostContext(context);
|
|
67
|
+
if (directory !== undefined) {
|
|
60
68
|
const fromDir = runIdFromRunDirectory(directory);
|
|
61
69
|
if (fromDir !== undefined) return fromDir;
|
|
62
70
|
}
|
|
@@ -72,16 +80,26 @@ function runIdentity(context: HostContext): string {
|
|
|
72
80
|
export const COURT_ATTEMPT_ENV = "AK_ROLE_COURT_ATTEMPT" as const;
|
|
73
81
|
|
|
74
82
|
function attemptIdentity(context: HostContext, runId: string): string {
|
|
75
|
-
const courtAttempt =
|
|
76
|
-
if (
|
|
83
|
+
const courtAttempt = courtAttemptIdFromHostContext(context);
|
|
84
|
+
if (courtAttempt !== undefined) return courtAttempt;
|
|
77
85
|
return context.sessionManager.getHeader?.()?.id ?? context.sessionManager.getLeafId?.() ?? `${runId}:initial`;
|
|
78
86
|
}
|
|
79
87
|
|
|
80
88
|
/** One recorded role submission as stored — original payload plus host identity. */
|
|
81
89
|
export type RecordedSubmissionRow = {
|
|
82
|
-
|
|
83
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Seat identity when known. Historical correctable/infrastructure rows may omit it;
|
|
92
|
+
* payloads still project (#881). Terminal acceptance kind still requires a role match.
|
|
93
|
+
*/
|
|
94
|
+
readonly role?: TerminalRoleName;
|
|
95
|
+
/**
|
|
96
|
+
* Recording class on the ledger. Terminal acceptance kind still only follows
|
|
97
|
+
* `accepted` / `audit-escalation`; every class carries the original payload (#881).
|
|
98
|
+
*/
|
|
99
|
+
readonly kind: "accepted" | "audit-escalation" | "correctable-rejection" | "infrastructure" | "candidate";
|
|
84
100
|
readonly accepted: unknown;
|
|
101
|
+
/** Present when the ledger row names the tool call — used to dedupe candidate+outcome. */
|
|
102
|
+
readonly toolCallId?: string;
|
|
85
103
|
};
|
|
86
104
|
|
|
87
105
|
/** Closed-submission callback: original payload, no status/facts projection (#836). */
|
|
@@ -191,44 +209,192 @@ function recordedRole(payload: { role?: unknown; projection?: { role?: unknown }
|
|
|
191
209
|
return undefined;
|
|
192
210
|
}
|
|
193
211
|
|
|
212
|
+
/** Prefer a more complete row for the same tool call without inventing a sole winner across calls. */
|
|
213
|
+
function rowRank(kind: RecordedSubmissionRow["kind"]): number {
|
|
214
|
+
switch (kind) {
|
|
215
|
+
case "accepted":
|
|
216
|
+
return 4;
|
|
217
|
+
case "audit-escalation":
|
|
218
|
+
return 3;
|
|
219
|
+
case "correctable-rejection":
|
|
220
|
+
case "infrastructure":
|
|
221
|
+
return 2;
|
|
222
|
+
case "candidate":
|
|
223
|
+
return 1;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
194
227
|
/**
|
|
195
|
-
*
|
|
228
|
+
* Same-call identity for reader pairing only (#881 / #836).
|
|
229
|
+
* attemptId is already on the record (subject/payload); bare toolCallId alone
|
|
230
|
+
* collapses distinct court attempts that reused a host call id.
|
|
231
|
+
*/
|
|
232
|
+
function submissionCallKey(attemptId: string | undefined, toolCallId: string): string {
|
|
233
|
+
return `${attemptId ?? ""}\0${toolCallId}`;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function rowFromPayload(
|
|
237
|
+
kind: RecordedSubmissionRow["kind"],
|
|
238
|
+
payload: {
|
|
239
|
+
role?: unknown;
|
|
240
|
+
projection?: { role?: unknown };
|
|
241
|
+
accepted?: unknown;
|
|
242
|
+
params?: unknown;
|
|
243
|
+
toolCallId?: unknown;
|
|
244
|
+
},
|
|
245
|
+
accepted: unknown,
|
|
246
|
+
roleFallback?: TerminalRoleName,
|
|
247
|
+
): RecordedSubmissionRow {
|
|
248
|
+
const role = recordedRole(payload) ?? roleFallback;
|
|
249
|
+
return {
|
|
250
|
+
...(role === undefined ? {} : { role }),
|
|
251
|
+
kind,
|
|
252
|
+
accepted,
|
|
253
|
+
...(typeof payload.toolCallId === "string" && payload.toolCallId.length > 0
|
|
254
|
+
? { toolCallId: payload.toolCallId }
|
|
255
|
+
: {}),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* All recorded role submissions in ledger order (#836 multi-submit / #881).
|
|
261
|
+
* Projects every original payload — sealed, audit-escalation, correctable-rejection,
|
|
262
|
+
* infrastructure, and bare candidate — without outcome-class filtering.
|
|
263
|
+
* Same call (candidate + outcome both carrying params) appears once — keyed by
|
|
264
|
+
* recorded attemptId + toolCallId so distinct court attempts stay distinct (#881).
|
|
196
265
|
* `accepted` is the original payload; never rebuilt from a status/facts envelope.
|
|
197
266
|
*/
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
): Promise<readonly RecordedSubmissionRow[]> {
|
|
203
|
-
const scope = resolveReadScope(homeOrScope);
|
|
204
|
-
const { owned } = await readOwnedSubmissionRecords(cwd, runId, scope.home);
|
|
205
|
-
const scoped = recordsForAttempt(owned, scope.attemptId);
|
|
267
|
+
function mapOwnedToSubmissionRows(
|
|
268
|
+
owned: readonly { kind?: unknown; subject?: unknown; payload?: unknown }[],
|
|
269
|
+
): readonly RecordedSubmissionRow[] {
|
|
270
|
+
const scoped = owned;
|
|
206
271
|
const out: RecordedSubmissionRow[] = [];
|
|
272
|
+
const indexByCall = new Map<string, number>();
|
|
273
|
+
// Recover seat identity for historical non-sealed rows that omitted role (#881).
|
|
274
|
+
const roleByCall = new Map<string, TerminalRoleName>();
|
|
275
|
+
for (const record of scoped) {
|
|
276
|
+
const payload = record.payload as {
|
|
277
|
+
toolCallId?: unknown;
|
|
278
|
+
role?: unknown;
|
|
279
|
+
projection?: { role?: unknown };
|
|
280
|
+
} | undefined;
|
|
281
|
+
if (typeof payload?.toolCallId !== "string" || payload.toolCallId.length === 0) continue;
|
|
282
|
+
const role = recordedRole(payload);
|
|
283
|
+
if (role !== undefined) {
|
|
284
|
+
roleByCall.set(submissionCallKey(recordAttemptId(record), payload.toolCallId), role);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const take = (row: RecordedSubmissionRow, callKey: string | undefined): void => {
|
|
289
|
+
const toolCallId = row.toolCallId;
|
|
290
|
+
if (toolCallId !== undefined && callKey !== undefined) {
|
|
291
|
+
const existingIndex = indexByCall.get(callKey);
|
|
292
|
+
if (existingIndex !== undefined) {
|
|
293
|
+
const existing = out[existingIndex]!;
|
|
294
|
+
if (rowRank(row.kind) >= rowRank(existing.kind)) {
|
|
295
|
+
out[existingIndex] = {
|
|
296
|
+
...row,
|
|
297
|
+
toolCallId,
|
|
298
|
+
// Keep a previously recovered role when the upgraded row still omits it.
|
|
299
|
+
...(row.role === undefined && existing.role !== undefined ? { role: existing.role } : {}),
|
|
300
|
+
};
|
|
301
|
+
} else if (existing.role === undefined && row.role !== undefined) {
|
|
302
|
+
out[existingIndex] = { ...existing, role: row.role };
|
|
303
|
+
}
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
indexByCall.set(callKey, out.length);
|
|
307
|
+
}
|
|
308
|
+
out.push(row);
|
|
309
|
+
};
|
|
310
|
+
|
|
207
311
|
for (const record of scoped) {
|
|
312
|
+
const attemptId = recordAttemptId(record);
|
|
313
|
+
if (record.kind === "candidate") {
|
|
314
|
+
const payload = record.payload as Partial<Extract<SubmissionLedgerEvent, { type: "candidate" }>> & {
|
|
315
|
+
projection?: { role?: unknown };
|
|
316
|
+
} | undefined;
|
|
317
|
+
if (payload?.type !== "candidate" || payload.params === undefined) continue;
|
|
318
|
+
const callKey =
|
|
319
|
+
typeof payload.toolCallId === "string"
|
|
320
|
+
? submissionCallKey(attemptId, payload.toolCallId)
|
|
321
|
+
: undefined;
|
|
322
|
+
const fallback = callKey !== undefined ? roleByCall.get(callKey) : undefined;
|
|
323
|
+
take(rowFromPayload("candidate", payload, payload.params, fallback), callKey);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
208
326
|
if (record.kind === "sealed") {
|
|
209
327
|
const payload = record.payload as Partial<Extract<SubmissionLedgerEvent, { type: "sealed" }>> & {
|
|
210
328
|
projection?: { role?: unknown };
|
|
211
329
|
} | undefined;
|
|
212
330
|
if (payload?.type !== "sealed" || payload.accepted === undefined) continue;
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
331
|
+
const callKey =
|
|
332
|
+
typeof payload.toolCallId === "string"
|
|
333
|
+
? submissionCallKey(attemptId, payload.toolCallId)
|
|
334
|
+
: undefined;
|
|
335
|
+
const fallback = callKey !== undefined ? roleByCall.get(callKey) : undefined;
|
|
336
|
+
take(rowFromPayload("accepted", payload, payload.accepted, fallback), callKey);
|
|
216
337
|
continue;
|
|
217
338
|
}
|
|
218
339
|
if (record.kind !== "outcome") continue;
|
|
219
340
|
const payload = record.payload as Partial<Extract<SubmissionLedgerEvent, { type: "outcome" }>> & {
|
|
220
341
|
projection?: { role?: unknown };
|
|
221
342
|
} | undefined;
|
|
222
|
-
if (payload?.type !== "outcome" || payload.
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
343
|
+
if (payload?.type !== "outcome" || payload.accepted === undefined) continue;
|
|
344
|
+
const outcome = payload.outcome;
|
|
345
|
+
const kind: RecordedSubmissionRow["kind"] |
|
|
346
|
+
undefined =
|
|
347
|
+
outcome === "audit-escalation"
|
|
348
|
+
? "audit-escalation"
|
|
349
|
+
: outcome === "correctable-rejection"
|
|
350
|
+
? "correctable-rejection"
|
|
351
|
+
: outcome === "infrastructure"
|
|
352
|
+
? "infrastructure"
|
|
353
|
+
: undefined;
|
|
354
|
+
if (kind === undefined) continue;
|
|
355
|
+
const callKey =
|
|
356
|
+
typeof payload.toolCallId === "string"
|
|
357
|
+
? submissionCallKey(attemptId, payload.toolCallId)
|
|
358
|
+
: undefined;
|
|
359
|
+
const fallback = callKey !== undefined ? roleByCall.get(callKey) : undefined;
|
|
360
|
+
take(rowFromPayload(kind, payload, payload.accepted, fallback), callKey);
|
|
228
361
|
}
|
|
229
362
|
return out;
|
|
230
363
|
}
|
|
231
364
|
|
|
365
|
+
/**
|
|
366
|
+
* All recorded role submissions in ledger order (#836 multi-submit).
|
|
367
|
+
* `accepted` is the original payload; never rebuilt from a status/facts envelope.
|
|
368
|
+
* Presentation stays run-scoped: attemptId on the read scope is a recording tag,
|
|
369
|
+
* not a visibility gate (#836).
|
|
370
|
+
*/
|
|
371
|
+
export async function readRecordedSubmissionRows(
|
|
372
|
+
cwd: string,
|
|
373
|
+
runId: string,
|
|
374
|
+
homeOrScope?: string | SubmissionLedgerReadScope,
|
|
375
|
+
): Promise<readonly RecordedSubmissionRow[]> {
|
|
376
|
+
const scope = resolveReadScope(homeOrScope);
|
|
377
|
+
const { owned } = await readOwnedSubmissionRecords(cwd, runId, scope.home);
|
|
378
|
+
const scoped = recordsForAttempt(owned, scope.attemptId);
|
|
379
|
+
return mapOwnedToSubmissionRows(scoped);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Settlement-only this-court rows (#879 return path).
|
|
384
|
+
* Filters by attemptId for court-scoped roleOutcome; does not replace the
|
|
385
|
+
* run-scoped presentation API above (#836 visibility gate stays pass-through).
|
|
386
|
+
*/
|
|
387
|
+
export async function readAttemptScopedSubmissionRows(
|
|
388
|
+
cwd: string,
|
|
389
|
+
runId: string,
|
|
390
|
+
attemptId: string,
|
|
391
|
+
home?: string,
|
|
392
|
+
): Promise<readonly RecordedSubmissionRow[]> {
|
|
393
|
+
if (attemptId.length === 0) return [];
|
|
394
|
+
const { owned } = await readOwnedSubmissionRecords(cwd, runId, home);
|
|
395
|
+
return mapOwnedToSubmissionRows(owned.filter((record) => recordAttemptId(record) === attemptId));
|
|
396
|
+
}
|
|
397
|
+
|
|
232
398
|
/**
|
|
233
399
|
* All raw role payloads in ledger order (#836 multi-submit).
|
|
234
400
|
* Returns `accepted` bytes as stored — unknown, never re-wrapped.
|
|
@@ -241,7 +407,7 @@ export async function readRecordedSubmissions(
|
|
|
241
407
|
return (await readRecordedSubmissionRows(cwd, runId, homeOrScope)).map((row) => row.accepted);
|
|
242
408
|
}
|
|
243
409
|
|
|
244
|
-
/** True when the run has at least one recorded
|
|
410
|
+
/** True when the run has at least one recorded original payload (any outcome class). */
|
|
245
411
|
export async function hasRecordedSubmission(
|
|
246
412
|
cwd: string,
|
|
247
413
|
runId: string,
|
|
@@ -364,6 +530,7 @@ export function createSubmissionLedgerHost(
|
|
|
364
530
|
toolCallId,
|
|
365
531
|
toolName: tool.name,
|
|
366
532
|
sequence: ++state.sequence,
|
|
533
|
+
role,
|
|
367
534
|
params,
|
|
368
535
|
});
|
|
369
536
|
let result: HostToolResult<unknown>;
|
|
@@ -389,6 +556,7 @@ export function createSubmissionLedgerHost(
|
|
|
389
556
|
outcome: "correctable-rejection",
|
|
390
557
|
code: "typed-bounce",
|
|
391
558
|
diagnostic: error instanceof Error ? error.message : String(error),
|
|
559
|
+
role,
|
|
392
560
|
accepted: params,
|
|
393
561
|
});
|
|
394
562
|
throw error;
|
|
@@ -399,6 +567,7 @@ export function createSubmissionLedgerHost(
|
|
|
399
567
|
toolCallId,
|
|
400
568
|
outcome: "infrastructure",
|
|
401
569
|
diagnostic: error instanceof Error ? error.message : String(error),
|
|
570
|
+
role,
|
|
402
571
|
accepted: params,
|
|
403
572
|
});
|
|
404
573
|
throw error;
|