@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/pi/role-turn-host.ts
CHANGED
|
@@ -21,9 +21,10 @@ import type {
|
|
|
21
21
|
RoleTurnRequest,
|
|
22
22
|
RoleTurnResult,
|
|
23
23
|
} from "../host-contracts.ts";
|
|
24
|
-
import { ExplicitInternalActivationError } from "../host-contracts.ts";
|
|
25
|
-
import { applyEngineChildEnv } from "../engine-detour.ts";
|
|
24
|
+
import { ExplicitInternalActivationError, isOfficerReviewSeat } from "../host-contracts.ts";
|
|
25
|
+
import { applyEngineChildEnv, ENGINE_MODEL_FLAG_NAME, normalizeEngineName } from "../engine-detour.ts";
|
|
26
26
|
import { projectActivationFlags } from "../role-activation-flags.ts";
|
|
27
|
+
import { encodeUserDialogueStdin } from "../user-dialogue-stdin.ts";
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
/** Package-relative Internal role entrypoint (ADR 0052; same path as public-cli registry). */
|
|
@@ -119,7 +120,7 @@ export function applyPiNativeSkillInvocation(
|
|
|
119
120
|
* Pi last-hop argv after `--no-extensions -e entry` (#819).
|
|
120
121
|
* Activation flag membership comes from middle-layer projectActivationFlags;
|
|
121
122
|
* this function only renders session coords, controlled constants, and pairs.
|
|
122
|
-
*
|
|
123
|
+
* User dialogue is not an argv element (#879): it rides spawn stdin.
|
|
123
124
|
*/
|
|
124
125
|
export function buildPiTurnExtraArgs(
|
|
125
126
|
request: RoleTurnRequest,
|
|
@@ -127,14 +128,6 @@ export function buildPiTurnExtraArgs(
|
|
|
127
128
|
extraPiArgs: readonly string[] = [],
|
|
128
129
|
): string[] {
|
|
129
130
|
const { sessionFile, sessionDirectory } = authority.decode(request.principal);
|
|
130
|
-
const rawPrompt =
|
|
131
|
-
request.continuation.kind === "initial" || request.continuation.kind === "resume"
|
|
132
|
-
? request.continuation.prompt
|
|
133
|
-
: (() => {
|
|
134
|
-
const _exhaustive: never = request.continuation;
|
|
135
|
-
return _exhaustive;
|
|
136
|
-
})();
|
|
137
|
-
const prompt = applyPiNativeSkillInvocation(request.methods, rawPrompt);
|
|
138
131
|
return [
|
|
139
132
|
"--no-skills",
|
|
140
133
|
...buildMethodArgs(request.methods),
|
|
@@ -148,13 +141,31 @@ export function buildPiTurnExtraArgs(
|
|
|
148
141
|
...extraPiArgs,
|
|
149
142
|
// Envelope assembly = projectActivationFlags; pi only renders argv pairs.
|
|
150
143
|
...activationFlagsToPiArgv(projectActivationFlags(request)),
|
|
144
|
+
...piEngineModelArgs(request),
|
|
151
145
|
"--mode",
|
|
152
146
|
"json",
|
|
153
147
|
...buildSeatModelCliArgs(request.model),
|
|
154
|
-
prompt,
|
|
155
148
|
];
|
|
156
149
|
}
|
|
157
150
|
|
|
151
|
+
/** Engine name stays on child env; model has no env fallback (#883 / #879). */
|
|
152
|
+
function piEngineModelArgs(request: RoleTurnRequest): string[] {
|
|
153
|
+
const model = normalizeEngineName(request.engineModel);
|
|
154
|
+
if (model === undefined) return [];
|
|
155
|
+
return [`--${ENGINE_MODEL_FLAG_NAME}`, model];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function piUserDialogueBody(request: RoleTurnRequest): string {
|
|
159
|
+
const rawPrompt =
|
|
160
|
+
request.continuation.kind === "initial" || request.continuation.kind === "resume"
|
|
161
|
+
? request.continuation.prompt
|
|
162
|
+
: (() => {
|
|
163
|
+
const _exhaustive: never = request.continuation;
|
|
164
|
+
return _exhaustive;
|
|
165
|
+
})();
|
|
166
|
+
return applyPiNativeSkillInvocation(request.methods, rawPrompt);
|
|
167
|
+
}
|
|
168
|
+
|
|
158
169
|
export type PiSpawnRunner = (
|
|
159
170
|
args: readonly string[],
|
|
160
171
|
options: {
|
|
@@ -163,6 +174,8 @@ export type PiSpawnRunner = (
|
|
|
163
174
|
timeoutMs?: number;
|
|
164
175
|
/** Parent cancellation; the child gets the same graceful SIGTERM as a budget. */
|
|
165
176
|
signal?: AbortSignal;
|
|
177
|
+
/** User dialogue body; omitted from argv so execve cannot E2BIG (#879). */
|
|
178
|
+
stdin?: string;
|
|
166
179
|
},
|
|
167
180
|
) => Promise<{
|
|
168
181
|
code: number | null;
|
|
@@ -271,11 +284,22 @@ export function createDefaultPiSpawnRunner(options: {
|
|
|
271
284
|
const child = spawn(piIdentity.executable, [...args], {
|
|
272
285
|
cwd: spawnOptions.cwd,
|
|
273
286
|
env: spawnOptions.env,
|
|
274
|
-
stdio: ["
|
|
287
|
+
stdio: ["pipe", "ignore", "pipe"],
|
|
275
288
|
});
|
|
289
|
+
if (child.stdin === null) {
|
|
290
|
+
throw new Error("Pi child stdin pipe was not created");
|
|
291
|
+
}
|
|
276
292
|
if (child.stderr === null) {
|
|
277
293
|
throw new Error("Pi child stderr pipe was not created");
|
|
278
294
|
}
|
|
295
|
+
let stdinDeliveryError: Error | undefined;
|
|
296
|
+
child.stdin.on("error", (error) => {
|
|
297
|
+
stdinDeliveryError ??= error;
|
|
298
|
+
});
|
|
299
|
+
if (spawnOptions.stdin !== undefined) {
|
|
300
|
+
child.stdin.write(spawnOptions.stdin);
|
|
301
|
+
}
|
|
302
|
+
child.stdin.end();
|
|
279
303
|
let stderr = "";
|
|
280
304
|
let timedOut = false;
|
|
281
305
|
// No default wall clock. Only an explicit caller budget arms a timer (ADR 0010).
|
|
@@ -344,6 +368,10 @@ export function createDefaultPiSpawnRunner(options: {
|
|
|
344
368
|
reject(executionError);
|
|
345
369
|
return;
|
|
346
370
|
}
|
|
371
|
+
if (stdinDeliveryError !== undefined) {
|
|
372
|
+
reject(stdinDeliveryError);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
347
375
|
resolveResult({
|
|
348
376
|
code,
|
|
349
377
|
stderr,
|
|
@@ -375,9 +403,13 @@ export function createPiRoleTurnHost(config: PiRoleTurnHostConfig): RoleTurnHost
|
|
|
375
403
|
async executeTurn(request: RoleTurnRequest): Promise<RoleTurnResult> {
|
|
376
404
|
// #617 DK-7: Pi argv gets projected native paths once; never record bytes.
|
|
377
405
|
// Pi already owns its own session file, so only sitian prior volume rides in.
|
|
406
|
+
// #879: station-child officer dialogue keeps peer words — do not splice
|
|
407
|
+
// host-transition priorNativePaths into the review prompt body.
|
|
378
408
|
let turnRequest = request;
|
|
409
|
+
const officerStationChild =
|
|
410
|
+
request.stationChild === true && isOfficerReviewSeat(request.activation.role);
|
|
379
411
|
const paths =
|
|
380
|
-
request.hostTransition?.priorNativeKind === "sitian"
|
|
412
|
+
!officerStationChild && request.hostTransition?.priorNativeKind === "sitian"
|
|
381
413
|
? request.hostTransition.priorNativePaths
|
|
382
414
|
: undefined;
|
|
383
415
|
if (
|
|
@@ -400,6 +432,7 @@ export function createPiRoleTurnHost(config: PiRoleTurnHostConfig): RoleTurnHost
|
|
|
400
432
|
config.extraPiArgs ?? [],
|
|
401
433
|
);
|
|
402
434
|
const args = buildExplicitInternalActivationArgs(roleEntry, extraArgs);
|
|
435
|
+
const stdin = encodeUserDialogueStdin(piUserDialogueBody(turnRequest));
|
|
403
436
|
// Shared envelope isolates this call's court identity: omitting courtAttemptId
|
|
404
437
|
// must not inherit a parent process.env.AK_ROLE_COURT_ATTEMPT (#637).
|
|
405
438
|
const env: NodeJS.ProcessEnv = {
|
|
@@ -444,6 +477,7 @@ export function createPiRoleTurnHost(config: PiRoleTurnHostConfig): RoleTurnHost
|
|
|
444
477
|
return await spawnRunner(args, {
|
|
445
478
|
cwd: request.cwd,
|
|
446
479
|
env,
|
|
480
|
+
stdin,
|
|
447
481
|
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
448
482
|
...(request.signal === undefined ? {} : { signal: request.signal }),
|
|
449
483
|
});
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
import { parseAutoResumeLimit } from "./config.ts";
|
|
34
34
|
import { isLawfulTypedTerminalOutcome, formatTerminalResult, type TerminalArtifactRef, type TerminalResult, type TerminalRoleName } from "./terminal.ts";
|
|
35
35
|
import {
|
|
36
|
+
attachRecordedSubmissions,
|
|
36
37
|
presentFailureTerminal,
|
|
37
38
|
presentStructuralRejection,
|
|
38
39
|
resolveControlledFailureResumeObservation,
|
|
@@ -362,6 +363,33 @@ function unwrapTurnDispatchedFailure(error: unknown): unknown {
|
|
|
362
363
|
return current;
|
|
363
364
|
}
|
|
364
365
|
|
|
366
|
+
async function attachDispatchExceptionTerminal(
|
|
367
|
+
admitted: {
|
|
368
|
+
readonly runDirectory: string;
|
|
369
|
+
readonly runId: string;
|
|
370
|
+
readonly projectRoot: string;
|
|
371
|
+
},
|
|
372
|
+
terminal: TerminalResult,
|
|
373
|
+
io: CliIo,
|
|
374
|
+
): Promise<TerminalResult> {
|
|
375
|
+
try {
|
|
376
|
+
return await attachRecordedSubmissions(
|
|
377
|
+
{
|
|
378
|
+
projectRoot: admitted.projectRoot,
|
|
379
|
+
runId: admitted.runId,
|
|
380
|
+
runDirectory: admitted.runDirectory,
|
|
381
|
+
},
|
|
382
|
+
terminal,
|
|
383
|
+
);
|
|
384
|
+
} catch (error) {
|
|
385
|
+
// Existing diagnostic seam: attach true cause on stderr, original Terminal stays.
|
|
386
|
+
io.stderr(
|
|
387
|
+
`dispatch exception ledger attach failed (best-effort continue): ${describeErrorIdentity(error)}\n`,
|
|
388
|
+
);
|
|
389
|
+
return terminal;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
365
393
|
/**
|
|
366
394
|
* Typed failure terminal for a retry path that ended with only exceptions:
|
|
367
395
|
* loud, non-lawful, carrying the last true cause and the pointers to the
|
|
@@ -384,8 +412,8 @@ function dispatchExceptionFailureTerminal(input: {
|
|
|
384
412
|
? "dispatch threw an exception on every attempt"
|
|
385
413
|
: "the final dispatch threw an exception";
|
|
386
414
|
const diagnostic = `${history} (${input.endReason}; resumes used ${input.autoResumeAttempts}); last cause: ${describeErrorIdentity(causeError)}`;
|
|
415
|
+
// #881: no fabricated cause class — original error identity + error-file pointers carry the fact.
|
|
387
416
|
const decisiveFacts: Record<string, unknown> = {
|
|
388
|
-
cause: "unrecognized",
|
|
389
417
|
diagnostic,
|
|
390
418
|
resumesUsed: input.autoResumeAttempts,
|
|
391
419
|
dispatchErrorFiles: [...input.errorFiles],
|
|
@@ -406,7 +434,6 @@ function dispatchExceptionFailureTerminal(input: {
|
|
|
406
434
|
roleOutcome: {
|
|
407
435
|
kind: "failure",
|
|
408
436
|
role: input.role,
|
|
409
|
-
cause: "unrecognized",
|
|
410
437
|
diagnostic,
|
|
411
438
|
decisiveFacts,
|
|
412
439
|
},
|
|
@@ -427,6 +454,8 @@ export async function runWithAutoResumeLoop<
|
|
|
427
454
|
role: TerminalRoleName;
|
|
428
455
|
runId: string;
|
|
429
456
|
principal: DurablePrincipal;
|
|
457
|
+
/** Required: exception terminals attach the ledger via this existing admitted fact. */
|
|
458
|
+
projectRoot: string;
|
|
430
459
|
};
|
|
431
460
|
principalAuthority: DurablePrincipalAuthority;
|
|
432
461
|
/**
|
|
@@ -571,15 +600,19 @@ export async function runWithAutoResumeLoop<
|
|
|
571
600
|
} else {
|
|
572
601
|
// Exception path: continue through the identical budget/session gates.
|
|
573
602
|
if (autoResumeAttempts >= limit) {
|
|
574
|
-
const terminal =
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
603
|
+
const terminal = await attachDispatchExceptionTerminal(
|
|
604
|
+
options.admitted,
|
|
605
|
+
dispatchExceptionFailureTerminal({
|
|
606
|
+
role: options.admitted.role,
|
|
607
|
+
runId: options.admitted.runId,
|
|
608
|
+
causeError: lastThrownError,
|
|
609
|
+
errorFiles: retainedErrorFiles,
|
|
610
|
+
autoResumeAttempts,
|
|
611
|
+
endReason: "auto-resume budget exhausted",
|
|
612
|
+
everyAttemptThrew,
|
|
613
|
+
}),
|
|
614
|
+
options.io,
|
|
615
|
+
);
|
|
583
616
|
await finalizeExceptionRunBestEffort(options.admitted.runDirectory, options.io);
|
|
584
617
|
presentTerminal(terminal, options.io);
|
|
585
618
|
return {
|
|
@@ -588,15 +621,19 @@ export async function runWithAutoResumeLoop<
|
|
|
588
621
|
} as T;
|
|
589
622
|
}
|
|
590
623
|
if (!(await isPrincipalAvailable(options.admitted.principal))) {
|
|
591
|
-
const terminal =
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
624
|
+
const terminal = await attachDispatchExceptionTerminal(
|
|
625
|
+
options.admitted,
|
|
626
|
+
dispatchExceptionFailureTerminal({
|
|
627
|
+
role: options.admitted.role,
|
|
628
|
+
runId: options.admitted.runId,
|
|
629
|
+
causeError: lastThrownError,
|
|
630
|
+
errorFiles: retainedErrorFiles,
|
|
631
|
+
autoResumeAttempts,
|
|
632
|
+
endReason: "session principal unavailable before further resume",
|
|
633
|
+
everyAttemptThrew,
|
|
634
|
+
}),
|
|
635
|
+
options.io,
|
|
636
|
+
);
|
|
600
637
|
await finalizeExceptionRunBestEffort(options.admitted.runDirectory, options.io);
|
|
601
638
|
presentTerminal(terminal, options.io);
|
|
602
639
|
return {
|
|
@@ -5,13 +5,31 @@
|
|
|
5
5
|
* 只读已有案卷:不刷新、不生成、不校验内容、不新增拒收或停工条件。
|
|
6
6
|
* 机器文本仅中立标识材料(ADR 0073),用途说明归角色材料所有。
|
|
7
7
|
*
|
|
8
|
-
* 递送挂载点唯一:`post-admission` 在 beforeDispatch
|
|
8
|
+
* 递送挂载点唯一:`post-admission` 在 beforeDispatch 之后为每个公共入口挂载。
|
|
9
|
+
* 普通入口把本段追加进 continuation;station-child 审核轮次走 attachments
|
|
10
|
+
* 冻结 + role-runtime `loadCaseDossierReadingMaterial` → readingMaterial →
|
|
11
|
+
* systemPrompt.materials fold(#879:对话 instruction 保持父腿 payload 原文;
|
|
12
|
+
* 起居录作独立附件面,不新造 RoleTurnRequest.materials)。
|
|
9
13
|
*/
|
|
14
|
+
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
|
|
10
18
|
import { resolveTicketProvenanceVolume } from "../ticket-provenance.ts";
|
|
19
|
+
import {
|
|
20
|
+
freezeAttachmentsIntoRun,
|
|
21
|
+
type FrozenAttachment,
|
|
22
|
+
} from "./invocation.ts";
|
|
11
23
|
|
|
12
24
|
/** Section heading of the system-delivered dossier pointer (presentation only). */
|
|
13
25
|
const CASE_DOSSIER_SECTION_HEADING = "## 本票起居录(系统随案提供)" as const;
|
|
14
26
|
|
|
27
|
+
/** Stable freeze key under run/attachments/ for station-child 0081 delivery. */
|
|
28
|
+
const CASE_DOSSIER_ATTACH_KEY = "case-dossier" as const;
|
|
29
|
+
|
|
30
|
+
/** Leaf name of the frozen pointer section (content = purpose + paths). */
|
|
31
|
+
const CASE_DOSSIER_ATTACH_FILE = "case-dossier-pointer.md" as const;
|
|
32
|
+
|
|
15
33
|
/** Pointer only — presence/absence is for the role to observe at the path. */
|
|
16
34
|
function describeDossierFile(path: string): string {
|
|
17
35
|
return path;
|
|
@@ -42,3 +60,70 @@ export async function projectCaseDossierPointerSection(input: {
|
|
|
42
60
|
`记录卷宗:${describeDossierFile(volume.recordFile)}`,
|
|
43
61
|
].join("\n");
|
|
44
62
|
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* ADR 0081 delivery for station-child officer turns (#879): freeze the same
|
|
66
|
+
* pointer section through the existing attachments seam. Role-runtime loads
|
|
67
|
+
* that freeze via loadCaseDossierReadingMaterial onto readingMaterial; the
|
|
68
|
+
* envelope then folds it into systemPrompt.materials. Peer dialogue instruction
|
|
69
|
+
* stays the parent payload; never RoleTurnRequest.materials.
|
|
70
|
+
* Returns frozen attachments, or undefined when unbound (no dossier).
|
|
71
|
+
*/
|
|
72
|
+
export async function deliverCaseDossierAsAttachment(input: {
|
|
73
|
+
readonly ticketNumber: number | undefined;
|
|
74
|
+
readonly projectRoot: string;
|
|
75
|
+
readonly home: string;
|
|
76
|
+
readonly runDirectory: string;
|
|
77
|
+
}): Promise<readonly FrozenAttachment[] | undefined> {
|
|
78
|
+
const section = await projectCaseDossierPointerSection({
|
|
79
|
+
ticketNumber: input.ticketNumber,
|
|
80
|
+
projectRoot: input.projectRoot,
|
|
81
|
+
home: input.home,
|
|
82
|
+
});
|
|
83
|
+
if (section === undefined) return undefined;
|
|
84
|
+
// Stage in OS temp only — never leave a run-local .case-dossier-stage copy.
|
|
85
|
+
const stagingDir = await mkdtemp(join(tmpdir(), "ak-case-dossier-"));
|
|
86
|
+
try {
|
|
87
|
+
const stagingPath = join(stagingDir, CASE_DOSSIER_ATTACH_FILE);
|
|
88
|
+
await writeFile(stagingPath, `${section}\n`, "utf8");
|
|
89
|
+
return await freezeAttachmentsIntoRun(
|
|
90
|
+
[stagingPath],
|
|
91
|
+
input.runDirectory,
|
|
92
|
+
CASE_DOSSIER_ATTACH_KEY,
|
|
93
|
+
);
|
|
94
|
+
} finally {
|
|
95
|
+
await rm(stagingDir, { recursive: true, force: true });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Typed reading-material face for a frozen station-child 0081 attachment. */
|
|
100
|
+
export type CaseDossierReadingMaterial = {
|
|
101
|
+
readonly kind: "case-dossier-pointer";
|
|
102
|
+
readonly frozenPath: string;
|
|
103
|
+
readonly section: string;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Load a previously frozen case-dossier attachment as reading material
|
|
108
|
+
* (existing agent-start / systemPrompt.materials fold — not dialogue prompt,
|
|
109
|
+
* not RoleTurnRequest.materials). Undefined when the run has no such freeze.
|
|
110
|
+
*/
|
|
111
|
+
export async function loadCaseDossierReadingMaterial(
|
|
112
|
+
runDirectory: string,
|
|
113
|
+
): Promise<CaseDossierReadingMaterial | undefined> {
|
|
114
|
+
const frozenPath = join(
|
|
115
|
+
runDirectory,
|
|
116
|
+
"attachments",
|
|
117
|
+
CASE_DOSSIER_ATTACH_KEY,
|
|
118
|
+
`00-${CASE_DOSSIER_ATTACH_FILE}`,
|
|
119
|
+
);
|
|
120
|
+
let section: string;
|
|
121
|
+
try {
|
|
122
|
+
section = await readFile(frozenPath, "utf8");
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
if (section.trim() === "") return undefined;
|
|
128
|
+
return { kind: "case-dossier-pointer", frozenPath, section };
|
|
129
|
+
}
|
package/src/public-cli/cli.ts
CHANGED
|
@@ -1637,10 +1637,10 @@ export async function runAkRole(
|
|
|
1637
1637
|
if (error.cause !== undefined) {
|
|
1638
1638
|
const detail = formatErrorCauseDetail(error.cause);
|
|
1639
1639
|
if (detail.trim().length > 0) {
|
|
1640
|
-
label = `${label || error.name || "
|
|
1640
|
+
label = `${label || error.name || "exception"}; cause: ${detail}`;
|
|
1641
1641
|
}
|
|
1642
1642
|
}
|
|
1643
|
-
io.stderr(formatCliDiagnostic(label || error.name || "
|
|
1643
|
+
io.stderr(formatCliDiagnostic(label || error.name || "exception"));
|
|
1644
1644
|
return { exitCode: 1 };
|
|
1645
1645
|
}
|
|
1646
1646
|
io.stderr(formatCliDiagnostic(String(error)));
|
|
@@ -142,7 +142,9 @@ function collectorAdapters(): PostAdmissionAdapters<AdmittedCollectorInvocation>
|
|
|
142
142
|
(infrastructureFailure === undefined
|
|
143
143
|
? undefined
|
|
144
144
|
: {
|
|
145
|
-
|
|
145
|
+
...(infrastructureFailure.cause === undefined
|
|
146
|
+
? {}
|
|
147
|
+
: { cause: infrastructureFailure.cause }),
|
|
146
148
|
diagnostic: infrastructureFailure.diagnostic,
|
|
147
149
|
...(infrastructureFailure.identity === undefined
|
|
148
150
|
? {}
|
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
trySettleCountersignTerminalResult,
|
|
55
55
|
} from "./settlement.ts";
|
|
56
56
|
import type { CliIo } from "./cli-io.ts";
|
|
57
|
-
import {
|
|
57
|
+
import type { TerminalResult, TerminalRoleOutcome } from "./terminal.ts";
|
|
58
58
|
import {
|
|
59
59
|
projectRoleTurnRequest,
|
|
60
60
|
type RoleTurnRequestProjectionOptions,
|
|
@@ -96,7 +96,24 @@ export function buildCountersignTurnRequest(
|
|
|
96
96
|
type CourtDiaristIdentity =
|
|
97
97
|
| { readonly kind: "ticket"; readonly ticketNumber: number }
|
|
98
98
|
| { readonly kind: "unbound" }
|
|
99
|
-
| { readonly kind: "escalate"
|
|
99
|
+
| { readonly kind: "escalate" };
|
|
100
|
+
|
|
101
|
+
type CourtDiaristInvocationResult = {
|
|
102
|
+
readonly identity: CourtDiaristIdentity;
|
|
103
|
+
readonly failedWithoutEscalate?: { readonly diagnostic: string };
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/** Routing boolean over the child's own typed sequence — does not pick or rewrite a sole row. */
|
|
107
|
+
function courtDiaristEscalated(roleOutcome: TerminalRoleOutcome | undefined): boolean {
|
|
108
|
+
if (roleOutcome === undefined) return false;
|
|
109
|
+
if (roleOutcome.kind === "audit_escalation") return true;
|
|
110
|
+
if (roleOutcome.kind !== "accepted") return false;
|
|
111
|
+
return (roleOutcome.payloads ?? []).some((payload) => {
|
|
112
|
+
if (typeof payload !== "object" || payload === null || Array.isArray(payload)) return false;
|
|
113
|
+
const record = payload as Record<string, unknown>;
|
|
114
|
+
return record.status === "escalate" || record.countersignStatus === "escalate";
|
|
115
|
+
});
|
|
116
|
+
}
|
|
100
117
|
|
|
101
118
|
/**
|
|
102
119
|
* Invoke public 起居郎 under the court-pipeline quiet face.
|
|
@@ -112,10 +129,7 @@ async function invokeCourtDiarist(input: {
|
|
|
112
129
|
readonly failureLabel: string;
|
|
113
130
|
/** Already-verified typed key from countersign (refresh / post-assert handoff). */
|
|
114
131
|
readonly boundTicketNumber?: number;
|
|
115
|
-
}, env: CountersignRunEnv, io: CliIo): Promise<{
|
|
116
|
-
readonly identity: CourtDiaristIdentity;
|
|
117
|
-
readonly failedWithoutEscalate?: { readonly diagnostic: string };
|
|
118
|
-
}> {
|
|
132
|
+
}, env: CountersignRunEnv, io: CliIo): Promise<CourtDiaristInvocationResult> {
|
|
119
133
|
// Quiet face: the countersign caller must not see diarist CLI chatter.
|
|
120
134
|
const quietIo: CliIo = {
|
|
121
135
|
stdout() {},
|
|
@@ -145,23 +159,12 @@ async function invokeCourtDiarist(input: {
|
|
|
145
159
|
});
|
|
146
160
|
|
|
147
161
|
const roleOutcome = result.terminal?.roleOutcome;
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
? facts.countersignStatus
|
|
155
|
-
: roleOutcome.kind === "audit_escalation"
|
|
156
|
-
? "escalate"
|
|
157
|
-
: undefined;
|
|
158
|
-
if (status === "escalate") {
|
|
159
|
-
const reason =
|
|
160
|
-
typeof facts?.reason === "string"
|
|
161
|
-
? facts.reason
|
|
162
|
-
: "diarist escalated without reason";
|
|
163
|
-
return { identity: { kind: "escalate", reason } };
|
|
164
|
-
}
|
|
162
|
+
// Escalate routing is a boolean over the preserved sequence (#881). Reasons and
|
|
163
|
+
// payload bodies stay on roleOutcome — never rewritten into a sole identity reason.
|
|
164
|
+
if (courtDiaristEscalated(roleOutcome)) {
|
|
165
|
+
return {
|
|
166
|
+
identity: { kind: "escalate" },
|
|
167
|
+
};
|
|
165
168
|
}
|
|
166
169
|
|
|
167
170
|
if (result.exitCode !== 0) {
|
|
@@ -183,9 +186,13 @@ async function invokeCourtDiarist(input: {
|
|
|
183
186
|
Number.isSafeInteger(asserted) &&
|
|
184
187
|
asserted >= 1
|
|
185
188
|
) {
|
|
186
|
-
return {
|
|
189
|
+
return {
|
|
190
|
+
identity: { kind: "ticket", ticketNumber: asserted },
|
|
191
|
+
};
|
|
187
192
|
}
|
|
188
|
-
return {
|
|
193
|
+
return {
|
|
194
|
+
identity: { kind: "unbound" },
|
|
195
|
+
};
|
|
189
196
|
}
|
|
190
197
|
|
|
191
198
|
/**
|
|
@@ -226,7 +233,7 @@ export async function runCountersignCourtDiaristStation(
|
|
|
226
233
|
|
|
227
234
|
if (outcome.identity.kind === "escalate") {
|
|
228
235
|
throw new StationChildExhaustedError(
|
|
229
|
-
|
|
236
|
+
"court diarist station escalated (cannot identify court target)",
|
|
230
237
|
);
|
|
231
238
|
}
|
|
232
239
|
if (outcome.failedWithoutEscalate !== undefined) {
|
|
@@ -309,7 +316,7 @@ export async function runPublicCountersign(
|
|
|
309
316
|
code: null,
|
|
310
317
|
stderr: "",
|
|
311
318
|
thrown: new Error(
|
|
312
|
-
|
|
319
|
+
"court diarist station escalated (cannot identify court target)",
|
|
313
320
|
),
|
|
314
321
|
},
|
|
315
322
|
countersignAdapters(),
|
|
@@ -35,11 +35,12 @@ import {
|
|
|
35
35
|
trySettleDiaristTerminalResult,
|
|
36
36
|
} from "./settlement.ts";
|
|
37
37
|
import type { CliIo } from "./cli-io.ts";
|
|
38
|
-
import {
|
|
38
|
+
import type { TerminalResult } from "./terminal.ts";
|
|
39
39
|
import {
|
|
40
40
|
projectRoleTurnRequest,
|
|
41
41
|
type RoleTurnRequestProjectionOptions,
|
|
42
42
|
} from "./turn-request.ts";
|
|
43
|
+
import { readRunTicketNumber } from "../run-ticket-number.ts";
|
|
43
44
|
|
|
44
45
|
export type DiaristRunEnv = PostAdmissionEnv & {
|
|
45
46
|
principalAuthority: DurablePrincipalAuthority;
|
|
@@ -191,33 +192,15 @@ export async function runPublicDiarist(
|
|
|
191
192
|
adapters: diaristAdapters(),
|
|
192
193
|
...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
|
|
193
194
|
}).then(async (result) => {
|
|
194
|
-
// Accept
|
|
195
|
-
//
|
|
196
|
-
//
|
|
195
|
+
// Accept hook binds ticket onto durable pages (#771). Mirror that page fact
|
|
196
|
+
// onto the caller-visible admitted object — never pick a ticketNumber out of
|
|
197
|
+
// the payload sequence (#881 sole-collapse ban on ticket/escalate).
|
|
197
198
|
if (admitted.ticketNumber === undefined && result.admitted !== undefined) {
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (
|
|
204
|
-
roleOutcome !== undefined &&
|
|
205
|
-
roleOutcome.kind === "accepted" &&
|
|
206
|
-
facts?.status !== "escalate"
|
|
207
|
-
) {
|
|
208
|
-
const raw =
|
|
209
|
-
typeof facts?.ticketNumber === "number"
|
|
210
|
-
? facts.ticketNumber
|
|
211
|
-
: typeof facts?.sitian === "object"
|
|
212
|
-
&& facts.sitian !== null
|
|
213
|
-
&& typeof (facts.sitian as { ticketNumber?: unknown }).ticketNumber === "number"
|
|
214
|
-
? (facts.sitian as { ticketNumber: number }).ticketNumber
|
|
215
|
-
: undefined;
|
|
216
|
-
if (typeof raw === "number" && Number.isSafeInteger(raw) && raw >= 1) {
|
|
217
|
-
(admitted as { ticketNumber?: number }).ticketNumber = raw;
|
|
218
|
-
if (result.admitted.ticketNumber === undefined) {
|
|
219
|
-
(result.admitted as { ticketNumber?: number }).ticketNumber = raw;
|
|
220
|
-
}
|
|
199
|
+
const fromPages = await readRunTicketNumber(admitted.runDirectory);
|
|
200
|
+
if (fromPages !== undefined) {
|
|
201
|
+
await bindAdmittedTicketNumber(admitted, fromPages);
|
|
202
|
+
if (result.admitted.ticketNumber === undefined) {
|
|
203
|
+
(result.admitted as { ticketNumber?: number }).ticketNumber = fromPages;
|
|
221
204
|
}
|
|
222
205
|
}
|
|
223
206
|
}
|
|
@@ -12,6 +12,7 @@ import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.ts";
|
|
|
12
12
|
import {
|
|
13
13
|
admitInspectorInvocation,
|
|
14
14
|
buildInspectorTransportPrompt,
|
|
15
|
+
persistAdmittedSourceRunPath,
|
|
15
16
|
type AdmittedInspectorInvocation,
|
|
16
17
|
type ParseInspectorArgvResult,
|
|
17
18
|
} from "./invocation.ts";
|
|
@@ -50,18 +51,35 @@ export type InspectorRunEnv = PostAdmissionEnv & {
|
|
|
50
51
|
* Never a public CLI argv — must not pollute the 卷宗指针 parentRunPath key (#747).
|
|
51
52
|
*/
|
|
52
53
|
reviewReask?: string;
|
|
54
|
+
/**
|
|
55
|
+
* #879 gate summons: verbatim parent-submission body for officer dialogue content.
|
|
56
|
+
* Rides summons.instruction / first-mint prompt when reviewReask is absent.
|
|
57
|
+
* Argv 卷宗指针 stays the binding/lookup key only.
|
|
58
|
+
*/
|
|
59
|
+
gateReviewInstruction?: string;
|
|
53
60
|
};
|
|
54
61
|
|
|
62
|
+
function inspectorParentRunPath(
|
|
63
|
+
admitted: AdmittedInspectorInvocation,
|
|
64
|
+
): string | undefined {
|
|
65
|
+
if (typeof admitted.sourceRunPath === "string" && admitted.sourceRunPath.trim() !== "") {
|
|
66
|
+
return admitted.sourceRunPath;
|
|
67
|
+
}
|
|
68
|
+
return parentRunPathFromGatePointerInstruction(admitted.instruction);
|
|
69
|
+
}
|
|
70
|
+
|
|
55
71
|
/** Project admitted invocation onto the host-neutral turn request. */
|
|
56
72
|
export function buildInspectorTurnRequest(
|
|
57
73
|
admitted: AdmittedInspectorInvocation,
|
|
58
74
|
options: RoleTurnRequestProjectionOptions,
|
|
59
75
|
): RoleTurnRequest {
|
|
76
|
+
const sourceRun = inspectorParentRunPath(admitted);
|
|
60
77
|
return projectRoleTurnRequest(
|
|
61
78
|
admitted,
|
|
62
79
|
{
|
|
63
80
|
activation: {
|
|
64
81
|
role: "inspector" as const,
|
|
82
|
+
...(sourceRun === undefined ? {} : { sourceRun }),
|
|
65
83
|
},
|
|
66
84
|
},
|
|
67
85
|
options,
|
|
@@ -97,7 +115,7 @@ export async function runPublicInspector(
|
|
|
97
115
|
// #753/#786: gate re-ask / verbatim submission body ride summons.instruction on resume.
|
|
98
116
|
const parentRunPath = parentRunPathFromGatePointerInstruction(parsed.instruction);
|
|
99
117
|
if (parentRunPath !== undefined) {
|
|
100
|
-
const resumeInstruction = env.reviewReask;
|
|
118
|
+
const resumeInstruction = env.reviewReask ?? env.gateReviewInstruction;
|
|
101
119
|
const summons: SameTicketSummonsMaterials = {
|
|
102
120
|
sourceRunPath: parentRunPath,
|
|
103
121
|
...(resumeInstruction === undefined
|
|
@@ -147,6 +165,10 @@ export async function runPublicInspector(
|
|
|
147
165
|
}
|
|
148
166
|
|
|
149
167
|
await markRunAdmitted(admitted, env.principalAuthority);
|
|
168
|
+
if (parentRunPath !== undefined) {
|
|
169
|
+
await persistAdmittedSourceRunPath(admitted, parentRunPath);
|
|
170
|
+
admitted = { ...admitted, sourceRunPath: parentRunPath };
|
|
171
|
+
}
|
|
150
172
|
|
|
151
173
|
const engineMaterial = engineSessionMaterialFromOptions({
|
|
152
174
|
...pickEngineAxis(env),
|
|
@@ -164,7 +186,9 @@ export async function runPublicInspector(
|
|
|
164
186
|
: { correlationId: env.correlationId }),
|
|
165
187
|
continuation: {
|
|
166
188
|
kind: "initial",
|
|
167
|
-
|
|
189
|
+
// #879: gate first mint uses parent payload as content; binding stays typed activation.
|
|
190
|
+
prompt: (env.reviewReask ?? env.gateReviewInstruction)
|
|
191
|
+
?? buildInspectorTransportPrompt(admitted, engineMaterial),
|
|
168
192
|
},
|
|
169
193
|
});
|
|
170
194
|
|