@akagilnc/pi-workflow-roles 0.1.4035 → 0.1.4193
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 +2368 -1155
- package/dist/archivist-record-entry.js +204 -20
- package/dist/archivist-record-topology.js +36 -16
- package/dist/auditor-dossier-tool.js +2 -1
- package/dist/diarist-contracts.js +96 -0
- package/dist/doctor-auditor.js +1 -1
- package/dist/doctor-evidence.js +3 -2
- 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 +2375 -1149
- package/dist/host-contracts.js +16 -0
- package/dist/inspector-contracts.js +8 -0
- package/dist/migrate-book-topology.js +278917 -0
- package/dist/navigator-public-session.js +22 -25
- package/dist/notary-source-run.js +7 -4
- package/dist/packaged-role-registry.js +1 -1
- package/dist/pi/durable-principal.js +9 -6
- package/dist/pi/role-turn-host.js +43 -13
- package/dist/public-cli/auto-resume.js +2 -5
- package/dist/public-cli/case-dossier-delivery.js +60 -1
- package/dist/public-cli/diarist-run.js +27 -25
- package/dist/public-cli/inspector-run.js +17 -3
- package/dist/public-cli/instruction-seat-run.js +17 -17
- package/dist/public-cli/invocation.js +127 -63
- package/dist/public-cli/main.js +1685 -696
- package/dist/public-cli/notary-run.js +7 -4
- package/dist/public-cli/option-definitions.js +2 -2
- package/dist/public-cli/post-admission.js +113 -71
- package/dist/public-cli/run-lifecycle.js +209 -58
- package/dist/public-cli/seat-ticket-binding.js +3 -6
- package/dist/public-cli/settlement.js +29 -10
- package/dist/public-role-summons.js +8 -0
- package/dist/role-run-placement.js +84 -0
- package/dist/role-run-relocation.js +358 -0
- package/dist/run-terminal-artifacts.js +2 -1
- package/dist/run-ticket-number.js +51 -32
- package/dist/sitian-appender.js +11 -26
- package/dist/submission-ledger.js +106 -25
- package/dist/ticket-provenance.js +3 -1
- package/dist/user-dialogue-stdin.js +33 -0
- package/extensions/role-runtime.ts +1 -0
- package/package.json +3 -2
- package/resources/836-deleted-machine-instruction-inventory.md +1 -1
- package/scripts/build-package.mjs +34 -2
- package/souls/fixer.md +3 -2
- package/src/analyst-ledger.ts +14 -15
- package/src/archivist-record-entry.ts +285 -38
- package/src/archivist-record-topology.ts +43 -18
- package/src/auditor-dossier-tool.ts +2 -1
- package/src/book-topology-migration-placement.ts +493 -0
- package/src/book-topology-migration.ts +220 -0
- package/src/book-topology-migrators-t11.ts +856 -0
- package/src/book-topology-mixed-volume-migrators.ts +470 -0
- package/src/book-topology-partition-migrators.ts +25 -0
- package/src/book-topology-record-class-migrators.ts +779 -0
- package/src/book-topology-runs-migrator.ts +244 -0
- package/src/diarist-contracts.ts +116 -0
- package/src/diarist-role.ts +6 -0
- package/src/diarist.ts +2 -0
- package/src/doctor-auditor.ts +1 -1
- package/src/doctor-evidence.ts +4 -1
- package/src/doctor-role.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 +22 -3
- package/src/inspector-contracts.ts +7 -0
- package/src/judge-role.ts +4 -0
- package/src/navigator-public-session.ts +33 -30
- package/src/navigator-work-context.ts +3 -4
- package/src/notary-role.ts +1 -0
- package/src/notary-source-run.ts +9 -4
- package/src/packaged-role-registry.ts +1 -1
- package/src/pi/adapter.ts +19 -4
- package/src/pi/durable-principal.ts +9 -13
- package/src/pi/role-turn-host.ts +48 -14
- package/src/public-cli/auto-resume.ts +2 -6
- package/src/public-cli/case-dossier-delivery.ts +86 -1
- package/src/public-cli/countersign-run.ts +182 -32
- package/src/public-cli/diarist-run.ts +34 -26
- package/src/public-cli/inspector-run.ts +26 -2
- package/src/public-cli/instruction-seat-run.ts +24 -20
- package/src/public-cli/invocation.ts +194 -58
- package/src/public-cli/notary-run.ts +13 -4
- package/src/public-cli/option-definitions.ts +2 -2
- package/src/public-cli/post-admission.ts +147 -78
- package/src/public-cli/run-lifecycle.ts +258 -65
- package/src/public-cli/seat-ticket-binding.ts +3 -6
- package/src/public-cli/settlement.ts +38 -10
- package/src/public-role-summons.ts +31 -7
- package/src/role-envelope.ts +9 -30
- package/src/role-run-placement.ts +128 -0
- package/src/role-run-relocation.ts +491 -0
- package/src/role-runtime-dependencies.ts +1 -0
- package/src/role-runtime.ts +106 -24
- package/src/run-terminal-artifacts.ts +3 -1
- package/src/run-ticket-number.ts +80 -20
- package/src/sitian-appender.ts +25 -28
- package/src/submission-ledger.ts +148 -30
- package/src/ticket-provenance.ts +4 -1
- package/src/ticket-trajectory.ts +22 -35
- package/src/user-dialogue-stdin.ts +37 -0
- package/src/worker-role.ts +8 -4
- package/src/worker-submission-gates.ts +6 -6
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,
|
|
@@ -414,7 +422,7 @@ export type { MergerRoleDependencies } from "./merger-role.ts";
|
|
|
414
422
|
|
|
415
423
|
type WorkerArmable = {
|
|
416
424
|
activate(context?: HostContext): Promise<void>;
|
|
417
|
-
armSubmissionGate(cwd: string, parent
|
|
425
|
+
armSubmissionGate(cwd: string, parent: { getSessionFile(): string | undefined }): void;
|
|
418
426
|
};
|
|
419
427
|
|
|
420
428
|
type ActivationRuntime = {
|
|
@@ -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) {
|
|
@@ -964,6 +974,7 @@ export function createDiaristRoleRuntime(
|
|
|
964
974
|
await commitDiaristEntries({
|
|
965
975
|
ticketNumber,
|
|
966
976
|
cwd: coords.projectRoot,
|
|
977
|
+
sessionParent: join(coords.runDirectory, "session", "session.jsonl"),
|
|
967
978
|
home: coords.home,
|
|
968
979
|
entries: projectDiaristEntries(parameters),
|
|
969
980
|
});
|
|
@@ -1020,6 +1031,8 @@ export function createCountersignRoleRuntime(
|
|
|
1020
1031
|
...(signal === undefined ? {} : { signal }),
|
|
1021
1032
|
hostActions,
|
|
1022
1033
|
toolCallId,
|
|
1034
|
+
// #879: this-turn typed payload — identity-bound at submit site.
|
|
1035
|
+
submission: parameters,
|
|
1023
1036
|
});
|
|
1024
1037
|
}
|
|
1025
1038
|
: undefined;
|
|
@@ -1057,6 +1070,10 @@ export function createRoleRuntimeExtension(
|
|
|
1057
1070
|
for (const flag of NOTARY_TRANSPORT_FLAGS) {
|
|
1058
1071
|
roleHost.registerFlag(flag.name, flag.definition);
|
|
1059
1072
|
}
|
|
1073
|
+
roleHost.registerFlag(
|
|
1074
|
+
INSPECTOR_SOURCE_RUN_FLAG.name,
|
|
1075
|
+
INSPECTOR_SOURCE_RUN_FLAG.definition,
|
|
1076
|
+
);
|
|
1060
1077
|
for (const flag of GLEANER_LEFT_TRANSPORT_FLAGS) {
|
|
1061
1078
|
roleHost.registerFlag(flag.name, flag.definition);
|
|
1062
1079
|
}
|
|
@@ -1066,6 +1083,13 @@ export function createRoleRuntimeExtension(
|
|
|
1066
1083
|
}
|
|
1067
1084
|
// Station-child identity (#840): omit navigator attendance. One flag.
|
|
1068
1085
|
roleHost.registerFlag(STATION_CHILD_FLAG.name, STATION_CHILD_FLAG.definition);
|
|
1086
|
+
// Register model only. Pi never sets ak-engine — resolveEngineName must
|
|
1087
|
+
// fall through to child-process env. An empty default would block that.
|
|
1088
|
+
roleHost.registerFlag(ENGINE_MODEL_FLAG_NAME, {
|
|
1089
|
+
description: "本次劳务引擎模型",
|
|
1090
|
+
type: "string",
|
|
1091
|
+
default: "",
|
|
1092
|
+
});
|
|
1069
1093
|
|
|
1070
1094
|
let admitted = false;
|
|
1071
1095
|
let selectedRole: string | undefined;
|
|
@@ -1079,6 +1103,8 @@ export function createRoleRuntimeExtension(
|
|
|
1079
1103
|
let pendingNavigatorPresentation: { event: import("./navigator-attendance.ts").NavigatorEvent; report: import("./navigator-attendance.ts").NavigatorReport } | undefined;
|
|
1080
1104
|
let pendingNavigatorSettlement: Promise<void> | undefined;
|
|
1081
1105
|
let navigatorWorkContext: NavigatorWorkContext | undefined;
|
|
1106
|
+
let navigatorSessionParent: string | undefined;
|
|
1107
|
+
let navigatorCwd: string | undefined;
|
|
1082
1108
|
/** toolCallId → fact+evidence; one-shot projected onto durable tool_result (#475). */
|
|
1083
1109
|
const pendingInfrastructureFailures = new Map<string, PendingInfrastructureFailure>();
|
|
1084
1110
|
// Envelope-owned execute→tool_result bridge for submission non-pass (ADR 0018 / #525).
|
|
@@ -1134,18 +1160,19 @@ export function createRoleRuntimeExtension(
|
|
|
1134
1160
|
void Promise.resolve(attendance.dispose()).then(
|
|
1135
1161
|
undefined,
|
|
1136
1162
|
(error) => {
|
|
1163
|
+
const diagnostic = error instanceof Error ? error.message : String(error);
|
|
1137
1164
|
try {
|
|
1138
1165
|
sitianReport({
|
|
1139
1166
|
level: "event",
|
|
1140
1167
|
kind: "navigator-dispose-failure",
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
},
|
|
1168
|
+
cwd: navigatorCwd,
|
|
1169
|
+
sessionParent: navigatorSessionParent,
|
|
1170
|
+
payload: { diagnostic },
|
|
1144
1171
|
source: "role-runtime",
|
|
1145
1172
|
});
|
|
1146
1173
|
} catch (recordError) {
|
|
1147
1174
|
envelopeHost.appendEntry?.("ak-navigator-dispose-failure", {
|
|
1148
|
-
diagnostic
|
|
1175
|
+
diagnostic,
|
|
1149
1176
|
recordFailure: recordError instanceof Error ? recordError.message : String(recordError),
|
|
1150
1177
|
});
|
|
1151
1178
|
}
|
|
@@ -1163,6 +1190,7 @@ export function createRoleRuntimeExtension(
|
|
|
1163
1190
|
settleNavigatorProjection,
|
|
1164
1191
|
);
|
|
1165
1192
|
roleHost.on("input", (event) => {
|
|
1193
|
+
const text = event.text;
|
|
1166
1194
|
const role = roleHost.getFlag(ROLE_FLAG.name);
|
|
1167
1195
|
if (role !== undefined && !admitted) return { action: "handled" as const };
|
|
1168
1196
|
// Reviewer: recover original request; Pi argv may already carry native form.
|
|
@@ -1175,14 +1203,19 @@ export function createRoleRuntimeExtension(
|
|
|
1175
1203
|
reviewerOriginalRequest =
|
|
1176
1204
|
roleHost.capabilities?.skillOriginalRequest?.(
|
|
1177
1205
|
activeReviewerParent.skillBinding.name,
|
|
1178
|
-
|
|
1179
|
-
) ??
|
|
1180
|
-
return
|
|
1206
|
+
text,
|
|
1207
|
+
) ?? text;
|
|
1208
|
+
return text === event.text
|
|
1209
|
+
? { action: "continue" as const }
|
|
1210
|
+
: { action: "transform" as const, text };
|
|
1181
1211
|
}
|
|
1182
|
-
return
|
|
1212
|
+
return text === event.text
|
|
1213
|
+
? { action: "continue" as const }
|
|
1214
|
+
: { action: "transform" as const, text };
|
|
1183
1215
|
});
|
|
1184
1216
|
roleHost.on("before_agent_start", async (event, ctx) => {
|
|
1185
1217
|
const role = roleHost.getFlag(ROLE_FLAG.name);
|
|
1218
|
+
const prompt = event.prompt;
|
|
1186
1219
|
if (role === undefined) return;
|
|
1187
1220
|
if (!admitted || selectedRole !== role) {
|
|
1188
1221
|
failInfrastructure(new ActivationBarrierError(role), ctx);
|
|
@@ -1207,7 +1240,7 @@ export function createRoleRuntimeExtension(
|
|
|
1207
1240
|
// output payload (#836 targets the latter): the human's own raw
|
|
1208
1241
|
// prompt bytes, copied verbatim into Navigator's work context when
|
|
1209
1242
|
// nothing else has supplied a subject yet.
|
|
1210
|
-
const subject =
|
|
1243
|
+
const subject = prompt.trim();
|
|
1211
1244
|
if (subject !== "") {
|
|
1212
1245
|
const root = subjectPath(ctx.sessionManager.getSessionDir(), ctx.cwd);
|
|
1213
1246
|
const subjectProvenance = "user_prompt" satisfies NavigatorSubjectProvenance;
|
|
@@ -1231,7 +1264,7 @@ export function createRoleRuntimeExtension(
|
|
|
1231
1264
|
if (!reviewerExpansionCaptured) {
|
|
1232
1265
|
if (reviewerOriginalRequest !== undefined) {
|
|
1233
1266
|
activeReviewerParent.skillBinding.captureExpansion(
|
|
1234
|
-
roleHost.capabilities?.skillExpansion(
|
|
1267
|
+
roleHost.capabilities?.skillExpansion(prompt),
|
|
1235
1268
|
reviewerOriginalRequest,
|
|
1236
1269
|
);
|
|
1237
1270
|
}
|
|
@@ -1255,6 +1288,54 @@ export function createRoleRuntimeExtension(
|
|
|
1255
1288
|
};
|
|
1256
1289
|
}
|
|
1257
1290
|
});
|
|
1291
|
+
// #879: engine coordinates ride readingMaterial only when station-child
|
|
1292
|
+
// officer dialogue replaced the ordinary transport prompt (no double fold).
|
|
1293
|
+
roleHost.on("before_agent_start", () => {
|
|
1294
|
+
const role = selectedRole ?? roleHost.getFlag(ROLE_FLAG.name);
|
|
1295
|
+
if (typeof role !== "string" || !isOfficerReviewSeat(role)) return;
|
|
1296
|
+
if (roleHost.getFlag(STATION_CHILD_FLAG.name) !== true) return;
|
|
1297
|
+
const engine = resolveEngineName((name) => roleHost.getFlag(name));
|
|
1298
|
+
if (engine === undefined || dependencies.packageRoot === undefined) return;
|
|
1299
|
+
const engineModel = resolveEngineModel((name) => roleHost.getFlag(name));
|
|
1300
|
+
const material = engineSessionMaterialFromOptions({
|
|
1301
|
+
engine,
|
|
1302
|
+
...(engineModel === undefined ? {} : { engineModel }),
|
|
1303
|
+
packageRoot: dependencies.packageRoot,
|
|
1304
|
+
});
|
|
1305
|
+
if (material === undefined) return;
|
|
1306
|
+
return {
|
|
1307
|
+
readingMaterial: {
|
|
1308
|
+
kind: "engine-session-material" as const,
|
|
1309
|
+
name: material.name,
|
|
1310
|
+
...(material.model === undefined ? {} : { model: material.model }),
|
|
1311
|
+
...(material.materialPath === undefined ? {} : { materialPath: material.materialPath }),
|
|
1312
|
+
},
|
|
1313
|
+
};
|
|
1314
|
+
});
|
|
1315
|
+
roleHost.on("before_agent_start", () => {
|
|
1316
|
+
const path = roleHost.getFlag(INSPECTOR_SOURCE_RUN_FLAG.name);
|
|
1317
|
+
if (typeof path !== "string" || path.trim() === "") return;
|
|
1318
|
+
return {
|
|
1319
|
+
readingMaterial: {
|
|
1320
|
+
kind: "inspector-parent-binding" as const,
|
|
1321
|
+
sourceRunPath: path,
|
|
1322
|
+
},
|
|
1323
|
+
};
|
|
1324
|
+
});
|
|
1325
|
+
// #879: single shared owner of station-child 0081 case-dossier → readingMaterial fold.
|
|
1326
|
+
// post-admission freezes under run/attachments/case-dossier/; this handler alone
|
|
1327
|
+
// projects it onto the existing agent-start materials face (Pi + envelope collect).
|
|
1328
|
+
// Role modules must not re-read the freeze (ADR 0018 lifecycle; no duplicate fold).
|
|
1329
|
+
roleHost.on("before_agent_start", async (_event, ctx) => {
|
|
1330
|
+
const runDir = runDirectoryFromHostContext(ctx);
|
|
1331
|
+
if (runDir === undefined) return;
|
|
1332
|
+
const { loadCaseDossierReadingMaterial } = await import(
|
|
1333
|
+
"./public-cli/case-dossier-delivery.ts"
|
|
1334
|
+
);
|
|
1335
|
+
const caseDossier = await loadCaseDossierReadingMaterial(runDir);
|
|
1336
|
+
if (caseDossier === undefined) return;
|
|
1337
|
+
return { readingMaterial: caseDossier };
|
|
1338
|
+
});
|
|
1258
1339
|
roleHost.on("tool_result", async (event) => {
|
|
1259
1340
|
const role = selectedRole;
|
|
1260
1341
|
if (role === undefined) return;
|
|
@@ -1341,7 +1422,7 @@ export function createRoleRuntimeExtension(
|
|
|
1341
1422
|
display: false,
|
|
1342
1423
|
}, { triggerTurn: true, deliverAs: "followUp" });
|
|
1343
1424
|
} else if (receiptDelivery.nextAction() === "no-receipt" && !noReceiptRecorded) {
|
|
1344
|
-
const runPointer =
|
|
1425
|
+
const runPointer = runDirectoryFromHostContext(ctx);
|
|
1345
1426
|
if (runPointer !== undefined) {
|
|
1346
1427
|
noReceiptRecorded = true;
|
|
1347
1428
|
const facts = receiptDelivery.facts({ runPointer, attemptPointer: `current:${runPointer}` });
|
|
@@ -1750,8 +1831,8 @@ export function createRoleRuntimeExtension(
|
|
|
1750
1831
|
provider: string,
|
|
1751
1832
|
ctx: HostContext,
|
|
1752
1833
|
): Promise<void> => {
|
|
1753
|
-
const runDir =
|
|
1754
|
-
if (
|
|
1834
|
+
const runDir = runDirectoryFromHostContext(ctx);
|
|
1835
|
+
if (runDir === undefined) return;
|
|
1755
1836
|
try {
|
|
1756
1837
|
await recordTypedProviderHttpStatus(runDir, { httpStatus: status, provider });
|
|
1757
1838
|
} catch (error) {
|
|
@@ -1785,10 +1866,9 @@ export function createRoleRuntimeExtension(
|
|
|
1785
1866
|
const underlying = priorFetch;
|
|
1786
1867
|
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
1787
1868
|
const response = await underlying(input, init);
|
|
1788
|
-
const runDir =
|
|
1869
|
+
const runDir = runDirectoryFromHostContext(ctx);
|
|
1789
1870
|
if (
|
|
1790
|
-
|
|
1791
|
-
&& runDir.trim() !== ""
|
|
1871
|
+
runDir !== undefined
|
|
1792
1872
|
&& typeof response?.status === "number"
|
|
1793
1873
|
&& (response.status < 200 || response.status >= 300)
|
|
1794
1874
|
) {
|
|
@@ -1887,6 +1967,8 @@ export function createRoleRuntimeExtension(
|
|
|
1887
1967
|
&& entry.role !== "navigator"
|
|
1888
1968
|
&& !isStationChild
|
|
1889
1969
|
) {
|
|
1970
|
+
navigatorSessionParent = ctx.sessionManager.getSessionFile();
|
|
1971
|
+
navigatorCwd = ctx.cwd;
|
|
1890
1972
|
let work: NavigatorWorkContext;
|
|
1891
1973
|
let contextError: unknown;
|
|
1892
1974
|
if (dependencies.loadNavigatorWorkContext === undefined) {
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
import { readdir, readFile } from "node:fs/promises";
|
|
10
10
|
import { basename, dirname, join } from "node:path";
|
|
11
11
|
|
|
12
|
+
import { roleRunArtifactsDirectory } from "./role-run-placement.ts";
|
|
13
|
+
|
|
12
14
|
export const RUN_TERMINAL_ARTIFACT_FILES = [
|
|
13
15
|
"report.json",
|
|
14
16
|
"error.json",
|
|
@@ -191,7 +193,7 @@ function presentUniqueFallbackBoundToRun(
|
|
|
191
193
|
export async function readRunTerminalArtifact(
|
|
192
194
|
runDirectory: string,
|
|
193
195
|
): Promise<RunTerminalArtifactRead> {
|
|
194
|
-
const artifactsDir =
|
|
196
|
+
const artifactsDir = roleRunArtifactsDirectory(runDirectory);
|
|
195
197
|
for (const file of RUN_TERMINAL_ARTIFACT_FILES) {
|
|
196
198
|
const path = join(artifactsDir, file);
|
|
197
199
|
const read = await readTerminalArtifactAtPath(path, file);
|
package/src/run-ticket-number.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Typed ticketNumber reader for a retained run's durable pages.
|
|
3
|
-
* admitted-request
|
|
4
|
-
*
|
|
3
|
+
* Board pages first (admitted-request, then invocation); migration derivation
|
|
4
|
+
* page last so worktree-derived placement stays readable without forging a
|
|
5
|
+
* board assertion. Missing page (ENOENT) → try next / undefined; damage and
|
|
6
|
+
* non-ENOENT IO failures propagate.
|
|
5
7
|
*/
|
|
6
8
|
import { readFile } from "node:fs/promises";
|
|
7
9
|
import { join } from "node:path";
|
|
8
10
|
|
|
11
|
+
/** Sole on-disk page for worktree-basename ticket derivation (#865). */
|
|
12
|
+
export const MIGRATION_TICKET_DERIVATION_PAGE =
|
|
13
|
+
"migration-ticket-derivation.json" as const;
|
|
14
|
+
|
|
9
15
|
function isEnoent(error: unknown): boolean {
|
|
10
16
|
return (
|
|
11
17
|
error instanceof Error &&
|
|
@@ -14,40 +20,94 @@ function isEnoent(error: unknown): boolean {
|
|
|
14
20
|
);
|
|
15
21
|
}
|
|
16
22
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
/** Sole safe-positive ticket invariant (bind / admission / placement / readers). */
|
|
24
|
+
export function isSafePositiveTicketNumber(value: unknown): value is number {
|
|
25
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Loud reject for non-safe-positive ticket numbers before placement or bind. */
|
|
29
|
+
export function requireSafePositiveTicketNumber(
|
|
30
|
+
ticketNumber: number,
|
|
31
|
+
context = "ticketNumber",
|
|
32
|
+
): number {
|
|
33
|
+
if (!isSafePositiveTicketNumber(ticketNumber)) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`${context} requires a safe positive integer, got ${String(ticketNumber)}`,
|
|
36
|
+
);
|
|
25
37
|
}
|
|
26
|
-
return
|
|
38
|
+
return ticketNumber;
|
|
27
39
|
}
|
|
28
40
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
41
|
+
function ticketFromRecord(record: Record<string, unknown>): number | undefined {
|
|
42
|
+
return isSafePositiveTicketNumber(record.ticketNumber)
|
|
43
|
+
? record.ticketNumber
|
|
44
|
+
: undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function readJsonObject(
|
|
48
|
+
path: string,
|
|
49
|
+
): Promise<Record<string, unknown> | undefined> {
|
|
33
50
|
try {
|
|
34
|
-
const raw = JSON.parse(await readFile(
|
|
51
|
+
const raw: unknown = JSON.parse(await readFile(path, "utf8"));
|
|
35
52
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
36
53
|
return undefined;
|
|
37
54
|
}
|
|
38
|
-
return
|
|
55
|
+
return raw as Record<string, unknown>;
|
|
39
56
|
} catch (error) {
|
|
40
57
|
if (isEnoent(error)) return undefined;
|
|
41
58
|
throw error;
|
|
42
59
|
}
|
|
43
60
|
}
|
|
44
61
|
|
|
45
|
-
|
|
62
|
+
async function readBoardPageTicketNumber(
|
|
63
|
+
runDirectory: string,
|
|
64
|
+
page: "admitted-request.json" | "invocation.json",
|
|
65
|
+
): Promise<number | undefined> {
|
|
66
|
+
const record = await readJsonObject(join(runDirectory, page));
|
|
67
|
+
if (record === undefined) return undefined;
|
|
68
|
+
return ticketFromRecord(record);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Board-only ticketNumber (admitted → invocation). Placement attribution and
|
|
73
|
+
* any caller that must ignore derivation pages use this sole projection.
|
|
74
|
+
*/
|
|
75
|
+
export async function readBoardTicketNumber(
|
|
76
|
+
runDirectory: string,
|
|
77
|
+
): Promise<number | undefined> {
|
|
78
|
+
return (
|
|
79
|
+
(await readBoardPageTicketNumber(runDirectory, "admitted-request.json")) ??
|
|
80
|
+
(await readBoardPageTicketNumber(runDirectory, "invocation.json"))
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Worktree-derivation ticket only — never a board assertion. Callers that must
|
|
86
|
+
* distinguish board vs derived use this; effective readers use readRunTicketNumber.
|
|
87
|
+
*/
|
|
88
|
+
export async function readMigrationDerivedTicketNumber(
|
|
89
|
+
runDirectory: string,
|
|
90
|
+
): Promise<number | undefined> {
|
|
91
|
+
const record = await readJsonObject(
|
|
92
|
+
join(runDirectory, MIGRATION_TICKET_DERIVATION_PAGE),
|
|
93
|
+
);
|
|
94
|
+
if (record === undefined) return undefined;
|
|
95
|
+
if (record.derivation !== "worktree-path-basename") return undefined;
|
|
96
|
+
return ticketFromRecord(record);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Display / historical-placement ticketNumber for a retained run:
|
|
101
|
+
* board admitted → board invocation → migration derivation.
|
|
102
|
+
* Derivation is last so board wins. Callers that mint, resume, inherit, or
|
|
103
|
+
* otherwise write ticket identity must use readBoardTicketNumber instead —
|
|
104
|
+
* derived placement is never a board assertion.
|
|
105
|
+
*/
|
|
46
106
|
export async function readRunTicketNumber(
|
|
47
107
|
runDirectory: string,
|
|
48
108
|
): Promise<number | undefined> {
|
|
49
109
|
return (
|
|
50
|
-
(await
|
|
51
|
-
(await
|
|
110
|
+
(await readBoardTicketNumber(runDirectory)) ??
|
|
111
|
+
(await readMigrationDerivedTicketNumber(runDirectory))
|
|
52
112
|
);
|
|
53
113
|
}
|
package/src/sitian-appender.ts
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
unlinkSync,
|
|
12
12
|
writeFileSync,
|
|
13
13
|
} from "node:fs";
|
|
14
|
-
import {
|
|
14
|
+
import { dirname, join } from "node:path";
|
|
15
15
|
|
|
16
16
|
import { resolveBookKeyFromGit } from "./activation-ledger-git.ts";
|
|
17
17
|
import {
|
|
@@ -204,14 +204,6 @@ export function resolveSitianVolumeCategory(kind: string): string {
|
|
|
204
204
|
return kind;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
function safeBookKey(cwd: string): string {
|
|
208
|
-
try {
|
|
209
|
-
return resolveBookKeyFromGit(cwd);
|
|
210
|
-
} catch {
|
|
211
|
-
return basename(resolve(cwd)) || "default";
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
207
|
type SitianRecordPath = {
|
|
216
208
|
readonly sessionDir: string;
|
|
217
209
|
readonly recordFile: string;
|
|
@@ -223,30 +215,35 @@ export function resolveSitianRecordPathInLedger(
|
|
|
223
215
|
input: SitianRecordInput,
|
|
224
216
|
ledgerHome: string,
|
|
225
217
|
): SitianRecordPath {
|
|
226
|
-
const cwd = input.cwd ?? process.cwd();
|
|
227
218
|
const category = resolveSitianVolumeCategory(input.kind);
|
|
219
|
+
const ticketNumber = category === "ticket-provenance"
|
|
220
|
+
? typeof input.subject === "string" && /^[1-9][0-9]*$/.test(input.subject)
|
|
221
|
+
? input.subject
|
|
222
|
+
: typeof input.subject === "object"
|
|
223
|
+
&& typeof input.subject.ticketNumber === "number"
|
|
224
|
+
&& Number.isSafeInteger(input.subject.ticketNumber)
|
|
225
|
+
&& input.subject.ticketNumber > 0
|
|
226
|
+
? String(input.subject.ticketNumber)
|
|
227
|
+
: undefined
|
|
228
|
+
: undefined;
|
|
228
229
|
|
|
229
230
|
let sessionDir: string;
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
231
|
+
if (ticketNumber !== undefined) {
|
|
232
|
+
// docs/dossier-topology.md: ticket dir holds records.jsonl + 起居录.md directly.
|
|
233
|
+
const bookDir = activationBookDirectory(
|
|
234
|
+
ledgerHome,
|
|
235
|
+
resolveBookKeyFromGit(input.cwd ?? process.cwd()),
|
|
236
|
+
);
|
|
237
|
+
sessionDir = join(bookDir, ticketNumber);
|
|
233
238
|
} else {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
} else if (typeof input.subject.runId === "string" && input.subject.runId.length > 0) {
|
|
241
|
-
subjectStr = input.subject.runId;
|
|
242
|
-
} else {
|
|
243
|
-
subjectStr = JSON.stringify(input.subject);
|
|
244
|
-
}
|
|
245
|
-
const digest = createHash("sha256").update(subjectStr).digest("hex").slice(0, 32);
|
|
246
|
-
sessionDir = join(bookDir, category, digest);
|
|
247
|
-
} else {
|
|
248
|
-
sessionDir = join(bookDir, category);
|
|
239
|
+
if (
|
|
240
|
+
input.sessionParent === undefined
|
|
241
|
+
|| input.sessionParent.length === 0
|
|
242
|
+
|| !physicallyContainedIn(ledgerHome, input.sessionParent)
|
|
243
|
+
) {
|
|
244
|
+
throw new Error("Sitian record ownership requires a parent session inside the ledger home");
|
|
249
245
|
}
|
|
246
|
+
sessionDir = join(dirname(input.sessionParent), category);
|
|
250
247
|
}
|
|
251
248
|
|
|
252
249
|
const recordFile = join(sessionDir, "records.jsonl");
|