@akagilnc/pi-workflow-roles 0.1.4035 → 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 +476 -250
- 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 +503 -264
- package/dist/host-contracts.js +16 -0
- package/dist/inspector-contracts.js +8 -0
- package/dist/packaged-role-registry.js +1 -1
- package/dist/pi/role-turn-host.js +43 -13
- package/dist/public-cli/case-dossier-delivery.js +60 -1
- package/dist/public-cli/inspector-run.js +17 -3
- package/dist/public-cli/instruction-seat-run.js +9 -6
- package/dist/public-cli/main.js +239 -112
- 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 +24 -7
- package/dist/public-role-summons.js +8 -0
- package/dist/submission-ledger.js +30 -8
- 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 +22 -3
- package/src/inspector-contracts.ts +7 -0
- package/src/judge-role.ts +4 -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/role-turn-host.ts +48 -14
- package/src/public-cli/case-dossier-delivery.ts +86 -1
- package/src/public-cli/inspector-run.ts +26 -2
- package/src/public-cli/instruction-seat-run.ts +17 -9
- package/src/public-cli/invocation.ts +2 -0
- package/src/public-cli/notary-run.ts +13 -4
- package/src/public-cli/post-admission.ts +74 -65
- package/src/public-cli/run-lifecycle.ts +3 -0
- package/src/public-cli/settlement.ts +32 -6
- 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 +48 -13
- package/src/user-dialogue-stdin.ts +37 -0
- package/src/worker-role.ts +4 -0
|
@@ -15,6 +15,7 @@ import { readSitianRecords, resolveSitianRecordPath, sitianReport } from "../sit
|
|
|
15
15
|
|
|
16
16
|
import {
|
|
17
17
|
hasFreshAttemptSubmission,
|
|
18
|
+
readAttemptScopedSubmissionRows,
|
|
18
19
|
readRecordedSubmissionRows,
|
|
19
20
|
readRecordedSubmissions,
|
|
20
21
|
} from "../submission-ledger.ts";
|
|
@@ -198,6 +199,22 @@ async function sealedLedgerOutcome(
|
|
|
198
199
|
role: TerminalRoleName,
|
|
199
200
|
scope?: SettlementCourtScope,
|
|
200
201
|
): Promise<Extract<TerminalRoleOutcome, { kind: "accepted" | "audit_escalation" }> | undefined> {
|
|
202
|
+
const home = sealedLedgerHome(admitted);
|
|
203
|
+
// #879: when court scope is present, roleOutcome is this-court original only —
|
|
204
|
+
// never last-wins over an undivided historical array, and never falls back to
|
|
205
|
+
// full-run history when this court sealed zero rows. #836 presentation of full
|
|
206
|
+
// history stays on attachRecordedSubmissions / terminal.submissions (run-scoped).
|
|
207
|
+
if (scope?.courtAttemptId !== undefined && scope.courtAttemptId.length > 0) {
|
|
208
|
+
const thisCourt = await readAttemptScopedSubmissionRows(
|
|
209
|
+
admitted.projectRoot,
|
|
210
|
+
admitted.runId,
|
|
211
|
+
scope.courtAttemptId,
|
|
212
|
+
home,
|
|
213
|
+
);
|
|
214
|
+
// Scope present + zero this-court rows → no this-court outcome (not run history).
|
|
215
|
+
return roleOutcomeFromRows(role, thisCourt);
|
|
216
|
+
}
|
|
217
|
+
// No court scope: keep run-scoped ledger face (#836).
|
|
201
218
|
const rows = await readRecordedSubmissionRows(
|
|
202
219
|
admitted.projectRoot,
|
|
203
220
|
admitted.runId,
|
|
@@ -247,12 +264,14 @@ export function withSubmissions<T extends TerminalResult>(
|
|
|
247
264
|
): T {
|
|
248
265
|
if (submissions.length === 0) return terminal;
|
|
249
266
|
const roleOutcome = terminal.roleOutcome;
|
|
250
|
-
//
|
|
251
|
-
//
|
|
267
|
+
// #879 keeps an already scoped this-court result; #881 full run history is
|
|
268
|
+
// presented independently on terminal.submissions. Only fill a missing result payload.
|
|
252
269
|
const withPayloads =
|
|
253
|
-
roleOutcome.kind === "accepted" || roleOutcome.kind === "audit_escalation"
|
|
254
|
-
? { ...roleOutcome, payloads: submissions }
|
|
255
|
-
: roleOutcome
|
|
270
|
+
roleOutcome.kind === "accepted" || roleOutcome.kind === "audit_escalation"
|
|
271
|
+
? { ...roleOutcome, payloads: (roleOutcome.payloads ?? []).length > 0 ? roleOutcome.payloads : submissions }
|
|
272
|
+
: roleOutcome.kind === "failure"
|
|
273
|
+
? { ...roleOutcome, payloads: roleOutcome.payloads ?? submissions }
|
|
274
|
+
: roleOutcome;
|
|
256
275
|
return { ...terminal, roleOutcome: withPayloads, submissions };
|
|
257
276
|
}
|
|
258
277
|
|
|
@@ -262,7 +281,14 @@ export async function attachRecordedSubmissions<T extends TerminalResult>(
|
|
|
262
281
|
terminal: T,
|
|
263
282
|
scope?: SettlementCourtScope,
|
|
264
283
|
): Promise<T> {
|
|
265
|
-
|
|
284
|
+
// #836 / #879: submissions presentation is always run-scoped history. Do not
|
|
285
|
+
// pass courtAttemptId here — roleOutcome already carries this-court payloads
|
|
286
|
+
// from sealedLedgerOutcome when scoped; withSubmissions keeps them.
|
|
287
|
+
void scope;
|
|
288
|
+
return withSubmissions(
|
|
289
|
+
terminal,
|
|
290
|
+
await recordedSubmissionPayloads(admitted, undefined),
|
|
291
|
+
);
|
|
266
292
|
}
|
|
267
293
|
|
|
268
294
|
/**
|
|
@@ -62,6 +62,12 @@ export type PublicSummonRequest = {
|
|
|
62
62
|
* summons.instruction. Never folded into argv / parent-run lookup keys.
|
|
63
63
|
*/
|
|
64
64
|
readonly reviewReask?: string;
|
|
65
|
+
/**
|
|
66
|
+
* #879 same-parent (and first-mint) gate summons: verbatim parent-submission body.
|
|
67
|
+
* Rides summons.instruction / first-mint prompt when reviewReask is absent.
|
|
68
|
+
* Never folded into argv / parent-run lookup keys (binding pointer stays pure).
|
|
69
|
+
*/
|
|
70
|
+
readonly gateReviewInstruction?: string;
|
|
65
71
|
/**
|
|
66
72
|
* Pi-adapter inject (tests). Used only as the `pi` row when composing the
|
|
67
73
|
* adapter table — never as an override of the child seat's selected host.
|
|
@@ -302,6 +308,10 @@ export async function summonPublicRole(
|
|
|
302
308
|
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
303
309
|
// #753: reask rides the existing notary same-ticket resume summons.instruction.
|
|
304
310
|
...(options.reviewReask === undefined ? {} : { reviewReask: options.reviewReask }),
|
|
311
|
+
// #879: verbatim submission body on officer dialogue content channel.
|
|
312
|
+
...(options.gateReviewInstruction === undefined
|
|
313
|
+
? {}
|
|
314
|
+
: { gateReviewInstruction: options.gateReviewInstruction }),
|
|
305
315
|
...(options.boundTicketNumber === undefined
|
|
306
316
|
? {}
|
|
307
317
|
: { boundTicketNumber: options.boundTicketNumber }),
|
|
@@ -430,6 +440,12 @@ export async function summonGateOfficer(options: {
|
|
|
430
440
|
* Never concatenated into argv (inspector parentRunPath is the pure 卷宗指针).
|
|
431
441
|
*/
|
|
432
442
|
readonly reask?: string;
|
|
443
|
+
/**
|
|
444
|
+
* In-flight parent 交卷 body (tool-call arguments). Relayed verbatim as officer
|
|
445
|
+
* dialogue content when reask is absent (#879). Binding pointer stays on
|
|
446
|
+
* --source-run / 卷宗指针 argv — never a content substitute.
|
|
447
|
+
*/
|
|
448
|
+
readonly submission?: unknown;
|
|
433
449
|
/** Pi-adapter inject — forwarded to summonPublicRole (not a parent-host override). */
|
|
434
450
|
readonly roleTurnHost?: RoleTurnHost;
|
|
435
451
|
readonly hostAdapters?: readonly NamedRoleTurnHostAdapter[];
|
|
@@ -444,8 +460,13 @@ export async function summonGateOfficer(options: {
|
|
|
444
460
|
}
|
|
445
461
|
// Officer host is seat-owned only (#821). Parent invocation.json.host stays a
|
|
446
462
|
// hostTransition consumer in post-admission — not an officer override channel.
|
|
447
|
-
// #
|
|
448
|
-
//
|
|
463
|
+
// #879: binding pointer = parent run directory; dialogue content = submission body.
|
|
464
|
+
// Conclusion re-ask keeps sole ownership of reviewReask when present.
|
|
465
|
+
let gateReviewInstruction: string | undefined;
|
|
466
|
+
if (options.reask === undefined && options.submission !== undefined) {
|
|
467
|
+
const { readableGateItem } = await import("./readable-gate-item.ts");
|
|
468
|
+
gateReviewInstruction = readableGateItem(options.submission);
|
|
469
|
+
}
|
|
449
470
|
const common = {
|
|
450
471
|
cwd: options.cwd,
|
|
451
472
|
...(home === undefined ? {} : { home }),
|
|
@@ -453,13 +474,16 @@ export async function summonGateOfficer(options: {
|
|
|
453
474
|
...(options.io === undefined ? {} : { io: options.io }),
|
|
454
475
|
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
455
476
|
...(options.reask === undefined ? {} : { reviewReask: options.reask }),
|
|
477
|
+
...(gateReviewInstruction === undefined
|
|
478
|
+
? {}
|
|
479
|
+
: { gateReviewInstruction }),
|
|
456
480
|
...(options.roleTurnHost === undefined ? {} : { roleTurnHost: options.roleTurnHost }),
|
|
457
481
|
...(options.hostAdapters === undefined ? {} : { hostAdapters: options.hostAdapters }),
|
|
458
482
|
...(options.createRunId === undefined ? {} : { createRunId: options.createRunId }),
|
|
459
483
|
} as const;
|
|
460
484
|
if (options.officer === "notary") {
|
|
461
|
-
// #753: reask
|
|
462
|
-
//
|
|
485
|
+
// #753/#879: reask or verbatim body rides summons.instruction / first-mint prompt.
|
|
486
|
+
// Binding stays --source-run (never folded into content).
|
|
463
487
|
return summonPublicRole({
|
|
464
488
|
role: "notary",
|
|
465
489
|
argv: ["--source-run", options.sourceRunDirectory, "--project", options.cwd],
|
|
@@ -468,7 +492,7 @@ export async function summonGateOfficer(options: {
|
|
|
468
492
|
}
|
|
469
493
|
if (options.officer === "auditor") {
|
|
470
494
|
// #756: judge compliance path — same queue law as notary/inspector.
|
|
471
|
-
//
|
|
495
|
+
// Binding pointer on argv; dialogue content rides gateReviewInstruction.
|
|
472
496
|
return summonPublicRole({
|
|
473
497
|
role: "auditor",
|
|
474
498
|
argv: [
|
|
@@ -481,8 +505,8 @@ export async function summonGateOfficer(options: {
|
|
|
481
505
|
...common,
|
|
482
506
|
});
|
|
483
507
|
}
|
|
484
|
-
// Inspector: argv stays pure 卷宗指针 (#747 parentRunPath
|
|
485
|
-
//
|
|
508
|
+
// Inspector: argv stays pure 卷宗指针 (#747 parentRunPath lookup key).
|
|
509
|
+
// Content (body/reask) rides env → summons.instruction / first-mint prompt.
|
|
486
510
|
return summonPublicRole({
|
|
487
511
|
role: "inspector",
|
|
488
512
|
argv: [`卷宗指针:${options.sourceRunDirectory}`],
|
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
|
|
|
@@ -57,8 +63,8 @@ export type SubmissionLedgerEvent =
|
|
|
57
63
|
* otherwise session header id. Never a shared "unbound" bucket.
|
|
58
64
|
*/
|
|
59
65
|
function runIdentity(context: HostContext): string {
|
|
60
|
-
const directory =
|
|
61
|
-
if (
|
|
66
|
+
const directory = runDirectoryFromHostContext(context);
|
|
67
|
+
if (directory !== undefined) {
|
|
62
68
|
const fromDir = runIdFromRunDirectory(directory);
|
|
63
69
|
if (fromDir !== undefined) return fromDir;
|
|
64
70
|
}
|
|
@@ -74,8 +80,8 @@ function runIdentity(context: HostContext): string {
|
|
|
74
80
|
export const COURT_ATTEMPT_ENV = "AK_ROLE_COURT_ATTEMPT" as const;
|
|
75
81
|
|
|
76
82
|
function attemptIdentity(context: HostContext, runId: string): string {
|
|
77
|
-
const courtAttempt =
|
|
78
|
-
if (
|
|
83
|
+
const courtAttempt = courtAttemptIdFromHostContext(context);
|
|
84
|
+
if (courtAttempt !== undefined) return courtAttempt;
|
|
79
85
|
return context.sessionManager.getHeader?.()?.id ?? context.sessionManager.getLeafId?.() ?? `${runId}:initial`;
|
|
80
86
|
}
|
|
81
87
|
|
|
@@ -258,14 +264,10 @@ function rowFromPayload(
|
|
|
258
264
|
* recorded attemptId + toolCallId so distinct court attempts stay distinct (#881).
|
|
259
265
|
* `accepted` is the original payload; never rebuilt from a status/facts envelope.
|
|
260
266
|
*/
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
): Promise<readonly RecordedSubmissionRow[]> {
|
|
266
|
-
const scope = resolveReadScope(homeOrScope);
|
|
267
|
-
const { owned } = await readOwnedSubmissionRecords(cwd, runId, scope.home);
|
|
268
|
-
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;
|
|
269
271
|
const out: RecordedSubmissionRow[] = [];
|
|
270
272
|
const indexByCall = new Map<string, number>();
|
|
271
273
|
// Recover seat identity for historical non-sealed rows that omitted role (#881).
|
|
@@ -360,6 +362,39 @@ export async function readRecordedSubmissionRows(
|
|
|
360
362
|
return out;
|
|
361
363
|
}
|
|
362
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
|
+
|
|
363
398
|
/**
|
|
364
399
|
* All raw role payloads in ledger order (#836 multi-submit).
|
|
365
400
|
* Returns `accepted` bytes as stored — unknown, never re-wrapped.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed user-dialogue stdin transport (#879 / #471).
|
|
3
|
+
* Pi's piped-stdin reader trims the wrapper; the body field keeps original bytes.
|
|
4
|
+
* No size cap and no path/pointer substitute — the whole dialogue rides the envelope.
|
|
5
|
+
*/
|
|
6
|
+
export const USER_DIALOGUE_STDIN_KIND = "ak-user-dialogue" as const;
|
|
7
|
+
|
|
8
|
+
export type UserDialogueStdinEnvelope = {
|
|
9
|
+
readonly kind: typeof USER_DIALOGUE_STDIN_KIND;
|
|
10
|
+
readonly body: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function encodeUserDialogueStdin(body: string): string {
|
|
14
|
+
return JSON.stringify({ kind: USER_DIALOGUE_STDIN_KIND, body } satisfies UserDialogueStdinEnvelope);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isUserDialogueEnvelope(value: unknown): value is UserDialogueStdinEnvelope {
|
|
18
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
19
|
+
const record = value as { kind?: unknown; body?: unknown };
|
|
20
|
+
return record.kind === USER_DIALOGUE_STDIN_KIND && typeof record.body === "string";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Host read: recover original dialogue after a trim-capable pipe.
|
|
25
|
+
* Non-envelope text (interactive Pi, officer JSON, headless raw body) is unchanged.
|
|
26
|
+
*/
|
|
27
|
+
export function readUserDialogueStdin(raw: string): string {
|
|
28
|
+
const trimmed = raw.trim();
|
|
29
|
+
if (!trimmed.startsWith("{")) return raw;
|
|
30
|
+
try {
|
|
31
|
+
const parsed: unknown = JSON.parse(trimmed);
|
|
32
|
+
if (isUserDialogueEnvelope(parsed)) return parsed.body;
|
|
33
|
+
} catch {
|
|
34
|
+
// Not our envelope — leave caller bytes intact.
|
|
35
|
+
}
|
|
36
|
+
return raw;
|
|
37
|
+
}
|
package/src/worker-role.ts
CHANGED
|
@@ -290,6 +290,8 @@ export function createFixerRoleRuntime(
|
|
|
290
290
|
...(_signal === undefined ? {} : { signal: _signal }),
|
|
291
291
|
hostActions,
|
|
292
292
|
toolCallId,
|
|
293
|
+
// #879: this-turn typed payload — identity-bound at submit site.
|
|
294
|
+
submission: output,
|
|
293
295
|
});
|
|
294
296
|
}
|
|
295
297
|
const acceptedDetails = output;
|
|
@@ -428,6 +430,8 @@ export function createCoderRoleRuntime(
|
|
|
428
430
|
...(_signal === undefined ? {} : { signal: _signal }),
|
|
429
431
|
hostActions,
|
|
430
432
|
toolCallId,
|
|
433
|
+
// #879: this-turn typed payload — identity-bound at submit site.
|
|
434
|
+
submission: output,
|
|
431
435
|
});
|
|
432
436
|
}
|
|
433
437
|
const acceptedDetails = output;
|