@akagilnc/pi-workflow-roles 0.1.4586 → 0.1.4631
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/README.md +2 -2
- package/README.zh-CN.md +3 -3
- package/dist/acp-host/production-host.js +1184 -717
- package/dist/activation-ledger-topology.js +0 -4
- package/dist/archivist-record-topology.js +27 -35
- package/dist/headless-host/description.js +7 -4
- package/dist/headless-host/production-host.js +998 -595
- package/dist/migrate-book-topology.js +3 -6
- package/dist/navigator-attendance.js +146 -241
- package/dist/navigator-public-session.js +81 -15
- package/dist/package-contracts/navigator-output.js +30 -12
- package/dist/pi/role-turn-host.js +0 -3
- package/dist/public-cli/auto-resume.js +26 -0
- package/dist/public-cli/judge-run.js +2 -2
- package/dist/public-cli/main.js +1119 -870
- package/dist/public-cli/option-definitions.js +1 -1
- package/dist/public-cli/post-admission.js +103 -9
- package/dist/public-cli/process-cancel.js +57 -0
- package/dist/public-cli/run-lifecycle.js +29 -7
- package/dist/public-cli/settlement.js +46 -32
- package/dist/public-cli/terminal.js +8 -27
- package/dist/public-role-summons.js +22 -4
- package/dist/submission-ledger.js +56 -23
- package/package.json +1 -1
- package/resources/836-deleted-machine-instruction-inventory.md +2 -0
- package/resources/navigator-route-playbook.md +9 -0
- package/scripts/build-package.mjs +6 -2
- package/src/acp-host/role-turn-host.ts +129 -13
- package/src/activation-ledger-topology.ts +1 -6
- package/src/activation-ledger.ts +5 -236
- package/src/analyst-entry.ts +19 -2
- package/src/analyst-ledger.ts +111 -6
- package/src/archivist-record-entry.ts +1 -1
- package/src/headless-host/description.ts +17 -7
- package/src/headless-host/role-turn-host.ts +42 -11
- package/src/navigator-attendance.ts +290 -425
- package/src/navigator-public-session.ts +126 -25
- package/src/navigator-role.ts +2 -2
- package/src/package-contracts/navigator-output.ts +41 -25
- package/src/pi/role-turn-host.ts +0 -3
- package/src/public-cli/auto-resume.ts +34 -0
- package/src/public-cli/cli.ts +30 -0
- package/src/public-cli/coder-run.ts +2 -2
- package/src/public-cli/fixer-run.ts +2 -2
- package/src/public-cli/judge-run.ts +2 -2
- package/src/public-cli/main.ts +25 -3
- package/src/public-cli/merger-run.ts +2 -2
- package/src/public-cli/option-definitions.ts +1 -1
- package/src/public-cli/post-admission.ts +175 -7
- package/src/public-cli/process-cancel.ts +75 -0
- package/src/public-cli/run-lifecycle.ts +37 -7
- package/src/public-cli/settlement.ts +42 -33
- package/src/public-cli/terminal.ts +12 -37
- package/src/public-role-summons.ts +34 -5
- package/src/role-envelope.ts +89 -4
- package/src/role-runtime.ts +78 -65
- package/src/submission-ledger.ts +63 -21
- package/dist/activation-reconciliation.js +0 -61
- package/dist/public-cli/command-renderer.js +0 -5
- package/dist/public-command-renderer.js +0 -29
- package/src/activation-reconciliation.ts +0 -163
- package/src/public-cli/command-renderer.ts +0 -9
- package/src/public-command-renderer.ts +0 -55
|
@@ -1050,7 +1050,7 @@ const ROLE_COMMAND_HELP = {
|
|
|
1050
1050
|
},
|
|
1051
1051
|
navigator: {
|
|
1052
1052
|
command: "navigator",
|
|
1053
|
-
summary: "Direct Navigator (游奕使) route advice
|
|
1053
|
+
summary: "Direct Navigator (游奕使) free-form prose route advice.",
|
|
1054
1054
|
usage: ["ak-role navigator [options] [instruction]"],
|
|
1055
1055
|
examples: [
|
|
1056
1056
|
'ak-role navigator "刚完成 coder apply 收敛,下一步?"',
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
9
|
import { writeFile } from "node:fs/promises";
|
|
10
10
|
import { isAbsolute, join, resolve } from "node:path";
|
|
11
|
-
import { buildResumeContinuationPrompt, RESUME_TRANSPORT_ENVELOPE, } from "./run-lifecycle.js";
|
|
11
|
+
import { buildAutoResumeContinuationPrompt, buildResumeContinuationPrompt, RESUME_TRANSPORT_ENVELOPE, } from "./run-lifecycle.js";
|
|
12
12
|
import { CliUsageError } from "./cli-errors.js";
|
|
13
13
|
import { bindAdmittedTicketNumber, buildInstructionTransportPrompt, freezeAttachmentsIntoRun, } from "./invocation.js";
|
|
14
14
|
import { readRecordedSubmissionRows } from "../submission-ledger.js";
|
|
@@ -37,10 +37,36 @@ import { mintEngineDetourInvocationScope, readInvocationSelectedHost, withEngine
|
|
|
37
37
|
import { projectHostTransitionPriorNative } from "../host-transition-prior-native.js";
|
|
38
38
|
import { missingCredentialPreDispatchFailure, postRunMissingCredentialFailure, } from "./public-run-credentials.js";
|
|
39
39
|
import { acquireRunWriterLease, clearCurrentCourt, clearTypedProviderHttpObservation, describeErrorIdentity, markRunRunning, readCurrentCourt, recordCurrentCourt, renderResumeCommand, RunWriterLeaseHeldError, } from "./run-lifecycle.js";
|
|
40
|
+
import { processCancelDiagnostic, processCancelSignalName, } from "./process-cancel.js";
|
|
40
41
|
import { homeFromRunDirectory } from "../activation-ledger-topology.js";
|
|
41
42
|
import { attemptProducedFreshSubmission, classifyPostAdmissionFailure, controlledFailureInputFromResolution, exitCodeForTerminalOutcome, explicitInternalKnownFailureClassificationInput, formatCliDiagnostic, formatTerminalResult, inspectJudgeSession, isLawfulTypedTerminalOutcome, presentFailureTerminal, presentStructuralRejection, resolveAuditedRunnerFailureResolution, resolveControlledFailureResumeObservation, settleFailureTerminalResult, settleHostEndedNoReceipt, attachRecordedSubmissions, } from "./settlement.js";
|
|
42
43
|
import {} from "./terminal.js";
|
|
43
44
|
import { ensureRealArtifactsDirectory, persistReturnedRunState, runWithAutoResumeLoop, TurnDispatchedFailure, } from "./auto-resume.js";
|
|
45
|
+
/** #855: process-cancel settlement never re-enters auto-resume. */
|
|
46
|
+
function withProcessCancelSkipAutoResume(result, signal) {
|
|
47
|
+
if (processCancelSignalName(signal) === undefined) {
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
return { ...result, skipAutoResume: true };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* #855: turn already started, then a catchable process signal arrived before
|
|
54
|
+
* lawful seal — settle as named cancel failure and skip auto-resume.
|
|
55
|
+
*/
|
|
56
|
+
async function settleProcessCancelAfterTurn(input) {
|
|
57
|
+
const failed = await settleAfterTurnStarted(input.admitted, withEngineDetourInvocationScope({
|
|
58
|
+
timedOut: input.result.timedOut,
|
|
59
|
+
code: input.result.code,
|
|
60
|
+
stderr: input.result.stderr,
|
|
61
|
+
knownDiagnostic: processCancelDiagnostic(input.cancelName),
|
|
62
|
+
}, input.invocationScopeId), input.adapters, input.env.principalAuthority, input.io, input.persistRunState);
|
|
63
|
+
return {
|
|
64
|
+
...failed,
|
|
65
|
+
turnDispatched: true,
|
|
66
|
+
skipAutoResume: true,
|
|
67
|
+
...input.deferredPersist,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
44
70
|
/**
|
|
45
71
|
* Nested station-child role already finished its own call-local loop.
|
|
46
72
|
* Parent records that failure once and must not auto-resume into a re-summon
|
|
@@ -258,6 +284,26 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
258
284
|
if (afterDispatchApplied)
|
|
259
285
|
return result;
|
|
260
286
|
afterDispatchApplied = true;
|
|
287
|
+
// #855: re-read cancel before afterDispatch finalization — never present
|
|
288
|
+
// lawful success after a catchable process signal. skipRunStateWrite: run-state
|
|
289
|
+
// may already be terminal from a prior lawful persist; do not reopen as resumable.
|
|
290
|
+
const cancelBeforeFinish = processCancelSignalName(env.signal);
|
|
291
|
+
if (cancelBeforeFinish !== undefined &&
|
|
292
|
+
result.terminal !== undefined &&
|
|
293
|
+
isLawfulTypedTerminalOutcome(result.terminal.roleOutcome)) {
|
|
294
|
+
result = {
|
|
295
|
+
...(await settleAfterTurnStarted(admitted, withEngineDetourInvocationScope({
|
|
296
|
+
timedOut: false,
|
|
297
|
+
code: null,
|
|
298
|
+
stderr: "",
|
|
299
|
+
knownDiagnostic: processCancelDiagnostic(cancelBeforeFinish),
|
|
300
|
+
skipRunStateWrite: true,
|
|
301
|
+
}, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState)),
|
|
302
|
+
turnDispatched: true,
|
|
303
|
+
skipAutoResume: true,
|
|
304
|
+
...deferredPersist,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
261
307
|
try {
|
|
262
308
|
// #858: an unbound seat may assert its ticket on the existing receipt.
|
|
263
309
|
// Read the original accepted payload; do not rewrite it, infer from prose,
|
|
@@ -412,13 +458,16 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
412
458
|
result = await env.roleTurnHost.executeTurn(turnRequest);
|
|
413
459
|
}
|
|
414
460
|
catch (error) {
|
|
461
|
+
const processCancelName = processCancelSignalName(env.signal);
|
|
415
462
|
const settled = await settleAfterTurnStarted(admitted, withEngineDetourInvocationScope({
|
|
416
463
|
timedOut: false,
|
|
417
464
|
code: null,
|
|
418
465
|
stderr: "",
|
|
419
|
-
thrown:
|
|
466
|
+
thrown: processCancelName === undefined
|
|
467
|
+
? error
|
|
468
|
+
: new Error(processCancelDiagnostic(processCancelName), { cause: error }),
|
|
420
469
|
}, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState);
|
|
421
|
-
return await finishAfterTurn({ ...settled, turnDispatched: true, ...deferredPersist });
|
|
470
|
+
return await finishAfterTurn(withProcessCancelSkipAutoResume({ ...settled, turnDispatched: true, ...deferredPersist }, env.signal));
|
|
422
471
|
}
|
|
423
472
|
// `result.stderr` stays live in memory for the real classification below
|
|
424
473
|
// regardless of whether this durable mirror write succeeds. A write
|
|
@@ -514,10 +563,12 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
514
563
|
// retroactively invalidate an acceptance that already sealed this turn
|
|
515
564
|
// — a resolved 429 observed earlier in the same session is exactly
|
|
516
565
|
// that.
|
|
566
|
+
const processCancelName = processCancelSignalName(env.signal);
|
|
517
567
|
const directHostFailureSignal = result.timedOut
|
|
518
568
|
|| result.knownFailure !== undefined
|
|
519
569
|
|| runnerKnownFailure !== undefined
|
|
520
|
-
|| credentialFailure !== undefined
|
|
570
|
+
|| credentialFailure !== undefined
|
|
571
|
+
|| processCancelName !== undefined;
|
|
521
572
|
hostSignalFailed =
|
|
522
573
|
directHostFailureSignal
|
|
523
574
|
|| (result.code !== null && result.code !== 0)
|
|
@@ -553,11 +604,15 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
553
604
|
// where a bare nonzero exit / resolution failure must not be outranked
|
|
554
605
|
// by someone else's earlier success (#836 r12 class 2).
|
|
555
606
|
const staleAcceptanceOutranksRealFailure = !settledIsFreshThisAttempt && hostSignalFailed;
|
|
607
|
+
// #855: re-read cancel after trySettle/attach awaits — a signal in this
|
|
608
|
+
// window must not land as lawful accepted.
|
|
609
|
+
const cancelAfterSettle = processCancelSignalName(env.signal);
|
|
556
610
|
if (settled !== undefined
|
|
557
611
|
&& shouldPresent(settled)
|
|
558
612
|
&& !directHostFailureSignal
|
|
559
613
|
&& !staleAcceptanceOutranksRealFailure
|
|
560
|
-
&& stderrLogWriteFailure === undefined
|
|
614
|
+
&& stderrLogWriteFailure === undefined
|
|
615
|
+
&& cancelAfterSettle === undefined) {
|
|
561
616
|
// This court sealed — drop open-court pointer (bare resume no longer continues it).
|
|
562
617
|
if (settled.roleOutcome.kind === "accepted" &&
|
|
563
618
|
request.courtAttemptId !== undefined &&
|
|
@@ -597,6 +652,22 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
597
652
|
return await finishAfterTurn({ ...settledFailure, turnDispatched: true, ...deferredPersist });
|
|
598
653
|
}
|
|
599
654
|
if (settledOutcome !== undefined) {
|
|
655
|
+
// #855: final cancel re-read before lawful seal — settlement window may
|
|
656
|
+
// have received SIGTERM/SIGINT/SIGHUP after the earlier snapshot.
|
|
657
|
+
const lateCancel = processCancelSignalName(env.signal);
|
|
658
|
+
if (lateCancel !== undefined) {
|
|
659
|
+
return await finishAfterTurn(await settleProcessCancelAfterTurn({
|
|
660
|
+
admitted,
|
|
661
|
+
cancelName: lateCancel,
|
|
662
|
+
result,
|
|
663
|
+
adapters,
|
|
664
|
+
env,
|
|
665
|
+
io,
|
|
666
|
+
persistRunState,
|
|
667
|
+
deferredPersist,
|
|
668
|
+
invocationScopeId: request.invocationScopeId,
|
|
669
|
+
}));
|
|
670
|
+
}
|
|
600
671
|
if (persistRunState) {
|
|
601
672
|
try {
|
|
602
673
|
await persistReturnedRunState(admitted, env.principalAuthority, { lawful: true });
|
|
@@ -619,16 +690,18 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
619
690
|
thrown: error,
|
|
620
691
|
skipRunStateWrite: true,
|
|
621
692
|
}, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState);
|
|
622
|
-
return await finishAfterTurn({ ...failed, turnDispatched: true, ...deferredPersist });
|
|
693
|
+
return await finishAfterTurn(withProcessCancelSkipAutoResume({ ...failed, turnDispatched: true, ...deferredPersist }, env.signal));
|
|
623
694
|
}
|
|
624
695
|
}
|
|
625
696
|
// Lawful persist + present is the caller's stop seam (auto-resume loop /
|
|
626
697
|
// manual resume), not this retried host-turn function.
|
|
698
|
+
// #855 cancel re-read lives in finishAfterTurn (before afterDispatch).
|
|
627
699
|
return await finishAfterTurn({ ...settledOutcome, ...deferredPersist });
|
|
628
700
|
}
|
|
629
701
|
// Host/runner true failure coexists with already-recorded payloads — never wash as accepted.
|
|
630
702
|
if (hostSignalFailed) {
|
|
631
703
|
const resolutionInput = controlledFailureInputFromResolution(resolution);
|
|
704
|
+
const processCancelName = processCancelSignalName(env.signal);
|
|
632
705
|
const stderrLogWriteDetails = stderrLogWriteFailure === undefined
|
|
633
706
|
? undefined
|
|
634
707
|
: { stderrLogWriteFailure: describeCaughtError(stderrLogWriteFailure) };
|
|
@@ -637,6 +710,9 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
637
710
|
code: result.code,
|
|
638
711
|
stderr: result.stderr,
|
|
639
712
|
...resolutionInput,
|
|
713
|
+
...(processCancelName === undefined
|
|
714
|
+
? {}
|
|
715
|
+
: { knownDiagnostic: processCancelDiagnostic(processCancelName) }),
|
|
640
716
|
// Secondary fact only — never the cause. Rides on whichever channel
|
|
641
717
|
// classification actually reads (knownFailure.details owns it when
|
|
642
718
|
// a knownFailure exists; the top-level knownDetails otherwise).
|
|
@@ -651,7 +727,7 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
651
727
|
}
|
|
652
728
|
: { knownDetails: stderrLogWriteDetails }),
|
|
653
729
|
}, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState);
|
|
654
|
-
return await finishAfterTurn({ ...failed, turnDispatched: true, ...deferredPersist });
|
|
730
|
+
return await finishAfterTurn(withProcessCancelSkipAutoResume({ ...failed, turnDispatched: true, ...deferredPersist }, env.signal));
|
|
655
731
|
}
|
|
656
732
|
// Nothing else was wrong, but the durable stderr mirror itself failed to
|
|
657
733
|
// write — that is the real (infrastructure) problem in this case, not a
|
|
@@ -666,6 +742,21 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
666
742
|
return await finishAfterTurn({ ...failed, turnDispatched: true, ...deferredPersist });
|
|
667
743
|
}
|
|
668
744
|
const noReceipt = await attachRecordedSubmissions(admitted, await settleHostEndedNoReceipt(admitted, env.principalAuthority, courtScope), courtScope);
|
|
745
|
+
// #855: cancel during no_receipt settlement window is not lawful success.
|
|
746
|
+
const noReceiptCancel = processCancelSignalName(env.signal);
|
|
747
|
+
if (noReceiptCancel !== undefined) {
|
|
748
|
+
return await finishAfterTurn(await settleProcessCancelAfterTurn({
|
|
749
|
+
admitted,
|
|
750
|
+
cancelName: noReceiptCancel,
|
|
751
|
+
result,
|
|
752
|
+
adapters,
|
|
753
|
+
env,
|
|
754
|
+
io,
|
|
755
|
+
persistRunState,
|
|
756
|
+
deferredPersist,
|
|
757
|
+
invocationScopeId: request.invocationScopeId,
|
|
758
|
+
}));
|
|
759
|
+
}
|
|
669
760
|
if (persistRunState) {
|
|
670
761
|
try {
|
|
671
762
|
await persistReturnedRunState(admitted, env.principalAuthority, { lawful: true });
|
|
@@ -683,9 +774,10 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
683
774
|
thrown: error,
|
|
684
775
|
skipRunStateWrite: true,
|
|
685
776
|
}, request.invocationScopeId), adapters, env.principalAuthority, io, persistRunState);
|
|
686
|
-
return await finishAfterTurn({ ...failed, turnDispatched: true, ...deferredPersist });
|
|
777
|
+
return await finishAfterTurn(withProcessCancelSkipAutoResume({ ...failed, turnDispatched: true, ...deferredPersist }, env.signal));
|
|
687
778
|
}
|
|
688
779
|
}
|
|
780
|
+
// #855 cancel re-read lives in finishAfterTurn (before afterDispatch).
|
|
689
781
|
return await finishAfterTurn({
|
|
690
782
|
exitCode: exitCodeForTerminalOutcome(noReceipt.roleOutcome),
|
|
691
783
|
admitted,
|
|
@@ -972,6 +1064,7 @@ export async function runPostAdmissionSeatResume(input) {
|
|
|
972
1064
|
io: input.io,
|
|
973
1065
|
sessionAppender: env.sessionAppender,
|
|
974
1066
|
autoResumeLimit: env.autoResumeLimit,
|
|
1067
|
+
...(env.signal === undefined ? {} : { signal: env.signal }),
|
|
975
1068
|
buildInitialPayload: () => ({ resumeTurn: false }),
|
|
976
1069
|
buildResumePayload: () => ({ resumeTurn: true }),
|
|
977
1070
|
// Same as public manual resume: prior-court sealed acceptance is not a
|
|
@@ -1062,7 +1155,7 @@ export async function runPostAdmissionOneShot(input) {
|
|
|
1062
1155
|
...input.request,
|
|
1063
1156
|
continuation: {
|
|
1064
1157
|
kind: "resume",
|
|
1065
|
-
prompt:
|
|
1158
|
+
prompt: buildAutoResumeContinuationPrompt({
|
|
1066
1159
|
packageRoot: input.env.packageRoot,
|
|
1067
1160
|
...pickEngineAxis({
|
|
1068
1161
|
engine: input.effectiveEngine ?? input.env.engine,
|
|
@@ -1095,6 +1188,7 @@ export async function runPostAdmissionResumable(input) {
|
|
|
1095
1188
|
io,
|
|
1096
1189
|
sessionAppender: env.sessionAppender,
|
|
1097
1190
|
autoResumeLimit: env.autoResumeLimit,
|
|
1191
|
+
...(env.signal === undefined ? {} : { signal: env.signal }),
|
|
1098
1192
|
buildInitialPayload: buildScopedInitial,
|
|
1099
1193
|
buildResumePayload: buildScopedResume,
|
|
1100
1194
|
dispatch: async (request, lease, _isFirst, attemptIo) => {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #855: catchable process signals for ak-role public entry.
|
|
3
|
+
* SIGTERM / SIGINT / SIGHUP abort the shared controller so hosts can gracefully
|
|
4
|
+
* terminate children (SIGTERM only, never SIGKILL). Uncatchable death (SIGKILL,
|
|
5
|
+
* crash, power loss) is reported via run-state + writer lease on analyst.
|
|
6
|
+
*/
|
|
7
|
+
const CATCHABLE_PROCESS_SIGNALS = ["SIGTERM", "SIGINT", "SIGHUP"];
|
|
8
|
+
/**
|
|
9
|
+
* Install once per process entry. First catchable signal wins; later signals
|
|
10
|
+
* are ignored so settlement can finish without re-entry storms.
|
|
11
|
+
*/
|
|
12
|
+
export function installProcessCancelHandlers(processRef = process) {
|
|
13
|
+
const controller = new AbortController();
|
|
14
|
+
let received;
|
|
15
|
+
const listeners = new Map();
|
|
16
|
+
for (const sig of CATCHABLE_PROCESS_SIGNALS) {
|
|
17
|
+
const onSignal = () => {
|
|
18
|
+
if (received !== undefined)
|
|
19
|
+
return;
|
|
20
|
+
received = sig;
|
|
21
|
+
try {
|
|
22
|
+
controller.abort(sig);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// already aborted
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
listeners.set(sig, onSignal);
|
|
29
|
+
processRef.on(sig, onSignal);
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
signal: controller.signal,
|
|
33
|
+
receivedSignal: () => received,
|
|
34
|
+
dispose() {
|
|
35
|
+
for (const [sig, onSignal] of listeners) {
|
|
36
|
+
processRef.off(sig, onSignal);
|
|
37
|
+
}
|
|
38
|
+
listeners.clear();
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Signal name when this AbortSignal was aborted by installProcessCancelHandlers. */
|
|
43
|
+
export function processCancelSignalName(signal) {
|
|
44
|
+
if (signal === undefined || signal.aborted !== true)
|
|
45
|
+
return undefined;
|
|
46
|
+
const reason = signal.reason;
|
|
47
|
+
if (reason === "SIGTERM" ||
|
|
48
|
+
reason === "SIGINT" ||
|
|
49
|
+
reason === "SIGHUP") {
|
|
50
|
+
return reason;
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
/** Diagnostic line for a catchable process-signal termination (failure honesty). */
|
|
55
|
+
export function processCancelDiagnostic(signalName) {
|
|
56
|
+
return `ak-role terminated by ${signalName}`;
|
|
57
|
+
}
|
|
@@ -26,20 +26,31 @@ export const V1_RESUMABLE_PROVIDERS = ["openai-codex", "xai"];
|
|
|
26
26
|
* source: runWithAutoResumeLoop receives the effective value once per call.
|
|
27
27
|
*/
|
|
28
28
|
export const AUTO_RESUME_LIMIT = 2;
|
|
29
|
-
/** Package-owned turn trigger for resume. Not caller instruction and not semantic task content. */
|
|
30
|
-
export const RESUME_TRANSPORT_ENVELOPE = "[ak-role:resume-continue]";
|
|
31
29
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
30
|
+
* Package-owned non-empty Chinese neutral resume transport (#959 / ADR 0073).
|
|
31
|
+
* Used by:
|
|
32
|
+
* - auto-resume (all seats via buildAutoResumeContinuationPrompt) — required so
|
|
33
|
+
* hosts that reject empty stdin (codex) still receive a prompt;
|
|
34
|
+
* - reviewer seat manual resume (reviewerResumePrompt) — same non-empty need on
|
|
35
|
+
* that seat's own manual entry.
|
|
36
|
+
* Generic bare `ak-role resume` stays empty-capable via selectResumeContinuationPrompt
|
|
37
|
+
* / buildResumeContinuationPrompt — ADR 0080 keeps auto and generic-manual entries separate.
|
|
38
|
+
*/
|
|
39
|
+
export const RESUME_TRANSPORT_ENVELOPE = "继续。";
|
|
40
|
+
/**
|
|
41
|
+
* Manual resume continuation selector (#471 / #600 / #736 / ADR 0080).
|
|
42
|
+
* Message present → those bytes; absent → engine pointers only (may be empty).
|
|
43
|
+
* Caller message (including blank) still wins verbatim when supplied.
|
|
44
|
+
* Auto-resume must use buildAutoResumeContinuationPrompt — do not fold the
|
|
45
|
+
* non-empty Chinese envelope into this shared manual selector (#959).
|
|
35
46
|
*/
|
|
36
47
|
export function selectResumeContinuationPrompt(message, engineMaterial) {
|
|
37
48
|
const lines = message !== undefined ? [message] : [];
|
|
38
49
|
return appendEngineSessionMaterial(lines, engineMaterial).join("\n");
|
|
39
50
|
}
|
|
40
51
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
52
|
+
* Manual resume continuation with engine material from the seat env (#600).
|
|
53
|
+
* Bare manual resume stays empty-prompt-capable; auto-resume is a separate entry.
|
|
43
54
|
*/
|
|
44
55
|
export function buildResumeContinuationPrompt(options) {
|
|
45
56
|
return selectResumeContinuationPrompt(options.message, engineSessionMaterialFromOptions({
|
|
@@ -47,6 +58,17 @@ export function buildResumeContinuationPrompt(options) {
|
|
|
47
58
|
...pickEngineAxis(options),
|
|
48
59
|
}));
|
|
49
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Auto-resume continuation only (#959 / ADR 0080).
|
|
63
|
+
* Always non-empty: Chinese neutral envelope plus optional engine pointers.
|
|
64
|
+
* Never call this from manual `ak-role resume`.
|
|
65
|
+
*/
|
|
66
|
+
export function buildAutoResumeContinuationPrompt(options) {
|
|
67
|
+
return selectResumeContinuationPrompt(RESUME_TRANSPORT_ENVELOPE, engineSessionMaterialFromOptions({
|
|
68
|
+
packageRoot: options.packageRoot,
|
|
69
|
+
...pickEngineAxis(options),
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
50
72
|
const RUN_STATE_FILE = "run-state.json";
|
|
51
73
|
const WRITER_LOCK_FILE = "writer.lock";
|
|
52
74
|
/** @deprecated #416: 429-only classification removed; kept for compatibility. */
|
|
@@ -157,7 +157,7 @@ async function closedLedgerOutcome(admitted, role, scope) {
|
|
|
157
157
|
function coordinatesFromAdmitted(authority, admitted) {
|
|
158
158
|
return authority.decode(admitted.principal);
|
|
159
159
|
}
|
|
160
|
-
import { exitCodeForTerminalOutcome, formatTerminalResult, isLawfulTypedTerminalOutcome,
|
|
160
|
+
import { exitCodeForTerminalOutcome, formatTerminalResult, isLawfulTypedTerminalOutcome, adviceNavigatorFact, } from "./terminal.js";
|
|
161
161
|
export { exitCodeForTerminalOutcome, formatTerminalResult, isLawfulTypedTerminalOutcome, };
|
|
162
162
|
/**
|
|
163
163
|
* Host stderr as recorded — full bytes, no flood filter, no char clip (#836).
|
|
@@ -457,7 +457,13 @@ export function explicitInternalKnownFailureClassificationInput(failure) {
|
|
|
457
457
|
...(failure.details === undefined ? {} : { knownDetails: failure.details }),
|
|
458
458
|
};
|
|
459
459
|
}
|
|
460
|
-
/**
|
|
460
|
+
/**
|
|
461
|
+
* Post-role Navigator delivery grace (Issue #11 / #101 / #106 / #159).
|
|
462
|
+
* After the parent finishes: wait at most this long for navigator output, then
|
|
463
|
+
* stop waiting. Navigator must already be running from parent start (prepare
|
|
464
|
+
* host round in parallel); this window is only the tail after parent end — not
|
|
465
|
+
* the budget to start a cold full host turn (owner 2026-09-17 #959).
|
|
466
|
+
*/
|
|
461
467
|
export const NAVIGATOR_POST_ROLE_GRACE_MS = 10_000;
|
|
462
468
|
function isMissingPathError(error) {
|
|
463
469
|
return (error instanceof Error &&
|
|
@@ -1334,11 +1340,6 @@ export async function appendRunAttemptHistory(source, outcome) {
|
|
|
1334
1340
|
}
|
|
1335
1341
|
catch { }
|
|
1336
1342
|
}
|
|
1337
|
-
function navigatorPhaseValue(value) {
|
|
1338
|
-
if (value === "plan" || value === "apply")
|
|
1339
|
-
return value;
|
|
1340
|
-
return null;
|
|
1341
|
-
}
|
|
1342
1343
|
/**
|
|
1343
1344
|
* Minimal attendance provenance against the bound marker (ADR 0043).
|
|
1344
1345
|
* Keep only invocationId + post-terminal ordering. Runtime-self-produced
|
|
@@ -1357,35 +1358,48 @@ function parseNavigatorAttendanceDetails(details) {
|
|
|
1357
1358
|
const advisoryDiagnostic = typeof details.routePlaybookReadFailure === "string"
|
|
1358
1359
|
? { advisoryDiagnostic: details.routePlaybookReadFailure }
|
|
1359
1360
|
: {};
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1361
|
+
// #959: advice prose is presented as-is. Legacy "recommendation" with next/reason/
|
|
1362
|
+
// command is projected into prose so historical sessions still render — never wash
|
|
1363
|
+
// a real recommendation into no-advice when any advice body is recoverable.
|
|
1364
|
+
if (disposition === "advice" || disposition === "recommendation") {
|
|
1365
|
+
let prose;
|
|
1366
|
+
if (typeof details.prose === "string" && details.prose.trim() !== "") {
|
|
1367
|
+
prose = details.prose;
|
|
1368
|
+
}
|
|
1369
|
+
else {
|
|
1370
|
+
const next = isRecord(details.next) && typeof details.next.role === "string"
|
|
1371
|
+
? details.next.role
|
|
1372
|
+
: undefined;
|
|
1373
|
+
const reason = typeof details.reason === "string" && details.reason.trim() !== ""
|
|
1374
|
+
? details.reason
|
|
1375
|
+
: undefined;
|
|
1376
|
+
const command = typeof details.command === "string" && details.command.trim() !== ""
|
|
1377
|
+
? details.command
|
|
1378
|
+
: undefined;
|
|
1379
|
+
if (reason !== undefined && next !== undefined) {
|
|
1380
|
+
prose = `${reason}(下一步:${next})`;
|
|
1381
|
+
}
|
|
1382
|
+
else if (reason !== undefined) {
|
|
1383
|
+
prose = reason;
|
|
1384
|
+
}
|
|
1385
|
+
else if (next !== undefined) {
|
|
1386
|
+
// Historical recommendation with only typed next — still real advice.
|
|
1387
|
+
prose = `下一步:${next}`;
|
|
1388
|
+
}
|
|
1389
|
+
else if (command !== undefined) {
|
|
1390
|
+
prose = command;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
if (prose === undefined || prose.trim() === "") {
|
|
1394
|
+
// Attended but empty body is affirmative no-advice, not unavailable (#959).
|
|
1363
1395
|
return {
|
|
1364
|
-
disposition: "
|
|
1365
|
-
|
|
1366
|
-
reason: "navigator recommendation missing typed next role",
|
|
1396
|
+
disposition: "no-advice",
|
|
1397
|
+
...advisoryDiagnostic,
|
|
1367
1398
|
};
|
|
1368
1399
|
}
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
? details.route
|
|
1372
|
-
.filter(isRecord)
|
|
1373
|
-
.map((target) => ({
|
|
1374
|
-
role: String(target.role),
|
|
1375
|
-
phase: navigatorPhaseValue(target.phase),
|
|
1376
|
-
}))
|
|
1377
|
-
: undefined;
|
|
1378
|
-
return recommendationNavigatorFact({
|
|
1400
|
+
return adviceNavigatorFact({
|
|
1401
|
+
prose,
|
|
1379
1402
|
...advisoryDiagnostic,
|
|
1380
|
-
next: {
|
|
1381
|
-
role: next.role,
|
|
1382
|
-
phase: navigatorPhaseValue(next.phase),
|
|
1383
|
-
},
|
|
1384
|
-
reason,
|
|
1385
|
-
...(route === undefined ? {} : { route }),
|
|
1386
|
-
...(typeof details.command === "string"
|
|
1387
|
-
? { modelCommand: details.command }
|
|
1388
|
-
: {}),
|
|
1389
1403
|
});
|
|
1390
1404
|
}
|
|
1391
1405
|
if (disposition === "unavailable") {
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Terminal result: typed semantic regions for one admitted Role run (ADR 0052 / #106).
|
|
3
|
-
* Presentation may rearrange labels/order; only regions and typed facts are stable.
|
|
4
|
-
*
|
|
5
|
-
* Free-text cells are JSON-string encoded so legitimate newlines/tabs cannot forge
|
|
6
|
-
* extra rows or shift column boundaries (note / fix.summary / decision question / reason).
|
|
7
|
-
*/
|
|
8
|
-
import { isPublicCallableRole, renderPublicAkRoleCommand, } from "./command-renderer.js";
|
|
9
1
|
/** Encode one free-text Terminal cell. JSON string form cannot embed raw tab/newline. */
|
|
10
2
|
export function encodeTerminalField(value) {
|
|
11
3
|
return JSON.stringify(value);
|
|
@@ -34,21 +26,13 @@ export function roleResultPayloads(outcome) {
|
|
|
34
26
|
return [];
|
|
35
27
|
}
|
|
36
28
|
/**
|
|
37
|
-
* Build
|
|
38
|
-
*
|
|
29
|
+
* Build an advice navigator fact (#959). Prose is presented as submitted;
|
|
30
|
+
* code does not parse route/next or judge usability.
|
|
39
31
|
*/
|
|
40
|
-
export function
|
|
41
|
-
const command = typeof input.modelCommand === "string" && input.modelCommand.trim() !== ""
|
|
42
|
-
? input.modelCommand
|
|
43
|
-
: isPublicCallableRole(input.next.role)
|
|
44
|
-
? renderPublicAkRoleCommand(input.next)
|
|
45
|
-
: undefined;
|
|
32
|
+
export function adviceNavigatorFact(input) {
|
|
46
33
|
return {
|
|
47
|
-
disposition: "
|
|
48
|
-
|
|
49
|
-
reason: input.reason,
|
|
50
|
-
...(command === undefined ? {} : { command }),
|
|
51
|
-
...(input.route === undefined ? {} : { route: input.route }),
|
|
34
|
+
disposition: "advice",
|
|
35
|
+
prose: input.prose,
|
|
52
36
|
...(input.advisoryDiagnostic === undefined ? {} : { advisoryDiagnostic: input.advisoryDiagnostic }),
|
|
53
37
|
};
|
|
54
38
|
}
|
|
@@ -82,12 +66,9 @@ export function formatTerminalResult(result) {
|
|
|
82
66
|
if (result.navigator.advisoryDiagnostic !== undefined) {
|
|
83
67
|
lines.push(`navigator-advisory\t${encodeTerminalField(result.navigator.advisoryDiagnostic)}`);
|
|
84
68
|
}
|
|
85
|
-
if (result.navigator.disposition === "
|
|
86
|
-
|
|
87
|
-
lines.push(`
|
|
88
|
-
if (result.navigator.command !== undefined) {
|
|
89
|
-
lines.push(`command\t${encodeTerminalField(result.navigator.command)}`);
|
|
90
|
-
}
|
|
69
|
+
if (result.navigator.disposition === "advice") {
|
|
70
|
+
// #959: present navigator prose as-is — no next/reason/command parsing.
|
|
71
|
+
lines.push(`prose\t${encodeTerminalField(result.navigator.prose)}`);
|
|
91
72
|
}
|
|
92
73
|
else if (result.navigator.disposition === "unavailable") {
|
|
93
74
|
lines.push(`unavailable\t${result.navigator.source}\t${encodeTerminalField(result.navigator.reason)}`);
|
|
@@ -269,14 +269,32 @@ async function summonPublicRole(options) {
|
|
|
269
269
|
case "navigator":
|
|
270
270
|
case "gatekeeper": {
|
|
271
271
|
const seat2 = options.role;
|
|
272
|
-
const [{ runPublicInstructionSeat }, invocation] = await Promise.all([
|
|
272
|
+
const [{ runPublicInstructionSeat, runPublicInstructionSeatResume }, invocation] = await Promise.all([
|
|
273
273
|
import("./public-cli/instruction-seat-run.js"),
|
|
274
274
|
import("./public-cli/invocation.js")
|
|
275
275
|
]);
|
|
276
276
|
const parse = seat2 === "auditor" ? invocation.parseAuditorArgv : seat2 === "navigator" ? invocation.parseNavigatorArgv : invocation.parseGatekeeperArgv;
|
|
277
|
-
const
|
|
278
|
-
if (
|
|
279
|
-
|
|
277
|
+
const resumeRunId = typeof options.resumeRunId === "string" && options.resumeRunId.trim() !== "" ? options.resumeRunId.trim() : void 0;
|
|
278
|
+
if (resumeRunId !== void 0) {
|
|
279
|
+
const instruction = options.argv[0] ?? "";
|
|
280
|
+
const stepped = await runPrepared(parse, (env) => runPublicInstructionSeatResume(
|
|
281
|
+
{
|
|
282
|
+
runId: resumeRunId,
|
|
283
|
+
summons: {
|
|
284
|
+
instruction,
|
|
285
|
+
instructionEmpty: instruction.trim() === ""
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
env,
|
|
289
|
+
io
|
|
290
|
+
));
|
|
291
|
+
if ("fail" in stepped) return stepped.fail;
|
|
292
|
+
result = stepped.ok;
|
|
293
|
+
} else {
|
|
294
|
+
const stepped = await runPrepared(parse, (env, once) => runPublicInstructionSeat(options.argv, env, io, seat2, once));
|
|
295
|
+
if ("fail" in stepped) return stepped.fail;
|
|
296
|
+
result = stepped.ok;
|
|
297
|
+
}
|
|
280
298
|
break;
|
|
281
299
|
}
|
|
282
300
|
case "judge": {
|