@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
|
@@ -10,6 +10,7 @@ import { writeFile } from "node:fs/promises";
|
|
|
10
10
|
import { isAbsolute, join, resolve } from "node:path";
|
|
11
11
|
|
|
12
12
|
import {
|
|
13
|
+
buildAutoResumeContinuationPrompt,
|
|
13
14
|
buildResumeContinuationPrompt,
|
|
14
15
|
RESUME_TRANSPORT_ENVELOPE,
|
|
15
16
|
type PublicResumeRequest,
|
|
@@ -86,6 +87,11 @@ import {
|
|
|
86
87
|
type TypedProviderHttpObservation,
|
|
87
88
|
type WriterLeaseDiagnosticKind,
|
|
88
89
|
} from "./run-lifecycle.ts";
|
|
90
|
+
import {
|
|
91
|
+
processCancelDiagnostic,
|
|
92
|
+
processCancelSignalName,
|
|
93
|
+
type CatchableProcessSignal,
|
|
94
|
+
} from "./process-cancel.ts";
|
|
89
95
|
import { homeFromRunDirectory } from "../activation-ledger-topology.ts";
|
|
90
96
|
import {
|
|
91
97
|
attemptProducedFreshSubmission,
|
|
@@ -118,6 +124,63 @@ import {
|
|
|
118
124
|
TurnDispatchedFailure,
|
|
119
125
|
} from "./auto-resume.ts";
|
|
120
126
|
|
|
127
|
+
/** #855: process-cancel settlement never re-enters auto-resume. */
|
|
128
|
+
function withProcessCancelSkipAutoResume<T extends object>(
|
|
129
|
+
result: T,
|
|
130
|
+
signal: AbortSignal | undefined,
|
|
131
|
+
): T & { skipAutoResume?: true } {
|
|
132
|
+
if (processCancelSignalName(signal) === undefined) {
|
|
133
|
+
return result as T & { skipAutoResume?: true };
|
|
134
|
+
}
|
|
135
|
+
return { ...result, skipAutoResume: true as const };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* #855: turn already started, then a catchable process signal arrived before
|
|
140
|
+
* lawful seal — settle as named cancel failure and skip auto-resume.
|
|
141
|
+
*/
|
|
142
|
+
async function settleProcessCancelAfterTurn<A extends AdmittedRoleInvocation, T extends TerminalResult>(input: {
|
|
143
|
+
admitted: A;
|
|
144
|
+
cancelName: CatchableProcessSignal;
|
|
145
|
+
result: RoleTurnResult;
|
|
146
|
+
adapters: PostAdmissionAdapters<A, T>;
|
|
147
|
+
env: PostAdmissionEnv;
|
|
148
|
+
io: CliIo;
|
|
149
|
+
persistRunState: boolean;
|
|
150
|
+
deferredPersist: { needsPersist?: true } | Record<string, never>;
|
|
151
|
+
invocationScopeId: string | undefined;
|
|
152
|
+
}): Promise<{
|
|
153
|
+
exitCode: number;
|
|
154
|
+
admitted: A;
|
|
155
|
+
terminal?: T;
|
|
156
|
+
skipAutoResume?: true;
|
|
157
|
+
turnDispatched?: true;
|
|
158
|
+
needsPersist?: true;
|
|
159
|
+
}> {
|
|
160
|
+
const failed = await settleAfterTurnStarted(
|
|
161
|
+
input.admitted,
|
|
162
|
+
withEngineDetourInvocationScope(
|
|
163
|
+
{
|
|
164
|
+
timedOut: input.result.timedOut,
|
|
165
|
+
code: input.result.code,
|
|
166
|
+
stderr: input.result.stderr,
|
|
167
|
+
knownDiagnostic: processCancelDiagnostic(input.cancelName),
|
|
168
|
+
},
|
|
169
|
+
input.invocationScopeId,
|
|
170
|
+
),
|
|
171
|
+
input.adapters,
|
|
172
|
+
input.env.principalAuthority,
|
|
173
|
+
input.io,
|
|
174
|
+
input.persistRunState,
|
|
175
|
+
);
|
|
176
|
+
return {
|
|
177
|
+
...failed,
|
|
178
|
+
turnDispatched: true as const,
|
|
179
|
+
skipAutoResume: true as const,
|
|
180
|
+
...input.deferredPersist,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
121
184
|
/**
|
|
122
185
|
* Nested station-child role already finished its own call-local loop.
|
|
123
186
|
* Parent records that failure once and must not auto-resume into a re-summon
|
|
@@ -586,6 +649,38 @@ export async function dispatchPostAdmissionTurn<
|
|
|
586
649
|
const finishAfterTurn = async (result: DispatchOutcome): Promise<DispatchOutcome> => {
|
|
587
650
|
if (afterDispatchApplied) return result;
|
|
588
651
|
afterDispatchApplied = true;
|
|
652
|
+
// #855: re-read cancel before afterDispatch finalization — never present
|
|
653
|
+
// lawful success after a catchable process signal. skipRunStateWrite: run-state
|
|
654
|
+
// may already be terminal from a prior lawful persist; do not reopen as resumable.
|
|
655
|
+
const cancelBeforeFinish = processCancelSignalName(env.signal);
|
|
656
|
+
if (
|
|
657
|
+
cancelBeforeFinish !== undefined &&
|
|
658
|
+
result.terminal !== undefined &&
|
|
659
|
+
isLawfulTypedTerminalOutcome(result.terminal.roleOutcome)
|
|
660
|
+
) {
|
|
661
|
+
result = {
|
|
662
|
+
...(await settleAfterTurnStarted(
|
|
663
|
+
admitted,
|
|
664
|
+
withEngineDetourInvocationScope(
|
|
665
|
+
{
|
|
666
|
+
timedOut: false,
|
|
667
|
+
code: null,
|
|
668
|
+
stderr: "",
|
|
669
|
+
knownDiagnostic: processCancelDiagnostic(cancelBeforeFinish),
|
|
670
|
+
skipRunStateWrite: true,
|
|
671
|
+
},
|
|
672
|
+
request.invocationScopeId,
|
|
673
|
+
),
|
|
674
|
+
adapters,
|
|
675
|
+
env.principalAuthority,
|
|
676
|
+
io,
|
|
677
|
+
persistRunState,
|
|
678
|
+
)),
|
|
679
|
+
turnDispatched: true as const,
|
|
680
|
+
skipAutoResume: true as const,
|
|
681
|
+
...deferredPersist,
|
|
682
|
+
};
|
|
683
|
+
}
|
|
589
684
|
try {
|
|
590
685
|
// #858: an unbound seat may assert its ticket on the existing receipt.
|
|
591
686
|
// Read the original accepted payload; do not rewrite it, infer from prose,
|
|
@@ -787,20 +882,29 @@ export async function dispatchPostAdmissionTurn<
|
|
|
787
882
|
try {
|
|
788
883
|
result = await env.roleTurnHost.executeTurn(turnRequest);
|
|
789
884
|
} catch (error) {
|
|
885
|
+
const processCancelName = processCancelSignalName(env.signal);
|
|
790
886
|
const settled = await settleAfterTurnStarted(
|
|
791
887
|
admitted,
|
|
792
888
|
withEngineDetourInvocationScope({
|
|
793
889
|
timedOut: false,
|
|
794
890
|
code: null,
|
|
795
891
|
stderr: "",
|
|
796
|
-
thrown:
|
|
892
|
+
thrown:
|
|
893
|
+
processCancelName === undefined
|
|
894
|
+
? error
|
|
895
|
+
: new Error(processCancelDiagnostic(processCancelName), { cause: error }),
|
|
797
896
|
}, request.invocationScopeId),
|
|
798
897
|
adapters,
|
|
799
898
|
env.principalAuthority,
|
|
800
899
|
io,
|
|
801
900
|
persistRunState,
|
|
802
901
|
);
|
|
803
|
-
return await finishAfterTurn(
|
|
902
|
+
return await finishAfterTurn(
|
|
903
|
+
withProcessCancelSkipAutoResume(
|
|
904
|
+
{ ...settled, turnDispatched: true as const, ...deferredPersist },
|
|
905
|
+
env.signal,
|
|
906
|
+
),
|
|
907
|
+
);
|
|
804
908
|
}
|
|
805
909
|
|
|
806
910
|
// `result.stderr` stays live in memory for the real classification below
|
|
@@ -916,11 +1020,13 @@ export async function dispatchPostAdmissionTurn<
|
|
|
916
1020
|
// retroactively invalidate an acceptance that already sealed this turn
|
|
917
1021
|
// — a resolved 429 observed earlier in the same session is exactly
|
|
918
1022
|
// that.
|
|
1023
|
+
const processCancelName = processCancelSignalName(env.signal);
|
|
919
1024
|
const directHostFailureSignal =
|
|
920
1025
|
result.timedOut
|
|
921
1026
|
|| result.knownFailure !== undefined
|
|
922
1027
|
|| runnerKnownFailure !== undefined
|
|
923
|
-
|| credentialFailure !== undefined
|
|
1028
|
+
|| credentialFailure !== undefined
|
|
1029
|
+
|| processCancelName !== undefined;
|
|
924
1030
|
hostSignalFailed =
|
|
925
1031
|
directHostFailureSignal
|
|
926
1032
|
|| (result.code !== null && result.code !== 0)
|
|
@@ -958,12 +1064,16 @@ export async function dispatchPostAdmissionTurn<
|
|
|
958
1064
|
// where a bare nonzero exit / resolution failure must not be outranked
|
|
959
1065
|
// by someone else's earlier success (#836 r12 class 2).
|
|
960
1066
|
const staleAcceptanceOutranksRealFailure = !settledIsFreshThisAttempt && hostSignalFailed;
|
|
1067
|
+
// #855: re-read cancel after trySettle/attach awaits — a signal in this
|
|
1068
|
+
// window must not land as lawful accepted.
|
|
1069
|
+
const cancelAfterSettle = processCancelSignalName(env.signal);
|
|
961
1070
|
if (
|
|
962
1071
|
settled !== undefined
|
|
963
1072
|
&& shouldPresent(settled)
|
|
964
1073
|
&& !directHostFailureSignal
|
|
965
1074
|
&& !staleAcceptanceOutranksRealFailure
|
|
966
1075
|
&& stderrLogWriteFailure === undefined
|
|
1076
|
+
&& cancelAfterSettle === undefined
|
|
967
1077
|
) {
|
|
968
1078
|
// This court sealed — drop open-court pointer (bare resume no longer continues it).
|
|
969
1079
|
if (
|
|
@@ -1016,6 +1126,24 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1016
1126
|
return await finishAfterTurn({ ...settledFailure, turnDispatched: true as const, ...deferredPersist });
|
|
1017
1127
|
}
|
|
1018
1128
|
if (settledOutcome !== undefined) {
|
|
1129
|
+
// #855: final cancel re-read before lawful seal — settlement window may
|
|
1130
|
+
// have received SIGTERM/SIGINT/SIGHUP after the earlier snapshot.
|
|
1131
|
+
const lateCancel = processCancelSignalName(env.signal);
|
|
1132
|
+
if (lateCancel !== undefined) {
|
|
1133
|
+
return await finishAfterTurn(
|
|
1134
|
+
await settleProcessCancelAfterTurn({
|
|
1135
|
+
admitted,
|
|
1136
|
+
cancelName: lateCancel,
|
|
1137
|
+
result,
|
|
1138
|
+
adapters,
|
|
1139
|
+
env,
|
|
1140
|
+
io,
|
|
1141
|
+
persistRunState,
|
|
1142
|
+
deferredPersist,
|
|
1143
|
+
invocationScopeId: request.invocationScopeId,
|
|
1144
|
+
}),
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1019
1147
|
if (persistRunState) {
|
|
1020
1148
|
try {
|
|
1021
1149
|
await persistReturnedRunState(admitted, env.principalAuthority, { lawful: true });
|
|
@@ -1044,17 +1172,24 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1044
1172
|
io,
|
|
1045
1173
|
persistRunState,
|
|
1046
1174
|
);
|
|
1047
|
-
return await finishAfterTurn(
|
|
1175
|
+
return await finishAfterTurn(
|
|
1176
|
+
withProcessCancelSkipAutoResume(
|
|
1177
|
+
{ ...failed, turnDispatched: true as const, ...deferredPersist },
|
|
1178
|
+
env.signal,
|
|
1179
|
+
),
|
|
1180
|
+
);
|
|
1048
1181
|
}
|
|
1049
1182
|
}
|
|
1050
1183
|
// Lawful persist + present is the caller's stop seam (auto-resume loop /
|
|
1051
1184
|
// manual resume), not this retried host-turn function.
|
|
1185
|
+
// #855 cancel re-read lives in finishAfterTurn (before afterDispatch).
|
|
1052
1186
|
return await finishAfterTurn({ ...settledOutcome, ...deferredPersist });
|
|
1053
1187
|
}
|
|
1054
1188
|
|
|
1055
1189
|
// Host/runner true failure coexists with already-recorded payloads — never wash as accepted.
|
|
1056
1190
|
if (hostSignalFailed) {
|
|
1057
1191
|
const resolutionInput = controlledFailureInputFromResolution(resolution!);
|
|
1192
|
+
const processCancelName = processCancelSignalName(env.signal);
|
|
1058
1193
|
const stderrLogWriteDetails =
|
|
1059
1194
|
stderrLogWriteFailure === undefined
|
|
1060
1195
|
? undefined
|
|
@@ -1066,6 +1201,9 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1066
1201
|
code: result.code,
|
|
1067
1202
|
stderr: result.stderr,
|
|
1068
1203
|
...resolutionInput,
|
|
1204
|
+
...(processCancelName === undefined
|
|
1205
|
+
? {}
|
|
1206
|
+
: { knownDiagnostic: processCancelDiagnostic(processCancelName) }),
|
|
1069
1207
|
// Secondary fact only — never the cause. Rides on whichever channel
|
|
1070
1208
|
// classification actually reads (knownFailure.details owns it when
|
|
1071
1209
|
// a knownFailure exists; the top-level knownDetails otherwise).
|
|
@@ -1085,7 +1223,12 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1085
1223
|
io,
|
|
1086
1224
|
persistRunState,
|
|
1087
1225
|
);
|
|
1088
|
-
return await finishAfterTurn(
|
|
1226
|
+
return await finishAfterTurn(
|
|
1227
|
+
withProcessCancelSkipAutoResume(
|
|
1228
|
+
{ ...failed, turnDispatched: true as const, ...deferredPersist },
|
|
1229
|
+
env.signal,
|
|
1230
|
+
),
|
|
1231
|
+
);
|
|
1089
1232
|
}
|
|
1090
1233
|
|
|
1091
1234
|
// Nothing else was wrong, but the durable stderr mirror itself failed to
|
|
@@ -1113,6 +1256,23 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1113
1256
|
await settleHostEndedNoReceipt(admitted, env.principalAuthority, courtScope) as T,
|
|
1114
1257
|
courtScope,
|
|
1115
1258
|
);
|
|
1259
|
+
// #855: cancel during no_receipt settlement window is not lawful success.
|
|
1260
|
+
const noReceiptCancel = processCancelSignalName(env.signal);
|
|
1261
|
+
if (noReceiptCancel !== undefined) {
|
|
1262
|
+
return await finishAfterTurn(
|
|
1263
|
+
await settleProcessCancelAfterTurn({
|
|
1264
|
+
admitted,
|
|
1265
|
+
cancelName: noReceiptCancel,
|
|
1266
|
+
result,
|
|
1267
|
+
adapters,
|
|
1268
|
+
env,
|
|
1269
|
+
io,
|
|
1270
|
+
persistRunState,
|
|
1271
|
+
deferredPersist,
|
|
1272
|
+
invocationScopeId: request.invocationScopeId,
|
|
1273
|
+
}),
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1116
1276
|
if (persistRunState) {
|
|
1117
1277
|
try {
|
|
1118
1278
|
await persistReturnedRunState(admitted, env.principalAuthority, { lawful: true });
|
|
@@ -1136,9 +1296,15 @@ export async function dispatchPostAdmissionTurn<
|
|
|
1136
1296
|
io,
|
|
1137
1297
|
persistRunState,
|
|
1138
1298
|
);
|
|
1139
|
-
return await finishAfterTurn(
|
|
1299
|
+
return await finishAfterTurn(
|
|
1300
|
+
withProcessCancelSkipAutoResume(
|
|
1301
|
+
{ ...failed, turnDispatched: true as const, ...deferredPersist },
|
|
1302
|
+
env.signal,
|
|
1303
|
+
),
|
|
1304
|
+
);
|
|
1140
1305
|
}
|
|
1141
1306
|
}
|
|
1307
|
+
// #855 cancel re-read lives in finishAfterTurn (before afterDispatch).
|
|
1142
1308
|
return await finishAfterTurn({
|
|
1143
1309
|
exitCode: exitCodeForTerminalOutcome(noReceipt.roleOutcome),
|
|
1144
1310
|
admitted,
|
|
@@ -1511,6 +1677,7 @@ export async function runPostAdmissionSeatResume<
|
|
|
1511
1677
|
io: input.io,
|
|
1512
1678
|
sessionAppender: env.sessionAppender,
|
|
1513
1679
|
autoResumeLimit: env.autoResumeLimit,
|
|
1680
|
+
...(env.signal === undefined ? {} : { signal: env.signal }),
|
|
1514
1681
|
buildInitialPayload: (): StationChildAttempt => ({ resumeTurn: false }),
|
|
1515
1682
|
buildResumePayload: (): StationChildAttempt => ({ resumeTurn: true }),
|
|
1516
1683
|
// Same as public manual resume: prior-court sealed acceptance is not a
|
|
@@ -1624,7 +1791,7 @@ export async function runPostAdmissionOneShot<
|
|
|
1624
1791
|
...input.request,
|
|
1625
1792
|
continuation: {
|
|
1626
1793
|
kind: "resume",
|
|
1627
|
-
prompt:
|
|
1794
|
+
prompt: buildAutoResumeContinuationPrompt({
|
|
1628
1795
|
packageRoot: input.env.packageRoot,
|
|
1629
1796
|
...pickEngineAxis({
|
|
1630
1797
|
engine: input.effectiveEngine ?? input.env.engine,
|
|
@@ -1677,6 +1844,7 @@ export async function runPostAdmissionResumable<
|
|
|
1677
1844
|
io,
|
|
1678
1845
|
sessionAppender: env.sessionAppender,
|
|
1679
1846
|
autoResumeLimit: env.autoResumeLimit,
|
|
1847
|
+
...(env.signal === undefined ? {} : { signal: env.signal }),
|
|
1680
1848
|
buildInitialPayload: buildScopedInitial,
|
|
1681
1849
|
buildResumePayload: buildScopedResume,
|
|
1682
1850
|
dispatch: async (request, lease, _isFirst, attemptIo) => {
|
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
|
|
8
|
+
const CATCHABLE_PROCESS_SIGNALS = ["SIGTERM", "SIGINT", "SIGHUP"] as const;
|
|
9
|
+
|
|
10
|
+
export type CatchableProcessSignal = (typeof CATCHABLE_PROCESS_SIGNALS)[number];
|
|
11
|
+
|
|
12
|
+
export type ProcessCancelHandle = {
|
|
13
|
+
readonly signal: AbortSignal;
|
|
14
|
+
/** Signal name once a catchable signal has been received; else undefined. */
|
|
15
|
+
readonly receivedSignal: () => CatchableProcessSignal | undefined;
|
|
16
|
+
dispose(): void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Install once per process entry. First catchable signal wins; later signals
|
|
21
|
+
* are ignored so settlement can finish without re-entry storms.
|
|
22
|
+
*/
|
|
23
|
+
export function installProcessCancelHandlers(
|
|
24
|
+
processRef: NodeJS.Process = process,
|
|
25
|
+
): ProcessCancelHandle {
|
|
26
|
+
const controller = new AbortController();
|
|
27
|
+
let received: CatchableProcessSignal | undefined;
|
|
28
|
+
const listeners = new Map<CatchableProcessSignal, () => void>();
|
|
29
|
+
|
|
30
|
+
for (const sig of CATCHABLE_PROCESS_SIGNALS) {
|
|
31
|
+
const onSignal = (): void => {
|
|
32
|
+
if (received !== undefined) return;
|
|
33
|
+
received = sig;
|
|
34
|
+
try {
|
|
35
|
+
controller.abort(sig);
|
|
36
|
+
} catch {
|
|
37
|
+
// already aborted
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
listeners.set(sig, onSignal);
|
|
41
|
+
processRef.on(sig, onSignal);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
signal: controller.signal,
|
|
46
|
+
receivedSignal: () => received,
|
|
47
|
+
dispose(): void {
|
|
48
|
+
for (const [sig, onSignal] of listeners) {
|
|
49
|
+
processRef.off(sig, onSignal);
|
|
50
|
+
}
|
|
51
|
+
listeners.clear();
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Signal name when this AbortSignal was aborted by installProcessCancelHandlers. */
|
|
57
|
+
export function processCancelSignalName(
|
|
58
|
+
signal: AbortSignal | undefined,
|
|
59
|
+
): CatchableProcessSignal | undefined {
|
|
60
|
+
if (signal === undefined || signal.aborted !== true) return undefined;
|
|
61
|
+
const reason = signal.reason;
|
|
62
|
+
if (
|
|
63
|
+
reason === "SIGTERM" ||
|
|
64
|
+
reason === "SIGINT" ||
|
|
65
|
+
reason === "SIGHUP"
|
|
66
|
+
) {
|
|
67
|
+
return reason;
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Diagnostic line for a catchable process-signal termination (failure honesty). */
|
|
73
|
+
export function processCancelDiagnostic(signalName: CatchableProcessSignal): string {
|
|
74
|
+
return `ak-role terminated by ${signalName}`;
|
|
75
|
+
}
|
|
@@ -127,8 +127,17 @@ export type RoleRunRecord = {
|
|
|
127
127
|
readonly resumable?: TypedHttp429Observation;
|
|
128
128
|
};
|
|
129
129
|
|
|
130
|
-
/**
|
|
131
|
-
|
|
130
|
+
/**
|
|
131
|
+
* Package-owned non-empty Chinese neutral resume transport (#959 / ADR 0073).
|
|
132
|
+
* Used by:
|
|
133
|
+
* - auto-resume (all seats via buildAutoResumeContinuationPrompt) — required so
|
|
134
|
+
* hosts that reject empty stdin (codex) still receive a prompt;
|
|
135
|
+
* - reviewer seat manual resume (reviewerResumePrompt) — same non-empty need on
|
|
136
|
+
* that seat's own manual entry.
|
|
137
|
+
* Generic bare `ak-role resume` stays empty-capable via selectResumeContinuationPrompt
|
|
138
|
+
* / buildResumeContinuationPrompt — ADR 0080 keeps auto and generic-manual entries separate.
|
|
139
|
+
*/
|
|
140
|
+
export const RESUME_TRANSPORT_ENVELOPE = "继续。" as const;
|
|
132
141
|
|
|
133
142
|
/** Public manual resume request after the unique CLI parser owns runId + optional message. */
|
|
134
143
|
export type PublicResumeRequest = {
|
|
@@ -169,9 +178,11 @@ export type SameTicketSummonsMaterials = {
|
|
|
169
178
|
};
|
|
170
179
|
|
|
171
180
|
/**
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
181
|
+
* Manual resume continuation selector (#471 / #600 / #736 / ADR 0080).
|
|
182
|
+
* Message present → those bytes; absent → engine pointers only (may be empty).
|
|
183
|
+
* Caller message (including blank) still wins verbatim when supplied.
|
|
184
|
+
* Auto-resume must use buildAutoResumeContinuationPrompt — do not fold the
|
|
185
|
+
* non-empty Chinese envelope into this shared manual selector (#959).
|
|
175
186
|
*/
|
|
176
187
|
export function selectResumeContinuationPrompt(
|
|
177
188
|
message?: string,
|
|
@@ -182,8 +193,8 @@ export function selectResumeContinuationPrompt(
|
|
|
182
193
|
}
|
|
183
194
|
|
|
184
195
|
/**
|
|
185
|
-
*
|
|
186
|
-
*
|
|
196
|
+
* Manual resume continuation with engine material from the seat env (#600).
|
|
197
|
+
* Bare manual resume stays empty-prompt-capable; auto-resume is a separate entry.
|
|
187
198
|
*/
|
|
188
199
|
export function buildResumeContinuationPrompt(options: {
|
|
189
200
|
packageRoot: string;
|
|
@@ -200,6 +211,25 @@ export function buildResumeContinuationPrompt(options: {
|
|
|
200
211
|
);
|
|
201
212
|
}
|
|
202
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Auto-resume continuation only (#959 / ADR 0080).
|
|
216
|
+
* Always non-empty: Chinese neutral envelope plus optional engine pointers.
|
|
217
|
+
* Never call this from manual `ak-role resume`.
|
|
218
|
+
*/
|
|
219
|
+
export function buildAutoResumeContinuationPrompt(options: {
|
|
220
|
+
packageRoot: string;
|
|
221
|
+
engine?: string;
|
|
222
|
+
engineModel?: string;
|
|
223
|
+
}): string {
|
|
224
|
+
return selectResumeContinuationPrompt(
|
|
225
|
+
RESUME_TRANSPORT_ENVELOPE,
|
|
226
|
+
engineSessionMaterialFromOptions({
|
|
227
|
+
packageRoot: options.packageRoot,
|
|
228
|
+
...pickEngineAxis(options),
|
|
229
|
+
}),
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
203
233
|
const RUN_STATE_FILE = "run-state.json";
|
|
204
234
|
const WRITER_LOCK_FILE = "writer.lock";
|
|
205
235
|
|
|
@@ -123,7 +123,6 @@ import {
|
|
|
123
123
|
parseInvocationMarkerIdentity,
|
|
124
124
|
type InvocationMarkerIdentity,
|
|
125
125
|
} from "../navigator-invocation-identity.ts";
|
|
126
|
-
import type { NavigatorPhase } from "../navigator-attendance.ts";
|
|
127
126
|
import {
|
|
128
127
|
NO_RECEIPT_LIFECYCLE_ENTRY_TYPE,
|
|
129
128
|
RECEIPT_DELIVERY_TURN_LIMIT,
|
|
@@ -360,7 +359,7 @@ import {
|
|
|
360
359
|
exitCodeForTerminalOutcome,
|
|
361
360
|
formatTerminalResult,
|
|
362
361
|
isLawfulTypedTerminalOutcome,
|
|
363
|
-
|
|
362
|
+
adviceNavigatorFact,
|
|
364
363
|
type ControlledFailureCause,
|
|
365
364
|
type TerminalArtifactRef,
|
|
366
365
|
type TerminalGateFact,
|
|
@@ -782,7 +781,13 @@ export function explicitInternalKnownFailureClassificationInput(
|
|
|
782
781
|
};
|
|
783
782
|
}
|
|
784
783
|
|
|
785
|
-
/**
|
|
784
|
+
/**
|
|
785
|
+
* Post-role Navigator delivery grace (Issue #11 / #101 / #106 / #159).
|
|
786
|
+
* After the parent finishes: wait at most this long for navigator output, then
|
|
787
|
+
* stop waiting. Navigator must already be running from parent start (prepare
|
|
788
|
+
* host round in parallel); this window is only the tail after parent end — not
|
|
789
|
+
* the budget to start a cold full host turn (owner 2026-09-17 #959).
|
|
790
|
+
*/
|
|
786
791
|
export const NAVIGATOR_POST_ROLE_GRACE_MS = 10_000;
|
|
787
792
|
|
|
788
793
|
type SessionMessage = {
|
|
@@ -1931,11 +1936,6 @@ export type LawfulJudgeRoleOutcome = Extract<
|
|
|
1931
1936
|
TerminalRoleOutcome,
|
|
1932
1937
|
{ kind: "accepted" } | { kind: "audit_escalation" }
|
|
1933
1938
|
>;
|
|
1934
|
-
function navigatorPhaseValue(value: unknown): NavigatorPhase {
|
|
1935
|
-
if (value === "plan" || value === "apply") return value;
|
|
1936
|
-
return null;
|
|
1937
|
-
}
|
|
1938
|
-
|
|
1939
1939
|
/**
|
|
1940
1940
|
* Minimal attendance provenance against the bound marker (ADR 0043).
|
|
1941
1941
|
* Keep only invocationId + post-terminal ordering. Runtime-self-produced
|
|
@@ -1960,35 +1960,44 @@ function parseNavigatorAttendanceDetails(
|
|
|
1960
1960
|
const advisoryDiagnostic = typeof details.routePlaybookReadFailure === "string"
|
|
1961
1961
|
? { advisoryDiagnostic: details.routePlaybookReadFailure }
|
|
1962
1962
|
: {};
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1963
|
+
// #959: advice prose is presented as-is. Legacy "recommendation" with next/reason/
|
|
1964
|
+
// command is projected into prose so historical sessions still render — never wash
|
|
1965
|
+
// a real recommendation into no-advice when any advice body is recoverable.
|
|
1966
|
+
if (disposition === "advice" || disposition === "recommendation") {
|
|
1967
|
+
let prose: string | undefined;
|
|
1968
|
+
if (typeof details.prose === "string" && details.prose.trim() !== "") {
|
|
1969
|
+
prose = details.prose;
|
|
1970
|
+
} else {
|
|
1971
|
+
const next = isRecord(details.next) && typeof details.next.role === "string"
|
|
1972
|
+
? details.next.role
|
|
1973
|
+
: undefined;
|
|
1974
|
+
const reason = typeof details.reason === "string" && details.reason.trim() !== ""
|
|
1975
|
+
? details.reason
|
|
1976
|
+
: undefined;
|
|
1977
|
+
const command = typeof details.command === "string" && details.command.trim() !== ""
|
|
1978
|
+
? details.command
|
|
1979
|
+
: undefined;
|
|
1980
|
+
if (reason !== undefined && next !== undefined) {
|
|
1981
|
+
prose = `${reason}(下一步:${next})`;
|
|
1982
|
+
} else if (reason !== undefined) {
|
|
1983
|
+
prose = reason;
|
|
1984
|
+
} else if (next !== undefined) {
|
|
1985
|
+
// Historical recommendation with only typed next — still real advice.
|
|
1986
|
+
prose = `下一步:${next}`;
|
|
1987
|
+
} else if (command !== undefined) {
|
|
1988
|
+
prose = command;
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
if (prose === undefined || prose.trim() === "") {
|
|
1992
|
+
// Attended but empty body is affirmative no-advice, not unavailable (#959).
|
|
1966
1993
|
return {
|
|
1967
|
-
disposition: "
|
|
1968
|
-
|
|
1969
|
-
reason: "navigator recommendation missing typed next role",
|
|
1994
|
+
disposition: "no-advice",
|
|
1995
|
+
...advisoryDiagnostic,
|
|
1970
1996
|
};
|
|
1971
1997
|
}
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
? details.route
|
|
1975
|
-
.filter(isRecord)
|
|
1976
|
-
.map((target) => ({
|
|
1977
|
-
role: String(target.role),
|
|
1978
|
-
phase: navigatorPhaseValue(target.phase),
|
|
1979
|
-
}))
|
|
1980
|
-
: undefined;
|
|
1981
|
-
return recommendationNavigatorFact({
|
|
1998
|
+
return adviceNavigatorFact({
|
|
1999
|
+
prose,
|
|
1982
2000
|
...advisoryDiagnostic,
|
|
1983
|
-
next: {
|
|
1984
|
-
role: next.role,
|
|
1985
|
-
phase: navigatorPhaseValue(next.phase),
|
|
1986
|
-
},
|
|
1987
|
-
reason,
|
|
1988
|
-
...(route === undefined ? {} : { route }),
|
|
1989
|
-
...(typeof details.command === "string"
|
|
1990
|
-
? { modelCommand: details.command }
|
|
1991
|
-
: {}),
|
|
1992
2001
|
});
|
|
1993
2002
|
}
|
|
1994
2003
|
if (disposition === "unavailable") {
|
|
@@ -5,11 +5,6 @@
|
|
|
5
5
|
* Free-text cells are JSON-string encoded so legitimate newlines/tabs cannot forge
|
|
6
6
|
* extra rows or shift column boundaries (note / fix.summary / decision question / reason).
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
9
|
-
isPublicCallableRole,
|
|
10
|
-
renderPublicAkRoleCommand,
|
|
11
|
-
} from "./command-renderer.ts";
|
|
12
|
-
import type { NavigatorPhase } from "../navigator-attendance.ts";
|
|
13
8
|
import type { NoReceiptLifecycleFacts } from "../receipt-delivery-policy.ts";
|
|
14
9
|
import type { ControlledFailureCause } from "../host-contracts.ts";
|
|
15
10
|
|
|
@@ -102,12 +97,9 @@ export function exitCodeForTerminalOutcome(
|
|
|
102
97
|
|
|
103
98
|
export type TerminalNavigatorFact =
|
|
104
99
|
| {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
/** Present only when role/phase is sufficient for a truthful public command. */
|
|
109
|
-
command?: string;
|
|
110
|
-
route?: ReadonlyArray<{ role: string; phase: NavigatorPhase }>;
|
|
100
|
+
/** #959: navigator speaks prose; code does not parse or rank the advice. */
|
|
101
|
+
disposition: "advice";
|
|
102
|
+
prose: string;
|
|
111
103
|
advisoryDiagnostic?: string;
|
|
112
104
|
}
|
|
113
105
|
| {
|
|
@@ -227,28 +219,16 @@ export type TerminalResult = {
|
|
|
227
219
|
);
|
|
228
220
|
|
|
229
221
|
/**
|
|
230
|
-
* Build
|
|
231
|
-
*
|
|
222
|
+
* Build an advice navigator fact (#959). Prose is presented as submitted;
|
|
223
|
+
* code does not parse route/next or judge usability.
|
|
232
224
|
*/
|
|
233
|
-
export function
|
|
234
|
-
|
|
235
|
-
reason: string;
|
|
236
|
-
route?: ReadonlyArray<{ role: string; phase: NavigatorPhase }>;
|
|
225
|
+
export function adviceNavigatorFact(input: {
|
|
226
|
+
prose: string;
|
|
237
227
|
advisoryDiagnostic?: string;
|
|
238
|
-
modelCommand?: string;
|
|
239
228
|
}): TerminalNavigatorFact {
|
|
240
|
-
const command =
|
|
241
|
-
typeof input.modelCommand === "string" && input.modelCommand.trim() !== ""
|
|
242
|
-
? input.modelCommand
|
|
243
|
-
: isPublicCallableRole(input.next.role)
|
|
244
|
-
? renderPublicAkRoleCommand(input.next)
|
|
245
|
-
: undefined;
|
|
246
229
|
return {
|
|
247
|
-
disposition: "
|
|
248
|
-
|
|
249
|
-
reason: input.reason,
|
|
250
|
-
...(command === undefined ? {} : { command }),
|
|
251
|
-
...(input.route === undefined ? {} : { route: input.route }),
|
|
230
|
+
disposition: "advice",
|
|
231
|
+
prose: input.prose,
|
|
252
232
|
...(input.advisoryDiagnostic === undefined ? {} : { advisoryDiagnostic: input.advisoryDiagnostic }),
|
|
253
233
|
};
|
|
254
234
|
}
|
|
@@ -288,14 +268,9 @@ export function formatTerminalResult(result: TerminalResult): string {
|
|
|
288
268
|
if (result.navigator.advisoryDiagnostic !== undefined) {
|
|
289
269
|
lines.push(`navigator-advisory\t${encodeTerminalField(result.navigator.advisoryDiagnostic)}`);
|
|
290
270
|
}
|
|
291
|
-
if (result.navigator.disposition === "
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
);
|
|
295
|
-
lines.push(`reason\t${encodeTerminalField(result.navigator.reason)}`);
|
|
296
|
-
if (result.navigator.command !== undefined) {
|
|
297
|
-
lines.push(`command\t${encodeTerminalField(result.navigator.command)}`);
|
|
298
|
-
}
|
|
271
|
+
if (result.navigator.disposition === "advice") {
|
|
272
|
+
// #959: present navigator prose as-is — no next/reason/command parsing.
|
|
273
|
+
lines.push(`prose\t${encodeTerminalField(result.navigator.prose)}`);
|
|
299
274
|
} else if (result.navigator.disposition === "unavailable") {
|
|
300
275
|
lines.push(
|
|
301
276
|
`unavailable\t${result.navigator.source}\t${encodeTerminalField(result.navigator.reason)}`,
|