@akagilnc/pi-workflow-roles 0.1.5252 → 0.1.5270
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 +101 -162
- package/dist/headless-host/production-host.js +357 -415
- package/dist/host-descriptions.js +0 -2
- package/dist/navigator-public-session.js +6 -1
- package/dist/pi/durable-principal.js +1 -12
- package/dist/public-cli/auto-resume.js +59 -87
- package/dist/public-cli/instruction-seat-run.js +3 -3
- package/dist/public-cli/main.js +81 -154
- package/dist/public-cli/post-admission.js +7 -15
- package/dist/public-cli/run-lifecycle.js +5 -15
- package/dist/public-cli/terminal.js +4 -2
- package/dist/session-identity.js +0 -18
- package/package.json +1 -1
- package/src/acp-host/description.ts +0 -2
- package/src/acp-host/production-host.ts +0 -1
- package/src/acp-host/role-turn-host.ts +12 -6
- package/src/headless-host/role-turn-host.ts +11 -3
- package/src/host-contracts.ts +0 -1
- package/src/host-descriptions.ts +0 -2
- package/src/navigator-public-session.ts +12 -1
- package/src/pi/durable-principal.ts +1 -11
- package/src/prepared-role-turn.ts +1 -1
- package/src/public-cli/auto-resume.ts +67 -109
- package/src/public-cli/instruction-seat-run.ts +3 -3
- package/src/public-cli/post-admission.ts +7 -21
- package/src/public-cli/run-lifecycle.ts +3 -21
- package/src/public-cli/terminal.ts +4 -2
- package/src/role-envelope.ts +3 -3
- package/src/session-identity.ts +0 -22
package/package.json
CHANGED
|
@@ -26,8 +26,6 @@ export type AcpHostDescription = Readonly<{
|
|
|
26
26
|
* `provider:model` (hermes).
|
|
27
27
|
*/
|
|
28
28
|
modelPassing: "argv" | "set_model";
|
|
29
|
-
/** Which verb a bound resume uses; "session/new" hosts always mint + bind. */
|
|
30
|
-
boundResume: "session/load" | "session/new";
|
|
31
29
|
/** Durable ACP binding filename written beside the session principal. */
|
|
32
30
|
sessionBindingFile: string;
|
|
33
31
|
/**
|
|
@@ -61,7 +61,6 @@ export function createProductionAcpRoleTurnHost(options: ProductionAcpHostOption
|
|
|
61
61
|
return createComposedAcpRoleTurnHost({
|
|
62
62
|
hostName,
|
|
63
63
|
sessionIdentity: createSessionIdentityAuthority(principalAuthority, description.sessionBindingFile),
|
|
64
|
-
boundResume: description.boundResume,
|
|
65
64
|
modelPassing: description.modelPassing,
|
|
66
65
|
roleRuntimeDependencies: createRoleRuntimeDependencies(packageRoot),
|
|
67
66
|
async connect(request) {
|
|
@@ -86,8 +86,6 @@ export type AcpRoleTurnHostConfig = Readonly<{
|
|
|
86
86
|
sessionIdentity: SessionIdentityAuthority;
|
|
87
87
|
/** Seat-table host key (e.g. grok-build) for sitian host field. */
|
|
88
88
|
hostName: string;
|
|
89
|
-
/** Whether a bound resume reuses the native session or mints a fresh one. */
|
|
90
|
-
boundResume: AcpHostDescription["boundResume"];
|
|
91
89
|
/**
|
|
92
90
|
* How the seat model reaches the agent: "set_model" sends an ACP
|
|
93
91
|
* `session/set_model` RPC with modelId `provider:model` once the session
|
|
@@ -324,7 +322,8 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
324
322
|
});
|
|
325
323
|
return loadedId;
|
|
326
324
|
};
|
|
327
|
-
|
|
325
|
+
let sessionReady = true;
|
|
326
|
+
if (request.continuation.kind === "resume") {
|
|
328
327
|
const explicitHostSessionId = request.continuation.hostSessionId;
|
|
329
328
|
const boundSessionId = explicitHostSessionId !== undefined && explicitHostSessionId !== ""
|
|
330
329
|
? explicitHostSessionId
|
|
@@ -333,10 +332,17 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
333
332
|
sessionId = boundSessionId;
|
|
334
333
|
sessionId = await loadSession(boundSessionId);
|
|
335
334
|
sessionOpened = true;
|
|
335
|
+
} else {
|
|
336
|
+
outcome = failure(
|
|
337
|
+
"session",
|
|
338
|
+
"AcpSessionFailure",
|
|
339
|
+
"session-id-missing",
|
|
340
|
+
undefined,
|
|
341
|
+
"resume requires a bound session id",
|
|
342
|
+
);
|
|
343
|
+
sessionReady = false;
|
|
336
344
|
}
|
|
337
|
-
}
|
|
338
|
-
let sessionReady = true;
|
|
339
|
-
if (sessionId === undefined) {
|
|
345
|
+
} else {
|
|
340
346
|
const session = await rpc("session/new", sessionBindParams);
|
|
341
347
|
sessionId = typeof session.sessionId === "string" ? session.sessionId : undefined;
|
|
342
348
|
if (sessionId === undefined || sessionId === "") {
|
|
@@ -475,10 +475,17 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
475
475
|
let sessionId = explicitHostSessionId !== undefined && explicitHostSessionId !== ""
|
|
476
476
|
? explicitHostSessionId
|
|
477
477
|
: await config.sessionIdentity.load(request.principal);
|
|
478
|
+
if (request.continuation.kind === "resume" && (sessionId === undefined || sessionId === "")) {
|
|
479
|
+
outcome = failure(
|
|
480
|
+
"session",
|
|
481
|
+
"HeadlessSessionFailure",
|
|
482
|
+
"session-id-missing",
|
|
483
|
+
undefined,
|
|
484
|
+
"resume requires a bound session id",
|
|
485
|
+
);
|
|
486
|
+
} else {
|
|
478
487
|
let sessionKind: "new" | "resume" =
|
|
479
|
-
request.continuation.kind === "resume"
|
|
480
|
-
? "resume"
|
|
481
|
-
: "new";
|
|
488
|
+
request.continuation.kind === "resume" ? "resume" : "new";
|
|
482
489
|
if (sessionKind === "new" && !codex) {
|
|
483
490
|
sessionId = randomUUID();
|
|
484
491
|
await config.sessionIdentity.bind(request.principal, sessionId);
|
|
@@ -809,6 +816,7 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
809
816
|
...(request.home !== undefined ? { home: request.home } : {}),
|
|
810
817
|
});
|
|
811
818
|
}
|
|
819
|
+
}
|
|
812
820
|
} finally {
|
|
813
821
|
try {
|
|
814
822
|
await prepared.dispose?.();
|
package/src/host-contracts.ts
CHANGED
|
@@ -241,7 +241,6 @@ export interface DurablePrincipalAuthority {
|
|
|
241
241
|
* object. Public layers must not forge opaque principal shapes (#636).
|
|
242
242
|
*/
|
|
243
243
|
seal(coordinates: DurablePrincipalCoordinates): DurablePrincipal;
|
|
244
|
-
isAvailable(principal: DurablePrincipal): Promise<boolean>;
|
|
245
244
|
decode(principal: unknown): DurablePrincipalCoordinates;
|
|
246
245
|
}
|
|
247
246
|
|
package/src/host-descriptions.ts
CHANGED
|
@@ -23,7 +23,6 @@ export const HOST_DESCRIPTIONS: Readonly<Record<string, AcpHostDescription>> = O
|
|
|
23
23
|
modelFlag: "--model",
|
|
24
24
|
}),
|
|
25
25
|
modelPassing: "argv",
|
|
26
|
-
boundResume: "session/load",
|
|
27
26
|
sessionBindingFile: "grok-acp-session.json",
|
|
28
27
|
}),
|
|
29
28
|
/**
|
|
@@ -41,7 +40,6 @@ export const HOST_DESCRIPTIONS: Readonly<Record<string, AcpHostDescription>> = O
|
|
|
41
40
|
thinkingFlag: "--reasoning",
|
|
42
41
|
}),
|
|
43
42
|
modelPassing: "set_model",
|
|
44
|
-
boundResume: "session/load",
|
|
45
43
|
sessionBindingFile: "hermes-acp-session.json",
|
|
46
44
|
seatProfileSoul: Object.freeze({
|
|
47
45
|
flag: "-p",
|
|
@@ -214,9 +214,20 @@ export function createNativeNavigatorSessionFactory(deps?: {
|
|
|
214
214
|
}
|
|
215
215
|
if (outcome.kind === "failure") {
|
|
216
216
|
providerFailure = navigatorProviderFailureFromPublicTerminal(outcome);
|
|
217
|
+
const failedAttempts = outcome.decisiveFacts.failedAttempts;
|
|
218
|
+
const attemptReasons = Array.isArray(failedAttempts)
|
|
219
|
+
? failedAttempts.map((entry: unknown, index: number) => {
|
|
220
|
+
const fact = entry !== null && typeof entry === "object"
|
|
221
|
+
? entry as { attempt?: unknown; diagnostic?: unknown }
|
|
222
|
+
: undefined;
|
|
223
|
+
return `${typeof fact?.attempt === "number" ? fact.attempt + 1 : index + 1}: ${
|
|
224
|
+
typeof fact?.diagnostic === "string" ? fact.diagnostic : outcome.diagnostic
|
|
225
|
+
}`;
|
|
226
|
+
})
|
|
227
|
+
: [];
|
|
217
228
|
throw navigatorUnavailableError(
|
|
218
229
|
providerFailure.source,
|
|
219
|
-
new Error(outcome.diagnostic),
|
|
230
|
+
new Error(attemptReasons.length > 0 ? attemptReasons.join("\n") : outcome.diagnostic),
|
|
220
231
|
providerFailure.cause,
|
|
221
232
|
);
|
|
222
233
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { lstat } from "node:fs/promises";
|
|
2
1
|
import { join } from "node:path";
|
|
3
2
|
|
|
4
3
|
import type {
|
|
@@ -42,7 +41,7 @@ export function issuePiDurablePrincipalCoordinates(
|
|
|
42
41
|
};
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
/** Pi's durable-principal codec
|
|
44
|
+
/** Pi's durable-principal codec. */
|
|
46
45
|
export const piDurablePrincipalAuthority: DurablePrincipalAuthority = {
|
|
47
46
|
issue(request: NewDurablePrincipalRequest): DurablePrincipal {
|
|
48
47
|
const coordinates = issuePiDurablePrincipalCoordinates(request);
|
|
@@ -73,13 +72,4 @@ export const piDurablePrincipalAuthority: DurablePrincipalAuthority = {
|
|
|
73
72
|
: join(record.sessionDirectory, "session.jsonl"),
|
|
74
73
|
};
|
|
75
74
|
},
|
|
76
|
-
async isAvailable(principal: DurablePrincipal): Promise<boolean> {
|
|
77
|
-
const { sessionFile } = this.decode(principal);
|
|
78
|
-
try {
|
|
79
|
-
const stat = await lstat(sessionFile);
|
|
80
|
-
return stat.isFile() && !stat.isSymbolicLink();
|
|
81
|
-
} catch {
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
75
|
};
|
|
@@ -63,6 +63,6 @@ export function renderSystemPromptOverride(authority: {
|
|
|
63
63
|
export type SessionIdentityAuthority = Readonly<{
|
|
64
64
|
load(principal: RoleTurnRequest["principal"]): Promise<string | undefined>;
|
|
65
65
|
bind(principal: RoleTurnRequest["principal"], sessionId: string): Promise<void>;
|
|
66
|
-
/** Durable principal session path for layout ownership
|
|
66
|
+
/** Durable principal session path for layout ownership — not a rebuild source (#617 DK-4). */
|
|
67
67
|
resolveSessionFile(principal: RoleTurnRequest["principal"]): string;
|
|
68
68
|
}>;
|
|
@@ -53,7 +53,6 @@ const dummyIo: CliIo = { stdout: () => {}, stderr: () => {} };
|
|
|
53
53
|
*/
|
|
54
54
|
export async function persistReturnedRunState(
|
|
55
55
|
admitted: { runDirectory: string; principal?: DurablePrincipal },
|
|
56
|
-
authority: Pick<DurablePrincipalAuthority, "isAvailable">,
|
|
57
56
|
options?: { readonly lawful?: boolean },
|
|
58
57
|
): Promise<void> {
|
|
59
58
|
if (options?.lawful === true) {
|
|
@@ -65,10 +64,8 @@ export async function persistReturnedRunState(
|
|
|
65
64
|
});
|
|
66
65
|
const typedHttp429 = resumeObservation.typedHttp429;
|
|
67
66
|
if (admitted.principal !== undefined && typedHttp429 !== undefined) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
67
|
+
await markRunResumable(admitted.runDirectory, typedHttp429);
|
|
68
|
+
return;
|
|
72
69
|
}
|
|
73
70
|
await markRunTerminal(admitted.runDirectory);
|
|
74
71
|
}
|
|
@@ -391,16 +388,6 @@ export async function runWithAutoResumeLoop<
|
|
|
391
388
|
projectRoot: string;
|
|
392
389
|
};
|
|
393
390
|
principalAuthority: DurablePrincipalAuthority;
|
|
394
|
-
/**
|
|
395
|
-
* Host-aware "can this principal's turn still be resumed" probe (#840 P1).
|
|
396
|
-
* Callers that know the selected host pass
|
|
397
|
-
* session-identity.ts's resolveHostAwareSessionAvailability(host,
|
|
398
|
-
* principalAuthority) so an ACP/headless turn's own binding file — not
|
|
399
|
-
* pi's session.jsonl — decides continuation for that host. Defaults to
|
|
400
|
-
* principalAuthority.isAvailable (pi-only check) when omitted, so a
|
|
401
|
-
* caller that never leaves the pi host keeps identical behavior.
|
|
402
|
-
*/
|
|
403
|
-
isPrincipalAvailable?: (principal: DurablePrincipal) => Promise<boolean>;
|
|
404
391
|
io: CliIo;
|
|
405
392
|
sessionAppender: SessionCustomEntryAppender;
|
|
406
393
|
/**
|
|
@@ -433,9 +420,6 @@ export async function runWithAutoResumeLoop<
|
|
|
433
420
|
// `attempts >= limit` comparison (always false) — reject here, before any dispatch.
|
|
434
421
|
const limit = options.autoResumeLimit ?? AUTO_RESUME_LIMIT;
|
|
435
422
|
parseAutoResumeLimit(limit);
|
|
436
|
-
const isPrincipalAvailable =
|
|
437
|
-
options.isPrincipalAvailable ??
|
|
438
|
-
((principal: DurablePrincipal) => options.principalAuthority.isAvailable(principal));
|
|
439
423
|
let autoResumeAttempts = 0;
|
|
440
424
|
let isFirst = true;
|
|
441
425
|
let currentPayload = options.buildInitialPayload();
|
|
@@ -443,6 +427,14 @@ export async function runWithAutoResumeLoop<
|
|
|
443
427
|
let lastThrownError: unknown;
|
|
444
428
|
let everyAttemptThrew = true;
|
|
445
429
|
const retainedErrorFiles: string[] = [];
|
|
430
|
+
const failedAttempts: Array<{
|
|
431
|
+
attempt: number;
|
|
432
|
+
diagnostic: string;
|
|
433
|
+
decisiveFacts?: Readonly<Record<string, unknown>>;
|
|
434
|
+
errorFile?: string;
|
|
435
|
+
}> = [];
|
|
436
|
+
let stoppedResult: T | undefined;
|
|
437
|
+
let endReason: string | undefined;
|
|
446
438
|
|
|
447
439
|
while (true) {
|
|
448
440
|
let result: T | undefined;
|
|
@@ -465,6 +457,11 @@ export async function runWithAutoResumeLoop<
|
|
|
465
457
|
// diagnostic-sink-isolation precedent). When a lease is held inside
|
|
466
458
|
// dispatch, that path owns release in its own finally.
|
|
467
459
|
lastThrownError = error;
|
|
460
|
+
const failedAttempt: (typeof failedAttempts)[number] = {
|
|
461
|
+
attempt: dispatchOrdinal,
|
|
462
|
+
diagnostic: describeErrorIdentity(unwrapTurnDispatchedFailure(error)),
|
|
463
|
+
};
|
|
464
|
+
failedAttempts.push(failedAttempt);
|
|
468
465
|
turnStartedBeforeThrow = error instanceof TurnDispatchedFailure;
|
|
469
466
|
const attempt = dispatchOrdinal;
|
|
470
467
|
try {
|
|
@@ -479,6 +476,7 @@ export async function runWithAutoResumeLoop<
|
|
|
479
476
|
error,
|
|
480
477
|
);
|
|
481
478
|
retainedErrorFiles.push(file);
|
|
479
|
+
failedAttempt.errorFile = file;
|
|
482
480
|
options.io.stderr(
|
|
483
481
|
`dispatch attempt ${attempt} threw (${describeErrorIdentity(error)}); full error retained at ${file}\n`,
|
|
484
482
|
);
|
|
@@ -508,106 +506,31 @@ export async function runWithAutoResumeLoop<
|
|
|
508
506
|
const terminal = (result as { terminal?: TerminalResult }).terminal;
|
|
509
507
|
if (terminal !== undefined) {
|
|
510
508
|
(terminal as { autoResumeCount?: number }).autoResumeCount = autoResumeAttempts;
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
options.io.stdout(formatTerminalResult(terminal));
|
|
509
|
+
if (terminal.roleOutcome.kind === "failure") {
|
|
510
|
+
failedAttempts.push({
|
|
511
|
+
attempt: dispatchOrdinal - 1,
|
|
512
|
+
diagnostic: terminal.roleOutcome.diagnostic,
|
|
513
|
+
decisiveFacts: terminal.roleOutcome.decisiveFacts,
|
|
514
|
+
});
|
|
518
515
|
}
|
|
519
|
-
return result;
|
|
520
|
-
}
|
|
521
|
-
if (result.skipAutoResume === true) {
|
|
522
|
-
if (terminal !== undefined) presentTerminal(terminal, options.io);
|
|
523
|
-
return result;
|
|
524
516
|
}
|
|
525
|
-
// #855: process cancel — stop even if dispatch forgot skipAutoResume.
|
|
526
|
-
if (processCancelSignalName(options.signal) !== undefined) {
|
|
527
|
-
if (terminal !== undefined) presentTerminal(terminal, options.io);
|
|
528
|
-
return result;
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
517
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
if (
|
|
539
|
-
result.turnDispatched === true
|
|
540
|
-
&& !(await isPrincipalAvailable(options.admitted.principal))
|
|
541
|
-
) {
|
|
542
|
-
if (terminal !== undefined) presentTerminal(terminal, options.io);
|
|
543
|
-
return result;
|
|
518
|
+
if (terminal !== undefined && isLawfulTypedTerminalOutcome(terminal.roleOutcome)
|
|
519
|
+
|| result.skipAutoResume === true
|
|
520
|
+
|| autoResumeAttempts >= limit
|
|
521
|
+
|| processCancelSignalName(options.signal) !== undefined) {
|
|
522
|
+
stoppedResult = result;
|
|
523
|
+
break;
|
|
544
524
|
}
|
|
545
525
|
} else {
|
|
546
|
-
// Exception path: continue through the identical budget/session gates.
|
|
547
526
|
if (autoResumeAttempts >= limit) {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
dispatchExceptionFailureTerminal({
|
|
551
|
-
role: options.admitted.role,
|
|
552
|
-
runId: options.admitted.runId,
|
|
553
|
-
causeError: lastThrownError,
|
|
554
|
-
errorFiles: retainedErrorFiles,
|
|
555
|
-
autoResumeAttempts,
|
|
556
|
-
endReason: "auto-resume budget exhausted",
|
|
557
|
-
everyAttemptThrew,
|
|
558
|
-
}),
|
|
559
|
-
options.io,
|
|
560
|
-
);
|
|
561
|
-
await finalizeExceptionRunBestEffort(options.admitted.runDirectory, options.io);
|
|
562
|
-
presentTerminal(terminal, options.io);
|
|
563
|
-
return {
|
|
564
|
-
exitCode: 1,
|
|
565
|
-
terminal,
|
|
566
|
-
} as T;
|
|
527
|
+
endReason = "auto-resume budget exhausted";
|
|
528
|
+
break;
|
|
567
529
|
}
|
|
568
|
-
// #855: process cancel on the throw path — do not re-dispatch; name the signal.
|
|
569
530
|
const cancelName = processCancelSignalName(options.signal);
|
|
570
531
|
if (cancelName !== undefined) {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
dispatchExceptionFailureTerminal({
|
|
574
|
-
role: options.admitted.role,
|
|
575
|
-
runId: options.admitted.runId,
|
|
576
|
-
causeError: lastThrownError,
|
|
577
|
-
errorFiles: retainedErrorFiles,
|
|
578
|
-
autoResumeAttempts,
|
|
579
|
-
endReason: `ak-role terminated by ${cancelName}`,
|
|
580
|
-
everyAttemptThrew,
|
|
581
|
-
}),
|
|
582
|
-
options.io,
|
|
583
|
-
);
|
|
584
|
-
await finalizeExceptionRunBestEffort(options.admitted.runDirectory, options.io);
|
|
585
|
-
presentTerminal(terminal, options.io);
|
|
586
|
-
return {
|
|
587
|
-
exitCode: 1,
|
|
588
|
-
terminal,
|
|
589
|
-
} as T;
|
|
590
|
-
}
|
|
591
|
-
if (!(await isPrincipalAvailable(options.admitted.principal))) {
|
|
592
|
-
const terminal = await attachDispatchExceptionTerminal(
|
|
593
|
-
options.admitted,
|
|
594
|
-
dispatchExceptionFailureTerminal({
|
|
595
|
-
role: options.admitted.role,
|
|
596
|
-
runId: options.admitted.runId,
|
|
597
|
-
causeError: lastThrownError,
|
|
598
|
-
errorFiles: retainedErrorFiles,
|
|
599
|
-
autoResumeAttempts,
|
|
600
|
-
endReason: "session principal unavailable before further resume",
|
|
601
|
-
everyAttemptThrew,
|
|
602
|
-
}),
|
|
603
|
-
options.io,
|
|
604
|
-
);
|
|
605
|
-
await finalizeExceptionRunBestEffort(options.admitted.runDirectory, options.io);
|
|
606
|
-
presentTerminal(terminal, options.io);
|
|
607
|
-
return {
|
|
608
|
-
exitCode: 1,
|
|
609
|
-
terminal,
|
|
610
|
-
} as T;
|
|
532
|
+
endReason = `ak-role terminated by ${cancelName}`;
|
|
533
|
+
break;
|
|
611
534
|
}
|
|
612
535
|
}
|
|
613
536
|
|
|
@@ -621,4 +544,39 @@ export async function runWithAutoResumeLoop<
|
|
|
621
544
|
isFirst = false;
|
|
622
545
|
}
|
|
623
546
|
}
|
|
547
|
+
|
|
548
|
+
const terminal = stoppedResult === undefined
|
|
549
|
+
? await attachDispatchExceptionTerminal(
|
|
550
|
+
options.admitted,
|
|
551
|
+
dispatchExceptionFailureTerminal({
|
|
552
|
+
role: options.admitted.role,
|
|
553
|
+
runId: options.admitted.runId,
|
|
554
|
+
causeError: lastThrownError,
|
|
555
|
+
errorFiles: retainedErrorFiles,
|
|
556
|
+
autoResumeAttempts,
|
|
557
|
+
endReason: endReason!,
|
|
558
|
+
everyAttemptThrew,
|
|
559
|
+
}),
|
|
560
|
+
options.io,
|
|
561
|
+
)
|
|
562
|
+
: stoppedResult.terminal;
|
|
563
|
+
if (stoppedResult === undefined) {
|
|
564
|
+
await finalizeExceptionRunBestEffort(options.admitted.runDirectory, options.io);
|
|
565
|
+
}
|
|
566
|
+
const complete: TerminalResult | undefined = terminal !== undefined && failedAttempts.length > 0
|
|
567
|
+
? { ...terminal, roleOutcome: {
|
|
568
|
+
...terminal.roleOutcome,
|
|
569
|
+
decisiveFacts: { ...terminal.roleOutcome.decisiveFacts, failedAttempts },
|
|
570
|
+
} } as TerminalResult
|
|
571
|
+
: terminal;
|
|
572
|
+
if (complete !== undefined) {
|
|
573
|
+
if (isLawfulTypedTerminalOutcome(complete.roleOutcome)) {
|
|
574
|
+
options.io.stdout(formatTerminalResult(complete));
|
|
575
|
+
} else {
|
|
576
|
+
presentTerminal(complete, options.io);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
return stoppedResult === undefined
|
|
580
|
+
? { exitCode: 1, terminal: complete } as T
|
|
581
|
+
: { ...stoppedResult, ...(complete === undefined ? {} : { terminal: complete }) } as T;
|
|
624
582
|
}
|
|
@@ -946,7 +946,7 @@ export async function runPublicInstructionSeatResume(
|
|
|
946
946
|
};
|
|
947
947
|
let binding: Awaited<ReturnType<typeof readAuditorResumeBinding>>;
|
|
948
948
|
try {
|
|
949
|
-
const loaded = await loadResumablePublicRole(env.home, request.runId, env.principalAuthority
|
|
949
|
+
const loaded = await loadResumablePublicRole(env.home, request.runId, env.principalAuthority);
|
|
950
950
|
binding = loaded.admitted.role === "auditor"
|
|
951
951
|
? await readAuditorResumeBinding(loaded.admitted.runDirectory)
|
|
952
952
|
: undefined;
|
|
@@ -1014,7 +1014,7 @@ export async function continueParentAfterChild(
|
|
|
1014
1014
|
env: InstructionSeatRunEnv,
|
|
1015
1015
|
io: CliIo,
|
|
1016
1016
|
): Promise<SeatRunResult> {
|
|
1017
|
-
const loaded = await loadResumablePublicRole(env.home, parentRunId, env.principalAuthority
|
|
1017
|
+
const loaded = await loadResumablePublicRole(env.home, parentRunId, env.principalAuthority);
|
|
1018
1018
|
if (loaded.run.state !== "admitted" && !AUDITED_ROLES.has(loaded.admitted.role)) {
|
|
1019
1019
|
return runPublicInstructionSeatResume({ runId: parentRunId }, env, io);
|
|
1020
1020
|
}
|
|
@@ -1215,7 +1215,7 @@ async function auditSubmittedRole(
|
|
|
1215
1215
|
?? (escalation?.runDirectory === undefined
|
|
1216
1216
|
? undefined : runIdFromRunDirectory(escalation.runDirectory));
|
|
1217
1217
|
if (escalatedRunId === undefined) throw new Error("escalated audit has no resumable run identity");
|
|
1218
|
-
const officer = await loadResumablePublicRole(env.home, escalatedRunId, env.principalAuthority
|
|
1218
|
+
const officer = await loadResumablePublicRole(env.home, escalatedRunId, env.principalAuthority);
|
|
1219
1219
|
const terminal = await trySettlePublicSeat(officer.admitted, env.principalAuthority, undefined);
|
|
1220
1220
|
if (terminal === undefined) throw new Error("escalated audit has no terminal result");
|
|
1221
1221
|
io.stdout(formatTerminalResult(terminal));
|
|
@@ -29,10 +29,7 @@ import { readRecordedSubmissionRows } from "../submission-ledger.ts";
|
|
|
29
29
|
import { readDeclaredTicketNumber } from "../run-ticket-number.ts";
|
|
30
30
|
import { pathContainedIn } from "../activation-ledger-topology.ts";
|
|
31
31
|
import { pickEngineAxis } from "../package-resources/engine-material.ts";
|
|
32
|
-
import {
|
|
33
|
-
readStoredHostSessionId,
|
|
34
|
-
resolveHostAwareSessionAvailability,
|
|
35
|
-
} from "../session-identity.ts";
|
|
32
|
+
import { readStoredHostSessionId } from "../session-identity.ts";
|
|
36
33
|
import { rewriteRunDirectoryPathValue } from "../role-run-relocation.ts";
|
|
37
34
|
|
|
38
35
|
import type {
|
|
@@ -525,17 +522,11 @@ export async function presentControlledFailure<
|
|
|
525
522
|
...(session === undefined ? {} : { session }),
|
|
526
523
|
});
|
|
527
524
|
|
|
528
|
-
// #665 / #416:
|
|
529
|
-
|
|
530
|
-
let resumable = false;
|
|
531
|
-
const typedHttp429 = resumeObservation.typedHttp429;
|
|
532
|
-
if (admitted.principal !== undefined) {
|
|
533
|
-
const sessionPrincipalAvailable = await authority.isAvailable(admitted.principal);
|
|
534
|
-
resumable = sessionPrincipalAvailable && typedHttp429 !== undefined;
|
|
535
|
-
}
|
|
525
|
+
// #665 / #416: typed 429 with a run principal offers resume; the host decides availability.
|
|
526
|
+
const resumable = admitted.principal !== undefined && resumeObservation.typedHttp429 !== undefined;
|
|
536
527
|
|
|
537
528
|
if (persistRunState && !failureInput.skipRunStateWrite) {
|
|
538
|
-
await persistReturnedRunState(admitted
|
|
529
|
+
await persistReturnedRunState(admitted);
|
|
539
530
|
}
|
|
540
531
|
|
|
541
532
|
let publishedErrorPath: string | undefined;
|
|
@@ -638,7 +629,7 @@ async function settleDeferredPersist<
|
|
|
638
629
|
const { needsPersist: _needsPersist, ...settledResult } = result;
|
|
639
630
|
const lawful = isLawfulTypedTerminalOutcome(result.terminal.roleOutcome);
|
|
640
631
|
try {
|
|
641
|
-
await persistReturnedRunState(admitted,
|
|
632
|
+
await persistReturnedRunState(admitted, lawful ? { lawful: true } : undefined);
|
|
642
633
|
return settledResult;
|
|
643
634
|
} catch (error) {
|
|
644
635
|
const failed = await settleAfterTurnStarted(
|
|
@@ -1136,7 +1127,7 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1136
1127
|
}
|
|
1137
1128
|
if (persistRunState) {
|
|
1138
1129
|
try {
|
|
1139
|
-
await persistReturnedRunState(admitted,
|
|
1130
|
+
await persistReturnedRunState(admitted, { lawful: true });
|
|
1140
1131
|
} catch (error) {
|
|
1141
1132
|
await recordBestEffortPostDispatchDiagnostic(
|
|
1142
1133
|
admitted,
|
|
@@ -1241,7 +1232,7 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1241
1232
|
}
|
|
1242
1233
|
if (persistRunState) {
|
|
1243
1234
|
try {
|
|
1244
|
-
await persistReturnedRunState(admitted,
|
|
1235
|
+
await persistReturnedRunState(admitted, { lawful: true });
|
|
1245
1236
|
} catch (error) {
|
|
1246
1237
|
await recordBestEffortPostDispatchDiagnostic(
|
|
1247
1238
|
admitted,
|
|
@@ -1634,10 +1625,6 @@ export async function runPostAdmissionSeatResume<
|
|
|
1634
1625
|
return await runWithAutoResumeLoop({
|
|
1635
1626
|
admitted: loaded.admitted,
|
|
1636
1627
|
principalAuthority: env.principalAuthority,
|
|
1637
|
-
isPrincipalAvailable: resolveHostAwareSessionAvailability(
|
|
1638
|
-
env.host,
|
|
1639
|
-
env.principalAuthority,
|
|
1640
|
-
),
|
|
1641
1628
|
io: input.io,
|
|
1642
1629
|
sessionAppender: env.sessionAppender,
|
|
1643
1630
|
autoResumeLimit: env.autoResumeLimit,
|
|
@@ -1913,7 +1900,6 @@ export async function runPostAdmissionResumable<
|
|
|
1913
1900
|
return runWithAutoResumeLoop({
|
|
1914
1901
|
admitted,
|
|
1915
1902
|
principalAuthority: env.principalAuthority,
|
|
1916
|
-
isPrincipalAvailable: resolveHostAwareSessionAvailability(env.host, env.principalAuthority),
|
|
1917
1903
|
io,
|
|
1918
1904
|
sessionAppender: env.sessionAppender,
|
|
1919
1905
|
autoResumeLimit: env.autoResumeLimit,
|
|
@@ -687,17 +687,6 @@ export async function clearCurrentCourt(
|
|
|
687
687
|
});
|
|
688
688
|
}
|
|
689
689
|
|
|
690
|
-
/**
|
|
691
|
-
* True when the host authority reports the durable principal available.
|
|
692
|
-
* Resume must reopen this exact principal; directory-latest is not identity.
|
|
693
|
-
*/
|
|
694
|
-
export async function isDurablePrincipalAvailable(
|
|
695
|
-
principal: DurablePrincipal,
|
|
696
|
-
authority: DurablePrincipalAuthority,
|
|
697
|
-
): Promise<boolean> {
|
|
698
|
-
return authority.isAvailable(principal);
|
|
699
|
-
}
|
|
700
|
-
|
|
701
690
|
export class RunWriterLeaseHeldError extends Error {
|
|
702
691
|
readonly code = "AK_RUN_WRITER_LEASE_HELD" as const;
|
|
703
692
|
constructor(message = "role run writer lease is already held") {
|
|
@@ -1325,7 +1314,6 @@ async function loadResumableRunRecord(
|
|
|
1325
1314
|
home: string,
|
|
1326
1315
|
runId: string,
|
|
1327
1316
|
authority: DurablePrincipalAuthority,
|
|
1328
|
-
allowUnformedAdmitted = false,
|
|
1329
1317
|
): Promise<{
|
|
1330
1318
|
readonly run: RoleRunRecord;
|
|
1331
1319
|
readonly principal: DurablePrincipal;
|
|
@@ -1340,19 +1328,14 @@ async function loadResumableRunRecord(
|
|
|
1340
1328
|
if (disk === undefined) {
|
|
1341
1329
|
throw new CliUsageError(`unknown role run id: ${runId}`);
|
|
1342
1330
|
}
|
|
1343
|
-
// #416: removed terminal/resumable
|
|
1344
|
-
//
|
|
1331
|
+
// #416 / #1091: removed terminal/resumable and local session-file gates.
|
|
1332
|
+
// The load seam locates the run and recovers identity; host CLI answers continuation.
|
|
1345
1333
|
// One authority.decode of the uninterpreted wire yields both principal and record.
|
|
1346
1334
|
const materialized = materializeRoleRunFromDisk(disk, authority);
|
|
1347
1335
|
if (materialized === undefined) {
|
|
1348
1336
|
throw new CliUsageError(`unknown role run id: ${runId}`);
|
|
1349
1337
|
}
|
|
1350
1338
|
const { run, principal } = materialized;
|
|
1351
|
-
if (!(allowUnformedAdmitted && run.state === "admitted") && !(await isDurablePrincipalAvailable(principal, authority))) {
|
|
1352
|
-
throw new CliUsageError(
|
|
1353
|
-
`role run Pi session principal is unavailable: ${runId}`,
|
|
1354
|
-
);
|
|
1355
|
-
}
|
|
1356
1339
|
// Reconstruct admitted identity from durable run record + admitted-request.json.
|
|
1357
1340
|
let instruction = "";
|
|
1358
1341
|
let instructionEmpty = true;
|
|
@@ -1755,9 +1738,8 @@ export async function loadResumablePublicRole(
|
|
|
1755
1738
|
home: string,
|
|
1756
1739
|
runId: string,
|
|
1757
1740
|
authority: DurablePrincipalAuthority,
|
|
1758
|
-
allowUnformedAdmitted = false,
|
|
1759
1741
|
): Promise<LoadedResumablePublicRole> {
|
|
1760
|
-
const loaded = await loadResumableRunRecord(home, runId, authority
|
|
1742
|
+
const loaded = await loadResumableRunRecord(home, runId, authority);
|
|
1761
1743
|
return seatLoadedResult(loaded, admitResumedRole(loaded));
|
|
1762
1744
|
}
|
|
1763
1745
|
|
|
@@ -278,8 +278,10 @@ export function formatTerminalResult(result: TerminalResult): string {
|
|
|
278
278
|
`diagnostic\t${encodeTerminalField(result.roleOutcome.diagnostic)}`,
|
|
279
279
|
);
|
|
280
280
|
}
|
|
281
|
-
if (result.roleOutcome.kind === "failure" || result.roleOutcome.kind === "no_receipt" || result.roleOutcome.kind === "audit_escalation") {
|
|
282
|
-
const facts = result.roleOutcome.
|
|
281
|
+
if (result.roleOutcome.kind === "failure" || result.roleOutcome.kind === "no_receipt" || result.roleOutcome.kind === "audit_escalation" || result.roleOutcome.decisiveFacts?.failedAttempts !== undefined) {
|
|
282
|
+
const facts = result.roleOutcome.kind === "accepted"
|
|
283
|
+
? { failedAttempts: result.roleOutcome.decisiveFacts?.failedAttempts }
|
|
284
|
+
: result.roleOutcome.decisiveFacts ?? {};
|
|
283
285
|
for (const [key, value] of Object.entries(facts)) {
|
|
284
286
|
if (value === undefined) continue;
|
|
285
287
|
const rendered =
|
package/src/role-envelope.ts
CHANGED
|
@@ -78,8 +78,8 @@ export async function prepareRoleEnvelope(options: {
|
|
|
78
78
|
readonly listTerminatingToolOnMcp?: boolean;
|
|
79
79
|
/**
|
|
80
80
|
* Durable principal session path (header layout only).
|
|
81
|
-
* Production passes DurablePrincipalAuthority.decode(principal).sessionFile
|
|
82
|
-
*
|
|
81
|
+
* Production passes DurablePrincipalAuthority.decode(principal).sessionFile.
|
|
82
|
+
* Tests may omit → runDirectory default.
|
|
83
83
|
*/
|
|
84
84
|
readonly sessionFile?: string;
|
|
85
85
|
}): Promise<PreparedRoleTurn> {
|
|
@@ -113,7 +113,7 @@ export async function prepareRoleEnvelope(options: {
|
|
|
113
113
|
const runId = request.runDirectory.split("/").filter(Boolean).at(-1) ?? randomUUID();
|
|
114
114
|
await mkdir(request.runDirectory, { recursive: true });
|
|
115
115
|
|
|
116
|
-
// Durable principal file for
|
|
116
|
+
// Durable principal file for package-owned session entries.
|
|
117
117
|
// #617 DK-4: never host conversation/tool writeback into Pi JSONL.
|
|
118
118
|
// #959: package-owned custom entries (invocation marker, submission-closure,
|
|
119
119
|
// navigator attendance, no-receipt) MUST flush so parent settlement can read
|
package/src/session-identity.ts
CHANGED
|
@@ -42,28 +42,6 @@ export function createSessionIdentityAuthority(
|
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
/**
|
|
46
|
-
* Host-aware resumable-session probe (#840 P1). The shared auto-resume loop
|
|
47
|
-
* (public-cli/auto-resume.ts) asks one question — "can this principal's turn
|
|
48
|
-
* still be resumed" — without itself knowing which concrete host executed it.
|
|
49
|
-
* Pi's own session.jsonl transcript (DurablePrincipalAuthority#isAvailable)
|
|
50
|
-
* answers that only for the pi host; ACP and headless hosts persist their
|
|
51
|
-
* resumable native session id under their own session-identity binding file
|
|
52
|
-
* instead (#729 / #731 / #645) and never write session.jsonl, so the plain
|
|
53
|
-
* pi check always reports them unavailable after one attempt. One lookup per
|
|
54
|
-
* registered host family — no per-host-name branch, no per-seat whitelist.
|
|
55
|
-
*/
|
|
56
|
-
export function resolveHostAwareSessionAvailability(
|
|
57
|
-
host: string | undefined,
|
|
58
|
-
principalAuthority: DurablePrincipalAuthority,
|
|
59
|
-
): (principal: DurablePrincipal) => Promise<boolean> {
|
|
60
|
-
if (host === undefined || host === DEFAULT_ROLE_TURN_HOST) {
|
|
61
|
-
return (principal) => principalAuthority.isAvailable(principal);
|
|
62
|
-
}
|
|
63
|
-
// Unregistered names have no binding file: absence, not a guess at pi.
|
|
64
|
-
return async (principal) =>
|
|
65
|
-
(await readStoredHostSessionId(host, principalAuthority, principal)) !== undefined;
|
|
66
|
-
}
|
|
67
45
|
|
|
68
46
|
/**
|
|
69
47
|
* Native session/thread id already stored for this principal on `host`.
|