@akagilnc/pi-workflow-roles 0.1.5245 → 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 +125 -217
- package/dist/headless-host/production-host.js +381 -470
- package/dist/host-descriptions.js +0 -2
- package/dist/navigator-invocation-identity.js +0 -57
- 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 +86 -206
- package/dist/public-cli/post-admission.js +7 -15
- package/dist/public-cli/run-lifecycle.js +5 -15
- package/dist/public-cli/settlement.js +10 -59
- 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-invocation-identity.ts +4 -121
- 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/settlement.ts +8 -75
- package/src/public-cli/terminal.ts +4 -2
- package/src/role-envelope.ts +3 -3
- package/src/role-runtime.ts +23 -5
- package/src/session-identity.ts +0 -22
|
@@ -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
|
|
|
@@ -88,11 +88,7 @@ import {
|
|
|
88
88
|
findLatestDurablePackagedRoleTerminal,
|
|
89
89
|
hasNavigatorInfrastructureFailureBase,
|
|
90
90
|
isAcceptedPackagedRoleTerminalResult,
|
|
91
|
-
isReceiptSettlementBindingClear,
|
|
92
|
-
NAVIGATOR_INVOCATION_ENTRY,
|
|
93
91
|
NAVIGATOR_ROUTE_PLAYBOOK_FAILURE_ENTRY,
|
|
94
|
-
parseInvocationMarkerIdentity,
|
|
95
|
-
type InvocationMarkerIdentity,
|
|
96
92
|
} from "../navigator-invocation-identity.ts";
|
|
97
93
|
import {
|
|
98
94
|
NO_RECEIPT_LIFECYCLE_ENTRY_TYPE,
|
|
@@ -1921,23 +1917,6 @@ export async function appendRunAttemptHistory(
|
|
|
1921
1917
|
} catch {}
|
|
1922
1918
|
}
|
|
1923
1919
|
|
|
1924
|
-
/**
|
|
1925
|
-
* Minimal attendance provenance against the bound marker (ADR 0043).
|
|
1926
|
-
* Keep only invocationId + post-terminal ordering. Runtime-self-produced
|
|
1927
|
-
* role/phase/subject/version are not re-reconciled here (ADR 0042).
|
|
1928
|
-
*/
|
|
1929
|
-
function navigatorAttendanceCorrelatedWithBoundMarker(
|
|
1930
|
-
details: Record<string, unknown>,
|
|
1931
|
-
attendanceIndex: number,
|
|
1932
|
-
terminalIndex: number,
|
|
1933
|
-
marker: InvocationMarkerIdentity,
|
|
1934
|
-
): boolean {
|
|
1935
|
-
if (attendanceIndex <= terminalIndex) return false;
|
|
1936
|
-
// Exact current invocation token is the bound marker principal.
|
|
1937
|
-
if (details.invocationId !== marker.invocationId) return false;
|
|
1938
|
-
return true;
|
|
1939
|
-
}
|
|
1940
|
-
|
|
1941
1920
|
function parseNavigatorAttendanceDetails(
|
|
1942
1921
|
details: Record<string, unknown>,
|
|
1943
1922
|
): TerminalNavigatorFact {
|
|
@@ -2216,10 +2195,9 @@ export function extractNavigatorFact(
|
|
|
2216
2195
|
function extractNavigatorAttendanceFact(
|
|
2217
2196
|
entries: readonly SessionEntry[],
|
|
2218
2197
|
): TerminalNavigatorFact {
|
|
2219
|
-
// Affirmative attendance only. Missing /
|
|
2220
|
-
//
|
|
2221
|
-
//
|
|
2222
|
-
// receipt settlement (isReceiptSettlementBindingClear), not attendance extraction.
|
|
2198
|
+
// Affirmative attendance only. Missing / unparseable is never no-advice.
|
|
2199
|
+
// The shared lifecycle places the settled event on this submission's closure;
|
|
2200
|
+
// an unrelated attendance message cannot supply another call's result.
|
|
2223
2201
|
const terminal = findLatestDurablePackagedRoleTerminal(entries);
|
|
2224
2202
|
if (terminal === undefined) {
|
|
2225
2203
|
return {
|
|
@@ -2229,57 +2207,12 @@ function extractNavigatorAttendanceFact(
|
|
|
2229
2207
|
};
|
|
2230
2208
|
}
|
|
2231
2209
|
|
|
2232
|
-
let
|
|
2233
|
-
for (let i = terminal.index - 1; i >= 0; i -= 1) {
|
|
2234
|
-
const entry = entries[i];
|
|
2235
|
-
if (entry?.type === "custom" && entry.customType === NAVIGATOR_INVOCATION_ENTRY) {
|
|
2236
|
-
markerIndex = i;
|
|
2237
|
-
break;
|
|
2238
|
-
}
|
|
2239
|
-
}
|
|
2240
|
-
if (markerIndex < 0) {
|
|
2241
|
-
return {
|
|
2242
|
-
disposition: "unavailable",
|
|
2243
|
-
source: "unknown",
|
|
2244
|
-
reason: "Navigator attendance is uncorrelated with session invocation facts",
|
|
2245
|
-
};
|
|
2246
|
-
}
|
|
2247
|
-
const marker = parseInvocationMarkerIdentity(entries[markerIndex]?.data);
|
|
2248
|
-
if (marker === undefined) {
|
|
2249
|
-
return {
|
|
2250
|
-
disposition: "unavailable",
|
|
2251
|
-
source: "unknown",
|
|
2252
|
-
reason: "Navigator attendance is uncorrelated with session invocation facts",
|
|
2253
|
-
};
|
|
2254
|
-
}
|
|
2255
|
-
|
|
2256
|
-
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
2210
|
+
for (let i = terminal.index; i < entries.length; i += 1) {
|
|
2257
2211
|
const entry = entries[i];
|
|
2258
|
-
if (entry?.type
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
disposition: "unavailable",
|
|
2263
|
-
source: "unknown",
|
|
2264
|
-
reason: "Navigator attendance is unparseable",
|
|
2265
|
-
};
|
|
2266
|
-
}
|
|
2267
|
-
if (
|
|
2268
|
-
!navigatorAttendanceCorrelatedWithBoundMarker(
|
|
2269
|
-
details,
|
|
2270
|
-
i,
|
|
2271
|
-
terminal.index,
|
|
2272
|
-
marker,
|
|
2273
|
-
)
|
|
2274
|
-
) {
|
|
2275
|
-
return {
|
|
2276
|
-
disposition: "unavailable",
|
|
2277
|
-
source: "unknown",
|
|
2278
|
-
reason: "Navigator attendance is uncorrelated with session invocation facts",
|
|
2279
|
-
};
|
|
2280
|
-
}
|
|
2281
|
-
return parseNavigatorAttendanceDetails(details);
|
|
2282
|
-
}
|
|
2212
|
+
if (entry?.type !== "custom" || entry.customType !== "ak-role-submission-closure") continue;
|
|
2213
|
+
const details = isRecord(entry.data) ? entry.data.navigator : undefined;
|
|
2214
|
+
if (!isRecord(details)) break;
|
|
2215
|
+
return parseNavigatorAttendanceDetails(details);
|
|
2283
2216
|
}
|
|
2284
2217
|
// Absence is not successful no-advice — require affirmative typed attendance.
|
|
2285
2218
|
return {
|
|
@@ -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/role-runtime.ts
CHANGED
|
@@ -523,7 +523,7 @@ export async function projectClosedSubmissionLifecycle(
|
|
|
523
523
|
context: HostContext,
|
|
524
524
|
phase: NavigatorPhase,
|
|
525
525
|
recordAccepted: () => void,
|
|
526
|
-
settle: (settlement: NavigatorSettlement | undefined) => Promise<
|
|
526
|
+
settle: (settlement: NavigatorSettlement | undefined) => Promise<NavigatorEvent | undefined>,
|
|
527
527
|
): Promise<void> {
|
|
528
528
|
recordAccepted();
|
|
529
529
|
const closure = {
|
|
@@ -531,8 +531,17 @@ export async function projectClosedSubmissionLifecycle(
|
|
|
531
531
|
isError: false,
|
|
532
532
|
details: closed.accepted,
|
|
533
533
|
};
|
|
534
|
-
|
|
535
|
-
|
|
534
|
+
let navigator: NavigatorEvent | undefined;
|
|
535
|
+
try {
|
|
536
|
+
navigator = await settle(publicNavigatorSettlement(closed.role, phase, closure));
|
|
537
|
+
} finally {
|
|
538
|
+
// A Navigator failure cannot erase an already accepted role closure.
|
|
539
|
+
// Missing attendance remains a typed unavailable at public settlement.
|
|
540
|
+
context.sessionManager.appendCustomEntry?.("ak-role-submission-closure", {
|
|
541
|
+
...closure,
|
|
542
|
+
...(navigator === undefined ? {} : { navigator }),
|
|
543
|
+
});
|
|
544
|
+
}
|
|
536
545
|
}
|
|
537
546
|
|
|
538
547
|
/**
|
|
@@ -1032,6 +1041,8 @@ export function createRoleRuntimeExtension(
|
|
|
1032
1041
|
let navigatorAttendance: NavigatorAttendanceDependency | undefined;
|
|
1033
1042
|
// #351: session-lifecycle owner for periodic OAuth refresh (orthogonal to role admission).
|
|
1034
1043
|
let pendingNavigatorPresentation: { event: import("./navigator-attendance.ts").NavigatorEvent; report: import("./navigator-attendance.ts").NavigatorReport } | undefined;
|
|
1044
|
+
let navigatorActivation = 0;
|
|
1045
|
+
let navigatorDeliveryClosed = false;
|
|
1035
1046
|
let pendingNavigatorSettlement: Promise<void> | undefined;
|
|
1036
1047
|
let navigatorWorkContext: NavigatorWorkContext | undefined;
|
|
1037
1048
|
let navigatorSessionParent: string | undefined;
|
|
@@ -1085,7 +1096,7 @@ export function createRoleRuntimeExtension(
|
|
|
1085
1096
|
}
|
|
1086
1097
|
void Promise.resolve(pending).then(undefined, recordDisposeFailure);
|
|
1087
1098
|
};
|
|
1088
|
-
const settleNavigatorProjection = async (settlement: NavigatorSettlement | undefined) => {
|
|
1099
|
+
const settleNavigatorProjection = async (settlement: NavigatorSettlement | undefined): Promise<NavigatorEvent | undefined> => {
|
|
1089
1100
|
const attendance = navigatorAttendance;
|
|
1090
1101
|
if (settlement === undefined || attendance === undefined) return;
|
|
1091
1102
|
const workContext = navigatorWorkContext;
|
|
@@ -1099,6 +1110,7 @@ export function createRoleRuntimeExtension(
|
|
|
1099
1110
|
void settlePromise.catch(() => undefined);
|
|
1100
1111
|
const raced = await raceNavigatorGrace(settlePromise, NAVIGATOR_POST_ROLE_GRACE_MS);
|
|
1101
1112
|
if (raced.status !== "timeout") return;
|
|
1113
|
+
navigatorDeliveryClosed = true;
|
|
1102
1114
|
if (pendingNavigatorPresentation === undefined) {
|
|
1103
1115
|
const report: NavigatorReport = {
|
|
1104
1116
|
disposition: "unavailable",
|
|
@@ -1124,6 +1136,7 @@ export function createRoleRuntimeExtension(
|
|
|
1124
1136
|
})();
|
|
1125
1137
|
pendingNavigatorSettlement = pending;
|
|
1126
1138
|
await pending;
|
|
1139
|
+
return pendingNavigatorPresentation?.event;
|
|
1127
1140
|
};
|
|
1128
1141
|
projectClosedSubmission = async (closed, context) => projectClosedSubmissionLifecycle(
|
|
1129
1142
|
closed,
|
|
@@ -1695,6 +1708,8 @@ export function createRoleRuntimeExtension(
|
|
|
1695
1708
|
receiptDelivery = createReceiptDeliveryPolicy();
|
|
1696
1709
|
noReceiptRecorded = false;
|
|
1697
1710
|
pendingNavigatorPresentation = undefined;
|
|
1711
|
+
navigatorActivation += 1;
|
|
1712
|
+
navigatorDeliveryClosed = false;
|
|
1698
1713
|
pendingNavigatorSettlement = undefined;
|
|
1699
1714
|
pendingInfrastructureFailures.clear();
|
|
1700
1715
|
pendingSubmissionNonPassByToolCallId.clear();
|
|
@@ -1811,6 +1826,7 @@ export function createRoleRuntimeExtension(
|
|
|
1811
1826
|
});
|
|
1812
1827
|
} catch {}
|
|
1813
1828
|
}
|
|
1829
|
+
const activation = navigatorActivation;
|
|
1814
1830
|
navigatorAttendance = await dependencies.createNavigatorAttendance({
|
|
1815
1831
|
context: ctx,
|
|
1816
1832
|
role: entry.role,
|
|
@@ -1821,7 +1837,9 @@ export function createRoleRuntimeExtension(
|
|
|
1821
1837
|
invocationId,
|
|
1822
1838
|
...(contextError === undefined ? {} : { contextError }),
|
|
1823
1839
|
onEvent: (navigatorEvent, report) => {
|
|
1824
|
-
|
|
1840
|
+
if (activation === navigatorActivation && !navigatorDeliveryClosed) {
|
|
1841
|
+
pendingNavigatorPresentation = { event: navigatorEvent, report };
|
|
1842
|
+
}
|
|
1825
1843
|
},
|
|
1826
1844
|
});
|
|
1827
1845
|
// Concrete work context starts standby attendance (record only, no model).
|
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`.
|