@akagilnc/pi-workflow-roles 0.1.4782 → 0.1.4802
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/acp-host/production-host.js +104 -215
- package/dist/headless-host/production-host.js +149 -229
- package/dist/migrate-book-topology.js +1 -1
- package/dist/pi/role-turn-host.js +1 -1
- package/dist/public-cli/auto-resume.js +9 -15
- package/dist/public-cli/countersign-run.js +75 -87
- package/dist/public-cli/diarist-run.js +6 -26
- package/dist/public-cli/inspector-run.js +1 -1
- package/dist/public-cli/main.js +64 -177
- package/dist/public-cli/notary-run.js +0 -3
- package/dist/public-cli/option-definitions.js +1 -1
- package/dist/public-cli/post-admission.js +35 -65
- package/dist/public-cli/reviewer-run.js +1 -1
- package/dist/public-cli/run-lifecycle.js +14 -46
- package/dist/public-cli/seat-ticket-binding.js +11 -25
- package/dist/public-role-summons.js +2 -0
- package/package.json +1 -1
- package/souls/coder.md +1 -2
- package/src/headless-host/role-turn-host.ts +49 -12
- package/src/pi/role-turn-host.ts +1 -1
- package/src/public-cli/auto-resume.ts +19 -18
- package/src/public-cli/countersign-run.ts +76 -129
- package/src/public-cli/diarist-run.ts +6 -33
- package/src/public-cli/inspector-run.ts +1 -1
- package/src/public-cli/invocation.ts +5 -0
- package/src/public-cli/notary-run.ts +3 -7
- package/src/public-cli/option-definitions.ts +1 -1
- package/src/public-cli/post-admission.ts +37 -69
- package/src/public-cli/reviewer-run.ts +1 -1
- package/src/public-cli/run-lifecycle.ts +18 -66
- package/src/public-cli/seat-ticket-binding.ts +12 -28
- package/src/public-role-summons.ts +15 -6
- package/src/role-runtime.ts +9 -0
|
@@ -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 { buildAutoResumeContinuationPrompt,
|
|
11
|
+
import { buildAutoResumeContinuationPrompt, RESUME_TRANSPORT_ENVELOPE, } from "./run-lifecycle.js";
|
|
12
12
|
import { CliUsageError } from "./cli-errors.js";
|
|
13
13
|
import { bindAdmittedTicketNumber, buildInstructionTransportPrompt, freezeAttachmentsIntoRun, relocateAdmittedRunToTicket, } from "./invocation.js";
|
|
14
14
|
import { readRecordedSubmissionRows } from "../submission-ledger.js";
|
|
@@ -655,12 +655,9 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
655
655
|
// Settlement already sealed accepted — a cleanup failure here must
|
|
656
656
|
// not erase that fact or make the caller replay this court's
|
|
657
657
|
// summons over already-delivered work (#840 已交劳动只整理终局不重做).
|
|
658
|
-
//
|
|
659
|
-
//
|
|
660
|
-
|
|
661
|
-
// 必须落痕) — durably, since every auto-resume attempt's io here is
|
|
662
|
-
// dummyIo (#840 r9 判词 class 1).
|
|
663
|
-
await recordBestEffortPostDispatchDiagnostic(admitted, env, `current-court cleanup failed after accepted settlement (best-effort continue, self-heals on next resume): ${describeErrorIdentity(error)}`, io);
|
|
658
|
+
// Preserve the cleanup failure as a post-dispatch diagnostic; the
|
|
659
|
+
// accepted settlement remains authoritative (#840 r9 判词 class 1).
|
|
660
|
+
await recordBestEffortPostDispatchDiagnostic(admitted, env, `current-court cleanup failed after accepted settlement (best-effort continue): ${describeErrorIdentity(error)}`, io);
|
|
664
661
|
}
|
|
665
662
|
}
|
|
666
663
|
settledOutcome = {
|
|
@@ -809,8 +806,7 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
809
806
|
/**
|
|
810
807
|
* Shared resume continuation projection (#471 / #600 / #633 / #637 / #755 / #879):
|
|
811
808
|
* seat-table model/engine/timeout axes, restored correlation, and either
|
|
812
|
-
* - manual resume (no same-ticket summons):
|
|
813
|
-
* message, with engine-axis handbook via buildResumeContinuationPrompt, or
|
|
809
|
+
* - manual resume (no same-ticket summons): caller message bytes, or
|
|
814
810
|
* - same-ticket summons (审核循环续话): caller/peer words + optional frozen
|
|
815
811
|
* attachment paths only — no「请重读」、no code-authored content substitute,
|
|
816
812
|
* no engine handbook packaging (#750/#755/#879).
|
|
@@ -843,12 +839,7 @@ export function resumeTurnRequestProjectionOptions(admitted, request, env, summo
|
|
|
843
839
|
prompt = request.message;
|
|
844
840
|
}
|
|
845
841
|
else {
|
|
846
|
-
|
|
847
|
-
prompt = buildResumeContinuationPrompt({
|
|
848
|
-
packageRoot: env.packageRoot,
|
|
849
|
-
...pickEngineAxis(env),
|
|
850
|
-
message: request.message,
|
|
851
|
-
});
|
|
842
|
+
prompt = request.message;
|
|
852
843
|
}
|
|
853
844
|
}
|
|
854
845
|
else if (summonsPrepared !== undefined) {
|
|
@@ -864,11 +855,7 @@ export function resumeTurnRequestProjectionOptions(admitted, request, env, summo
|
|
|
864
855
|
prompt = "";
|
|
865
856
|
}
|
|
866
857
|
else {
|
|
867
|
-
|
|
868
|
-
prompt = buildResumeContinuationPrompt({
|
|
869
|
-
packageRoot: env.packageRoot,
|
|
870
|
-
...pickEngineAxis(env),
|
|
871
|
-
});
|
|
858
|
+
prompt = "";
|
|
872
859
|
}
|
|
873
860
|
return {
|
|
874
861
|
packageRoot: env.packageRoot,
|
|
@@ -889,9 +876,10 @@ export function resumeTurnRequestProjectionOptions(admitted, request, env, summo
|
|
|
889
876
|
};
|
|
890
877
|
}
|
|
891
878
|
/**
|
|
892
|
-
*
|
|
893
|
-
*
|
|
894
|
-
*
|
|
879
|
+
* Build the turn request, then hand off to dispatch. When a writer lease is
|
|
880
|
+
* held, release it if build throws before handoff — dispatch's finally only
|
|
881
|
+
* runs after this handoff (manual resume and station-child auto-resume).
|
|
882
|
+
* #987: lease is optional; auto-resume no longer pre-acquires before host CLI.
|
|
895
883
|
*/
|
|
896
884
|
async function dispatchAfterWriterLease(input) {
|
|
897
885
|
let handedOffToDispatch = false;
|
|
@@ -901,7 +889,7 @@ async function dispatchAfterWriterLease(input) {
|
|
|
901
889
|
return await input.dispatch(request);
|
|
902
890
|
}
|
|
903
891
|
finally {
|
|
904
|
-
if (!handedOffToDispatch) {
|
|
892
|
+
if (!handedOffToDispatch && input.lease !== undefined) {
|
|
905
893
|
await input.lease.release();
|
|
906
894
|
}
|
|
907
895
|
}
|
|
@@ -916,7 +904,7 @@ function isAlreadyFrozenSummonsAttachment(runDirectory, attachmentPath) {
|
|
|
916
904
|
* Freeze same-ticket summons attachments into the retained run directory (#637).
|
|
917
905
|
* No-op materials (no paths / instruction-only) skip the freeze.
|
|
918
906
|
* Paths already under this run's attachments/ are the accepted freeze identity —
|
|
919
|
-
* reuse them
|
|
907
|
+
* reuse them for the same internal re-summons flow.
|
|
920
908
|
* Manual resume never calls this — old attachment semantics stay intact.
|
|
921
909
|
*/
|
|
922
910
|
export async function prepareSummonsResumeMaterials(runDirectory, summons) {
|
|
@@ -937,23 +925,22 @@ export async function prepareSummonsResumeMaterials(runDirectory, summons) {
|
|
|
937
925
|
return { instruction, instructionEmpty, attachments };
|
|
938
926
|
}
|
|
939
927
|
/**
|
|
940
|
-
* Shared manual-resume orchestration for seats
|
|
941
|
-
*
|
|
928
|
+
* Shared manual-resume orchestration for seats (#599 / #633):
|
|
929
|
+
* load once → structural rejection →
|
|
942
930
|
* optional seat afterAdmittedLoad (method material / controlled failure) →
|
|
943
931
|
* seat turn projection → station-child auto-resume or public manual resume.
|
|
944
932
|
* Seat-owned loader
|
|
945
933
|
* validation, turn builder, and adapters stay on the seat.
|
|
946
934
|
*
|
|
947
|
-
* Court
|
|
948
|
-
*
|
|
949
|
-
* and record. No pre-lease clear or stale court-snapshot consumption.
|
|
935
|
+
* Court handling (#637): public manual resume reads only the open court's
|
|
936
|
+
* settlement identity; internal re-summons may freeze and record its materials.
|
|
950
937
|
*/
|
|
951
938
|
export async function runPostAdmissionSeatResume(input) {
|
|
952
939
|
let request = input.request;
|
|
953
940
|
// Load once for runDirectory / structural rejection / afterAdmittedLoad.
|
|
954
941
|
// Court identity for public manual resume is judged in the turn builder
|
|
955
942
|
// (#987: no package writer-lease gate before host CLI resume). Station-child
|
|
956
|
-
// auto-resume
|
|
943
|
+
// auto-resume shares that rule via runWithAutoResumeLoop (no pre-acquire).
|
|
957
944
|
let loaded;
|
|
958
945
|
try {
|
|
959
946
|
loaded = await input.load(request);
|
|
@@ -982,32 +969,17 @@ export async function runPostAdmissionSeatResume(input) {
|
|
|
982
969
|
let preparedCleanup;
|
|
983
970
|
const buildRequestAfterLease = async () => {
|
|
984
971
|
let openCourtAttemptId;
|
|
985
|
-
//
|
|
986
|
-
|
|
987
|
-
//
|
|
988
|
-
|
|
989
|
-
// Bare resume: open-court pointer is the continue signal (not ledger seal).
|
|
972
|
+
// Settlement identity stays on the outer admitted.
|
|
973
|
+
const admittedForBuild = loaded.admitted;
|
|
974
|
+
// Bare resume keeps the open court's settlement identity, but public resume
|
|
975
|
+
// never re-delivers the prior summons or its attachments (#987).
|
|
990
976
|
if (request.summons === undefined) {
|
|
991
977
|
const openCourt = await readCurrentCourt(admittedForBuild.runDirectory);
|
|
992
978
|
if (openCourt !== undefined) {
|
|
993
979
|
openCourtAttemptId = openCourt.courtAttemptId;
|
|
994
|
-
request = {
|
|
995
|
-
runId: request.runId,
|
|
996
|
-
...(request.message === undefined
|
|
997
|
-
? {}
|
|
998
|
-
: { message: request.message }),
|
|
999
|
-
...(openCourt.summons === undefined
|
|
1000
|
-
? {}
|
|
1001
|
-
: { summons: openCourt.summons }),
|
|
1002
|
-
};
|
|
1003
|
-
if (openCourt.summons !== undefined) {
|
|
1004
|
-
const reloaded = await input.load(request);
|
|
1005
|
-
admittedForBuild = reloaded.admitted;
|
|
1006
|
-
}
|
|
1007
980
|
}
|
|
1008
981
|
}
|
|
1009
|
-
//
|
|
1010
|
-
// currentCourt + later bare resume reuse the accepted snapshot.
|
|
982
|
+
// Internal re-summons freezes external paths once and records that identity.
|
|
1011
983
|
if (request.summons !== undefined) {
|
|
1012
984
|
const prepared = await prepareSummonsResumeMaterials(admittedForBuild.runDirectory, request.summons);
|
|
1013
985
|
if (prepared !== undefined &&
|
|
@@ -1045,10 +1017,10 @@ export async function runPostAdmissionSeatResume(input) {
|
|
|
1045
1017
|
}
|
|
1046
1018
|
return turnRequest;
|
|
1047
1019
|
};
|
|
1048
|
-
// Court recovery / open, then dispatch. Public manual resume
|
|
1049
|
-
//
|
|
1050
|
-
// Station-child same-ticket/same-parent resume is
|
|
1051
|
-
// (#840 / #416)
|
|
1020
|
+
// Court recovery / open, then dispatch. Public manual resume and station-child
|
|
1021
|
+
// auto-resume both pass through to the host CLI without a package writer-lease
|
|
1022
|
+
// pre-gate (#987 / ADR 0080). Station-child same-ticket/same-parent resume is
|
|
1023
|
+
// still call-local auto-resume (#840 / #416).
|
|
1052
1024
|
// afterAdmittedPrepare runs inside this try so mint failure and cleanup share one finally.
|
|
1053
1025
|
try {
|
|
1054
1026
|
if (input.afterAdmittedPrepare !== undefined) {
|
|
@@ -1079,14 +1051,13 @@ export async function runPostAdmissionSeatResume(input) {
|
|
|
1079
1051
|
// Same as public manual resume: prior-court sealed acceptance is not a
|
|
1080
1052
|
// redispatch brake (#833). New-court station-child turns still auto-resume.
|
|
1081
1053
|
dispatch: async (payload, lease, _isFirst, attemptIo) => dispatchAfterWriterLease({
|
|
1082
|
-
lease,
|
|
1054
|
+
...(lease === undefined ? {} : { lease }),
|
|
1083
1055
|
build: async () => {
|
|
1084
1056
|
// #840 r8 判词 class 2: this call-local retry must keep this
|
|
1085
1057
|
// court's frozen summons / 交卷 body / attachments verbatim
|
|
1086
1058
|
// (same object as firstTurn) and project only the minimal
|
|
1087
|
-
// host-needed resume trigger — never
|
|
1088
|
-
//
|
|
1089
|
-
// a 审核循环 same-ticket continuation with a bare outsourcing
|
|
1059
|
+
// host-needed resume trigger — never engine handbook material,
|
|
1060
|
+
// which would replace a 审核循环 same-ticket continuation with a bare outsourcing
|
|
1090
1061
|
// 「重新读」 envelope (#755 contract, resumeTurnRequestProjectionOptions
|
|
1091
1062
|
// above). RESUME_TRANSPORT_ENVELOPE is the same package-owned,
|
|
1092
1063
|
// non-semantic trigger that projection already uses for a
|
|
@@ -1110,7 +1081,7 @@ export async function runPostAdmissionSeatResume(input) {
|
|
|
1110
1081
|
env,
|
|
1111
1082
|
io: attemptIo,
|
|
1112
1083
|
request: turnRequest,
|
|
1113
|
-
lease,
|
|
1084
|
+
...(lease === undefined ? {} : { lease }),
|
|
1114
1085
|
adapters: stationAdapters,
|
|
1115
1086
|
persistRunState: false,
|
|
1116
1087
|
...(input.effectiveEngine === undefined
|
|
@@ -1134,8 +1105,7 @@ export async function runPostAdmissionSeatResume(input) {
|
|
|
1134
1105
|
});
|
|
1135
1106
|
}
|
|
1136
1107
|
catch (error) {
|
|
1137
|
-
//
|
|
1138
|
-
// rejection (e.g. notary rejects caller message) — same exit face as pre-lease.
|
|
1108
|
+
// Seat preparation may still surface structural rejection.
|
|
1139
1109
|
if (error instanceof CliUsageError) {
|
|
1140
1110
|
presentStructuralRejection(error, input.io);
|
|
1141
1111
|
return { exitCode: 2 };
|
|
@@ -1209,7 +1179,7 @@ export async function runPostAdmissionResumable(input) {
|
|
|
1209
1179
|
},
|
|
1210
1180
|
io: attemptIo,
|
|
1211
1181
|
request,
|
|
1212
|
-
lease,
|
|
1182
|
+
...(lease === undefined ? {} : { lease }),
|
|
1213
1183
|
adapters,
|
|
1214
1184
|
persistRunState: false,
|
|
1215
1185
|
// #600: every attempt (initial + auto-resume) writes seat engine when present.
|
|
@@ -1223,8 +1193,8 @@ export async function runPostAdmissionResumable(input) {
|
|
|
1223
1193
|
* Manual resume: pass-through to the host CLI resume — no package writer-lease
|
|
1224
1194
|
* pre-gate (#987), no sealed-accepted short-circuit (#833 / #416). Court open
|
|
1225
1195
|
* (summons / message / open court) is built when using buildRequestAfterLease;
|
|
1226
|
-
* sole-final stays per-attempt. Station-child auto-resume
|
|
1227
|
-
*
|
|
1196
|
+
* sole-final stays per-attempt. Station-child auto-resume shares the same
|
|
1197
|
+
* no-pre-gate rule via runWithAutoResumeLoop + dispatchAfterWriterLease.
|
|
1228
1198
|
*/
|
|
1229
1199
|
export async function runPostAdmissionManualResume(input) {
|
|
1230
1200
|
const { admitted, env, io, adapters, effectiveEngine, buildRequestAfterLease, } = input;
|
|
@@ -301,7 +301,7 @@ export async function runPublicReviewerResume(request, env, io) {
|
|
|
301
301
|
...(sandbox.executionCwd === undefined ? {} : { cwd: sandbox.executionCwd }),
|
|
302
302
|
continuation: {
|
|
303
303
|
kind: "resume",
|
|
304
|
-
prompt:
|
|
304
|
+
prompt: effective.message ?? "",
|
|
305
305
|
},
|
|
306
306
|
});
|
|
307
307
|
},
|
|
@@ -9,7 +9,7 @@ import { chmod, lstat, open, readdir, readFile, unlink, writeFile } from "node:f
|
|
|
9
9
|
import { basename, isAbsolute, join } from "node:path";
|
|
10
10
|
import { activationBookDirectory, resolveActivationLedgerHome, } from "../activation-ledger-topology.js";
|
|
11
11
|
import { listBookRunDirectories } from "../role-run-placement.js";
|
|
12
|
-
import { isSafePositiveTicketNumber
|
|
12
|
+
import { isSafePositiveTicketNumber } from "../run-ticket-number.js";
|
|
13
13
|
import { CliUsageError } from "./cli-errors.js";
|
|
14
14
|
import { readLatestTypedProviderHttpObservation, } from "../typed-provider-http.js";
|
|
15
15
|
export { clearTypedProviderHttpObservation, recordTypedProviderHttpStatus, readLatestTypedProviderHttpObservation, } from "../typed-provider-http.js";
|
|
@@ -31,44 +31,20 @@ export const AUTO_RESUME_LIMIT = 2;
|
|
|
31
31
|
* Package-owned non-empty Chinese neutral resume transport (#959 / ADR 0073).
|
|
32
32
|
* Used by:
|
|
33
33
|
* - auto-resume (all seats via buildAutoResumeContinuationPrompt) — required so
|
|
34
|
-
* hosts that reject empty stdin (codex) still receive a prompt
|
|
35
|
-
*
|
|
36
|
-
* that seat's own manual entry.
|
|
37
|
-
* Generic bare `ak-role resume` stays empty-capable via selectResumeContinuationPrompt
|
|
38
|
-
* / buildResumeContinuationPrompt — ADR 0080 keeps auto and generic-manual entries separate.
|
|
34
|
+
* hosts that reject empty stdin (codex) still receive a prompt.
|
|
35
|
+
* Public manual resume forwards only the caller's bytes (#987).
|
|
39
36
|
*/
|
|
40
37
|
export const RESUME_TRANSPORT_ENVELOPE = "继续。";
|
|
41
|
-
/**
|
|
42
|
-
* Manual resume continuation selector (#471 / #600 / #736 / ADR 0080).
|
|
43
|
-
* Message present → those bytes; absent → engine pointers only (may be empty).
|
|
44
|
-
* Caller message (including blank) still wins verbatim when supplied.
|
|
45
|
-
* Auto-resume must use buildAutoResumeContinuationPrompt — do not fold the
|
|
46
|
-
* non-empty Chinese envelope into this shared manual selector (#959).
|
|
47
|
-
*/
|
|
48
|
-
export function selectResumeContinuationPrompt(message, engineMaterial) {
|
|
49
|
-
const lines = message !== undefined ? [message] : [];
|
|
50
|
-
return appendEngineSessionMaterial(lines, engineMaterial).join("\n");
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Manual resume continuation with engine material from the seat env (#600).
|
|
54
|
-
* Bare manual resume stays empty-prompt-capable; auto-resume is a separate entry.
|
|
55
|
-
*/
|
|
56
|
-
export function buildResumeContinuationPrompt(options) {
|
|
57
|
-
return selectResumeContinuationPrompt(options.message, engineSessionMaterialFromOptions({
|
|
58
|
-
packageRoot: options.packageRoot,
|
|
59
|
-
...pickEngineAxis(options),
|
|
60
|
-
}));
|
|
61
|
-
}
|
|
62
38
|
/**
|
|
63
39
|
* Auto-resume continuation only (#959 / ADR 0080).
|
|
64
40
|
* Always non-empty: Chinese neutral envelope plus optional engine pointers.
|
|
65
41
|
* Never call this from manual `ak-role resume`.
|
|
66
42
|
*/
|
|
67
43
|
export function buildAutoResumeContinuationPrompt(options) {
|
|
68
|
-
return
|
|
44
|
+
return appendEngineSessionMaterial([RESUME_TRANSPORT_ENVELOPE], engineSessionMaterialFromOptions({
|
|
69
45
|
packageRoot: options.packageRoot,
|
|
70
46
|
...pickEngineAxis(options),
|
|
71
|
-
}));
|
|
47
|
+
})).join("\n");
|
|
72
48
|
}
|
|
73
49
|
const RUN_STATE_FILE = "run-state.json";
|
|
74
50
|
const WRITER_LOCK_FILE = "writer.lock";
|
|
@@ -398,8 +374,7 @@ export async function markRunTerminal(runDirectory) {
|
|
|
398
374
|
if (current === undefined) {
|
|
399
375
|
throw new Error("cannot mark terminal: run state missing");
|
|
400
376
|
}
|
|
401
|
-
// Preserve open
|
|
402
|
-
// let bare resume continue that court (#637).
|
|
377
|
+
// Preserve the open court's settlement identity after a failed/incomplete turn (#637).
|
|
403
378
|
await writeRoleRunStateDisk(runDirectory, {
|
|
404
379
|
runId: current.runId,
|
|
405
380
|
role: current.role,
|
|
@@ -843,13 +818,14 @@ async function runHasFormedSessionPrincipal(runDirectory) {
|
|
|
843
818
|
/**
|
|
844
819
|
* Locate the latest retained run for one seat under a book (#637 / #747).
|
|
845
820
|
* Same walk surface as findRunDirectoryById (listBookRunDirectories). Match by
|
|
846
|
-
* parent run path (officer seats, #747
|
|
847
|
-
*
|
|
848
|
-
*
|
|
821
|
+
* parent run path (officer / gate seats, #747 / #987). runId is UUIDv7 —
|
|
822
|
+
* lexicographic max is latest among runs that formed a session principal. No
|
|
823
|
+
* parallel index. Public ticket-number selection of a prior run was deleted
|
|
824
|
+
* (#987 Result 7); callers use explicit `ak-role resume <runId>`.
|
|
849
825
|
* Only a truly missing book directory means no history; damage/permission errors propagate.
|
|
850
826
|
*/
|
|
851
827
|
export async function findLatestRunIdForSeatTicket(input) {
|
|
852
|
-
if (input.
|
|
828
|
+
if (input.parentRunPath.trim() === "") {
|
|
853
829
|
return undefined;
|
|
854
830
|
}
|
|
855
831
|
const ledgerHome = resolveActivationLedgerHome(input.home);
|
|
@@ -872,17 +848,9 @@ export async function findLatestRunIdForSeatTicket(input) {
|
|
|
872
848
|
const runId = entry.slice(0, entry.length - suffix.length);
|
|
873
849
|
if (runId.length === 0)
|
|
874
850
|
continue;
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
continue;
|
|
879
|
-
}
|
|
880
|
-
else if (input.ticketNumber !== undefined) {
|
|
881
|
-
// Same-ticket resume is board identity only — never migration-derived.
|
|
882
|
-
const ticketNumber = await readBoardTicketNumber(runDirectory);
|
|
883
|
-
if (ticketNumber !== input.ticketNumber)
|
|
884
|
-
continue;
|
|
885
|
-
}
|
|
851
|
+
const parentPath = await readRunParentPath(runDirectory);
|
|
852
|
+
if (parentPath !== input.parentRunPath)
|
|
853
|
+
continue;
|
|
886
854
|
// Durable fact: never resume-select a provisional that never formed principal.
|
|
887
855
|
if (!(await runHasFormedSessionPrincipal(runDirectory)))
|
|
888
856
|
continue;
|
|
@@ -1,43 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared
|
|
2
|
+
* Shared same-parent resume seam for gate / officer seats (#635 / #637 / #747 / #987).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* This module owns same-ticket / same-parent resume only.
|
|
4
|
+
* Lookup key is parent run path only (#747 / #987 Result 7). Public seats no
|
|
5
|
+
* longer select a prior run by ticket number — callers use explicit
|
|
6
|
+
* `ak-role resume <runId>`. Officer and gate countersign same-parent re-summons
|
|
7
|
+
* keep this seam. Lookup/resume failures propagate (失败诚实) — never wash into
|
|
8
|
+
* a fresh mint. Returns undefined when the caller declared an explicit fresh
|
|
9
|
+
* summons (`ak-role new`) or when no prior run exists; both mint new.
|
|
10
|
+
* freshSummons is required so no seat can drift back into its own skip branch.
|
|
13
11
|
*/
|
|
14
12
|
import { resolveBookKeyFromGit } from "../activation-ledger-git.js";
|
|
15
13
|
import { findLatestRunIdForSeatTicket, } from "./run-lifecycle.js";
|
|
16
14
|
/**
|
|
17
|
-
* Sole same-seat → resume decision (#637 / #724 / #747).
|
|
18
|
-
* Officer seats (notary/inspector/auditor) look up by parent run path; countersign
|
|
19
|
-
* / diarist keep ticket-number principal. When found, runs resume with this
|
|
20
|
-
* summons' materials. Lookup/resume failures propagate (失败诚实) — never wash
|
|
21
|
-
* into a fresh mint. Returns undefined when the caller declared an explicit
|
|
22
|
-
* fresh summons (`ak-role new`) or when no prior run exists; both mint new.
|
|
23
|
-
* freshSummons is required so no seat can drift back into its own skip branch.
|
|
15
|
+
* Sole same-seat → resume decision by parent run path (#637 / #724 / #747 / #987).
|
|
24
16
|
*/
|
|
25
17
|
export async function tryResumeSameTicketSeatRun(input) {
|
|
26
18
|
if (input.freshSummons === true)
|
|
27
19
|
return undefined;
|
|
28
|
-
if (input.parentRunPath
|
|
20
|
+
if (input.parentRunPath.trim() === "")
|
|
29
21
|
return undefined;
|
|
30
|
-
}
|
|
31
22
|
const previousRunId = await findLatestRunIdForSeatTicket({
|
|
32
23
|
home: input.home,
|
|
33
24
|
bookKey: resolveBookKeyFromGit(input.projectRoot),
|
|
34
25
|
role: input.role,
|
|
35
|
-
|
|
36
|
-
? {}
|
|
37
|
-
: { parentRunPath: input.parentRunPath }),
|
|
38
|
-
...(input.ticketNumber === undefined
|
|
39
|
-
? {}
|
|
40
|
-
: { ticketNumber: input.ticketNumber }),
|
|
26
|
+
parentRunPath: input.parentRunPath,
|
|
41
27
|
});
|
|
42
28
|
if (previousRunId === undefined)
|
|
43
29
|
return undefined;
|
|
@@ -205,6 +205,7 @@ async function summonPublicRole(options) {
|
|
|
205
205
|
...options.reviewReask === void 0 ? {} : { reviewReask: options.reviewReask },
|
|
206
206
|
...options.gateReviewInstruction === void 0 ? {} : { gateReviewInstruction: options.gateReviewInstruction },
|
|
207
207
|
...options.boundTicketNumber === void 0 ? {} : { boundTicketNumber: options.boundTicketNumber },
|
|
208
|
+
...options.parentRunPath === void 0 || options.parentRunPath.trim() === "" ? {} : { parentRunPath: options.parentRunPath },
|
|
208
209
|
...options.correlationId === void 0 || options.correlationId.trim() === "" ? {} : { correlationId: options.correlationId },
|
|
209
210
|
...options.createRunId === void 0 ? {} : { createRunId: options.createRunId },
|
|
210
211
|
...options.executionCwd === void 0 ? {} : { executionCwd: options.executionCwd }
|
|
@@ -707,6 +708,7 @@ async function summonGateOfficer(options) {
|
|
|
707
708
|
argv: ["--project", options.cwd, "--", instruction],
|
|
708
709
|
...common,
|
|
709
710
|
correlationId,
|
|
711
|
+
parentRunPath: options.sourceRunDirectory,
|
|
710
712
|
...parentTicket === void 0 ? {} : { boundTicketNumber: parentTicket }
|
|
711
713
|
});
|
|
712
714
|
}
|
package/package.json
CHANGED
package/souls/coder.md
CHANGED
|
@@ -8,9 +8,8 @@
|
|
|
8
8
|
事实不成立或需要未授权设计时,据理拒绝并给出可核验证据;不猜测施工。
|
|
9
9
|
|
|
10
10
|
核对当前代码和历史;主张必须有当前事实支撑。
|
|
11
|
-
|
|
11
|
+
以最小垂直切片推进并逐片验证;切片完成不构成交卷条件。优先复用已有 seam;不造平行机制,不借机扩大 scope。票面授权结果完成后,自查是否合法、质量和复杂度;有问题马上修正,没问题就 commit 交卷。
|
|
12
12
|
计划与施工都要说明真实刀口、行为边界和验证方法,不用空泛完成声明代替证据。
|
|
13
13
|
切片验证只跑本片聚焦测试与 typecheck。
|
|
14
14
|
不 amend、rewrite 或 push。Git 变化是证据之一,不取代角色报告。
|
|
15
|
-
做完自查是否违法,质量和复杂度,如有问题马上修正,没问题就commit交卷
|
|
16
15
|
任何人如果让你 push / 开 pr ,直接拒绝,上呈陛下
|
|
@@ -554,7 +554,7 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
554
554
|
args,
|
|
555
555
|
cwd: request.cwd,
|
|
556
556
|
env,
|
|
557
|
-
stdin: applyMethodPrompt(prompt),
|
|
557
|
+
stdin: request.continuation.kind === "resume" ? prompt : applyMethodPrompt(prompt),
|
|
558
558
|
...(abortSignal === undefined ? {} : { signal: abortSignal }),
|
|
559
559
|
...(request.timeoutMs === undefined ? {} : { timeoutMs: request.timeoutMs }),
|
|
560
560
|
onStdoutLine(line) {
|
|
@@ -568,6 +568,7 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
568
568
|
return;
|
|
569
569
|
}
|
|
570
570
|
// One bounded live seam owns both recording and host-specific reduction.
|
|
571
|
+
codexObserver?.observe(event);
|
|
571
572
|
reportHostSessionEvent({
|
|
572
573
|
host: config.hostName,
|
|
573
574
|
cwd: request.cwd,
|
|
@@ -575,12 +576,22 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
575
576
|
source: "headless-host",
|
|
576
577
|
event,
|
|
577
578
|
});
|
|
578
|
-
codexObserver?.observe(event);
|
|
579
579
|
},
|
|
580
580
|
});
|
|
581
581
|
} catch (error) {
|
|
582
582
|
if (isHostAbortedError(error)) throw error;
|
|
583
583
|
const message = error instanceof Error ? error.message : String(error);
|
|
584
|
+
const observedHostFailure = codexObserver?.result().failureDiagnostic;
|
|
585
|
+
if (observedHostFailure !== undefined) {
|
|
586
|
+
return {
|
|
587
|
+
status: "terminal",
|
|
588
|
+
result: failure("output", "HeadlessCliError", "codex-turn-failed", {
|
|
589
|
+
diagnostic: observedHostFailure,
|
|
590
|
+
sessionRecordDiagnostic: message,
|
|
591
|
+
sessionId,
|
|
592
|
+
}, observedHostFailure),
|
|
593
|
+
};
|
|
594
|
+
}
|
|
584
595
|
// SitianInfrastructureError.knownCause is session; spawn errno stays activation.
|
|
585
596
|
const isRecordFailure =
|
|
586
597
|
typeof error === "object"
|
|
@@ -618,23 +629,34 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
618
629
|
|
|
619
630
|
if (codex) {
|
|
620
631
|
const observation = codexObserver!.result();
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
632
|
+
// #987 result 6 / 失败诚实: host-reported failure wins over a package
|
|
633
|
+
// missing-thread-id label or package persistence failure.
|
|
634
|
+
if (observation.threadId !== undefined && observation.threadId !== "") {
|
|
635
|
+
sessionId = observation.threadId;
|
|
636
|
+
}
|
|
637
|
+
let sessionBindingDiagnostic: string | undefined;
|
|
638
|
+
const hostFailed = observation.failureDiagnostic !== undefined
|
|
639
|
+
|| (spawned.code !== 0 && spawned.code !== null);
|
|
640
|
+
if (hostFailed && observation.threadId !== undefined && observation.threadId !== "") {
|
|
641
|
+
try {
|
|
642
|
+
await config.sessionIdentity.bind(request.principal, observation.threadId);
|
|
643
|
+
} catch (error) {
|
|
644
|
+
// The host failure remains primary; retain persistence failure as
|
|
645
|
+
// secondary typed evidence instead of replacing the terminal.
|
|
646
|
+
sessionBindingDiagnostic = error instanceof Error ? error.message : String(error);
|
|
647
|
+
}
|
|
628
648
|
}
|
|
629
|
-
sessionId = observation.threadId;
|
|
630
|
-
await config.sessionIdentity.bind(request.principal, sessionId);
|
|
631
649
|
|
|
632
650
|
if (observation.failureDiagnostic !== undefined) {
|
|
633
651
|
return terminalFromSpawned(spawned, {
|
|
634
652
|
cause: "output",
|
|
635
653
|
identity: { name: "HeadlessCliError", code: "codex-turn-failed" },
|
|
636
654
|
diagnostic: observation.failureDiagnostic,
|
|
637
|
-
details: {
|
|
655
|
+
details: {
|
|
656
|
+
sessionId,
|
|
657
|
+
exitCode: spawned.code,
|
|
658
|
+
...(sessionBindingDiagnostic === undefined ? {} : { sessionBindingDiagnostic }),
|
|
659
|
+
},
|
|
638
660
|
});
|
|
639
661
|
}
|
|
640
662
|
|
|
@@ -644,9 +666,24 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
644
666
|
cause: "output",
|
|
645
667
|
identity: { name: "HeadlessCliError", code: "codex-nonzero-exit" },
|
|
646
668
|
diagnostic: spawned.stderr.trim() || `codex exec exited ${String(spawned.code)}`,
|
|
669
|
+
details: {
|
|
670
|
+
sessionId,
|
|
671
|
+
exitCode: spawned.code,
|
|
672
|
+
...(sessionBindingDiagnostic === undefined ? {} : { sessionBindingDiagnostic }),
|
|
673
|
+
},
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if (observation.threadId === undefined || observation.threadId === "") {
|
|
678
|
+
return terminalFromSpawned(spawned, {
|
|
679
|
+
cause: "session",
|
|
680
|
+
identity: { name: "HeadlessMissingThreadId", code: "missing-thread-id" },
|
|
681
|
+
diagnostic: "codex exec emitted no thread.started thread_id",
|
|
647
682
|
details: { sessionId, exitCode: spawned.code },
|
|
648
683
|
});
|
|
649
684
|
}
|
|
685
|
+
sessionId = observation.threadId;
|
|
686
|
+
await config.sessionIdentity.bind(request.principal, sessionId);
|
|
650
687
|
|
|
651
688
|
if (!observation.turnCompleted) {
|
|
652
689
|
return terminalFromSpawned(spawned, {
|
package/src/pi/role-turn-host.ts
CHANGED
|
@@ -164,7 +164,7 @@ function piUserDialogueBody(request: RoleTurnRequest): string {
|
|
|
164
164
|
const _exhaustive: never = request.continuation;
|
|
165
165
|
return _exhaustive;
|
|
166
166
|
})();
|
|
167
|
-
return request.continuation.kind === "resume"
|
|
167
|
+
return request.continuation.kind === "resume"
|
|
168
168
|
? rawPrompt
|
|
169
169
|
: applyPiNativeSkillInvocation(request.methods, rawPrompt);
|
|
170
170
|
}
|
|
@@ -37,7 +37,6 @@ import { isLawfulTypedTerminalOutcome, formatTerminalResult, type TerminalArtifa
|
|
|
37
37
|
import {
|
|
38
38
|
attachRecordedSubmissions,
|
|
39
39
|
presentFailureTerminal,
|
|
40
|
-
presentStructuralRejection,
|
|
41
40
|
resolveControlledFailureResumeObservation,
|
|
42
41
|
} from "./settlement.ts";
|
|
43
42
|
import type { CliIo } from "./cli-io.ts";
|
|
@@ -481,7 +480,17 @@ export async function runWithAutoResumeLoop<
|
|
|
481
480
|
signal?: AbortSignal;
|
|
482
481
|
buildInitialPayload: () => TPayload;
|
|
483
482
|
buildResumePayload: () => TPayload;
|
|
484
|
-
|
|
483
|
+
/**
|
|
484
|
+
* #987: the initial turn retains the existing writer lease/liveness guard;
|
|
485
|
+
* actual resume attempts pass no lease so a live holder cannot pre-block the
|
|
486
|
+
* host CLI's own continuation contract.
|
|
487
|
+
*/
|
|
488
|
+
dispatch: (
|
|
489
|
+
payload: TPayload,
|
|
490
|
+
lease: RunWriterLease | undefined,
|
|
491
|
+
isFirst: boolean,
|
|
492
|
+
attemptIo: CliIo,
|
|
493
|
+
) => Promise<T>;
|
|
485
494
|
}): Promise<T> {
|
|
486
495
|
// #422 single-point resolution + domain validation. NaN would bypass every
|
|
487
496
|
// `attempts >= limit` comparison (always false) — reject here, before any dispatch.
|
|
@@ -499,19 +508,6 @@ export async function runWithAutoResumeLoop<
|
|
|
499
508
|
const retainedErrorFiles: string[] = [];
|
|
500
509
|
|
|
501
510
|
while (true) {
|
|
502
|
-
let lease: RunWriterLease;
|
|
503
|
-
try {
|
|
504
|
-
lease = await acquireRunWriterLease(options.admitted.runDirectory, (diagnostic) =>
|
|
505
|
-
options.io.stderr(diagnostic),
|
|
506
|
-
);
|
|
507
|
-
} catch (error) {
|
|
508
|
-
if (error instanceof RunWriterLeaseHeldError) {
|
|
509
|
-
presentStructuralRejection(error, options.io);
|
|
510
|
-
return { exitCode: 2 } as T;
|
|
511
|
-
}
|
|
512
|
-
throw error;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
511
|
let result: T | undefined;
|
|
516
512
|
// Set only when the caught throw is a TurnDispatchedFailure (#840 r9 判词
|
|
517
513
|
// class 1): the host turn genuinely started this attempt even though
|
|
@@ -519,13 +515,18 @@ export async function runWithAutoResumeLoop<
|
|
|
519
515
|
// not a replay of the initial one.
|
|
520
516
|
let turnStartedBeforeThrow = false;
|
|
521
517
|
try {
|
|
518
|
+
// #987: guard the initial new turn with the existing lease, but do not
|
|
519
|
+
// pre-block a real host CLI resume on a package-side live holder.
|
|
520
|
+
const lease = isFirst
|
|
521
|
+
? await acquireRunWriterLease(options.admitted.runDirectory)
|
|
522
|
+
: undefined;
|
|
522
523
|
result = await options.dispatch(currentPayload, lease, isFirst, dummyIo);
|
|
523
524
|
} catch (error) {
|
|
524
525
|
// Owner 2026-08-23: 「出了异常,就原地记录错误信息,然后重试。」
|
|
525
526
|
// Retain the whole exception in place (per-attempt full file + dossier
|
|
526
527
|
// pointer); recording failure must not break the retry path (PR #418
|
|
527
|
-
// diagnostic-sink-isolation precedent).
|
|
528
|
-
//
|
|
528
|
+
// diagnostic-sink-isolation precedent). When a lease is held inside
|
|
529
|
+
// dispatch, that path owns release in its own finally.
|
|
529
530
|
lastThrownError = error;
|
|
530
531
|
turnStartedBeforeThrow = error instanceof TurnDispatchedFailure;
|
|
531
532
|
const attempt = dispatchOrdinal;
|
|
@@ -680,7 +681,7 @@ export async function runWithAutoResumeLoop<
|
|
|
680
681
|
// throws and beforeDispatch failures retry the initial payload (#840 / #416).
|
|
681
682
|
if (result?.turnDispatched === true || turnStartedBeforeThrow) {
|
|
682
683
|
currentPayload = options.buildResumePayload();
|
|
684
|
+
isFirst = false;
|
|
683
685
|
}
|
|
684
|
-
isFirst = false;
|
|
685
686
|
}
|
|
686
687
|
}
|