@akagilnc/pi-workflow-roles 0.1.3902 → 0.1.3932
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 +3 -3
- package/README.zh-CN.md +4 -4
- package/dist/acp-host/production-host.js +2540 -1994
- package/dist/compliance-transport.js +4 -1
- package/dist/headless-host/production-host.js +2274 -1728
- package/dist/prepared-role-turn.js +5 -0
- package/dist/public-cli/auto-resume.js +115 -13
- package/dist/public-cli/diarist-run.js +154 -0
- package/dist/public-cli/main.js +7082 -5854
- package/dist/public-cli/option-definitions.js +1 -1
- package/dist/public-cli/post-admission.js +455 -188
- package/dist/public-cli/role-turn-host-resolution.js +59 -0
- package/dist/public-cli/run-lifecycle.js +11 -6
- package/dist/public-cli/turn-request.js +1 -0
- package/dist/public-role-summons.js +48 -42
- package/dist/role-activation-flags.js +3 -0
- package/dist/role-runtime.js +14 -3
- package/dist/session-identity.js +58 -0
- package/package.json +1 -1
- package/src/compliance-transport.ts +6 -1
- package/src/host-contracts.ts +2 -0
- package/src/public-cli/auto-resume.ts +157 -11
- package/src/public-cli/cli.ts +25 -85
- package/src/public-cli/countersign-run.ts +26 -33
- package/src/public-cli/option-definitions.ts +1 -1
- package/src/public-cli/post-admission.ts +514 -159
- package/src/public-cli/reviewer-run.ts +0 -2
- package/src/public-cli/role-turn-host-resolution.ts +122 -0
- package/src/public-cli/run-lifecycle.ts +15 -10
- package/src/public-cli/turn-request.ts +3 -0
- package/src/public-role-summons.ts +71 -54
- package/src/role-activation-flags.ts +3 -0
- package/src/role-runtime.ts +14 -2
- package/src/session-identity.ts +34 -0
|
@@ -12,6 +12,7 @@ import { buildResumeContinuationPrompt, RESUME_TRANSPORT_ENVELOPE, } from "./run
|
|
|
12
12
|
import { CliUsageError } from "./cli-errors.js";
|
|
13
13
|
import { buildInstructionTransportPrompt, freezeAttachmentsIntoRun, } from "./invocation.js";
|
|
14
14
|
import { pathContainedIn } from "../activation-ledger-topology.js";
|
|
15
|
+
import { resolveHostAwareSessionAvailability } from "../session-identity.js";
|
|
15
16
|
import { projectCaseDossierPointerSection } from "./case-dossier-delivery.js";
|
|
16
17
|
/** Append one system section to a continuation prompt, keeping its kind. */
|
|
17
18
|
function appendContinuationSection(continuation, section) {
|
|
@@ -22,12 +23,72 @@ function appendContinuationSection(continuation, section) {
|
|
|
22
23
|
}
|
|
23
24
|
import { projectHostTransitionPriorNative } from "../host-transition-prior-native.js";
|
|
24
25
|
import { missingCredentialPreDispatchFailure, postRunMissingCredentialFailure, } from "./public-run-credentials.js";
|
|
25
|
-
import { acquireRunWriterLease, clearCurrentCourt, clearTypedProviderHttpObservation,
|
|
26
|
+
import { acquireRunWriterLease, clearCurrentCourt, clearTypedProviderHttpObservation, describeErrorIdentity, markRunRunning, readCurrentCourt, recordCurrentCourt, renderResumeCommand, RunWriterLeaseHeldError, } from "./run-lifecycle.js";
|
|
26
27
|
import { homeFromRunDirectory } from "../activation-ledger-topology.js";
|
|
27
28
|
import { readSealedSubmission } from "../submission-ledger.js";
|
|
29
|
+
import { clearReviewerDispatchRejection } from "./reviewer-dispatch-rejection.js";
|
|
28
30
|
import { classifyPostAdmissionFailure, controlledFailureInputFromResolution, exitCodeForTerminalOutcome, explicitInternalKnownFailureClassificationInput, formatCliDiagnostic, formatTerminalResult, inspectJudgeSession, isLawfulTypedTerminalOutcome, presentFailureTerminal, presentStructuralRejection, resolveAuditedRunnerFailureResolution, resolveControlledFailureResumeObservation, settleFailureTerminalResult, sealedAcceptanceRedispatchDisposition, } from "./settlement.js";
|
|
29
31
|
import {} from "./terminal.js";
|
|
30
|
-
import { runWithAutoResumeLoop } from "./auto-resume.js";
|
|
32
|
+
import { ensureRealArtifactsDirectory, persistReturnedRunState, runWithAutoResumeLoop, TurnDispatchedFailure, } from "./auto-resume.js";
|
|
33
|
+
/**
|
|
34
|
+
* Nested station-child role already finished its own call-local loop.
|
|
35
|
+
* Parent records that failure once and must not auto-resume into a re-summon
|
|
36
|
+
* (#840 父子不层叠). Other beforeDispatch failures keep the shared #416 budget.
|
|
37
|
+
*/
|
|
38
|
+
export class StationChildExhaustedError extends Error {
|
|
39
|
+
name = "StationChildExhaustedError";
|
|
40
|
+
}
|
|
41
|
+
function withOnceSuccessfulBeforeDispatch(adapters) {
|
|
42
|
+
const hook = adapters.beforeDispatch;
|
|
43
|
+
if (hook === undefined)
|
|
44
|
+
return adapters;
|
|
45
|
+
let succeeded = false;
|
|
46
|
+
return {
|
|
47
|
+
...adapters,
|
|
48
|
+
beforeDispatch: async (admitted) => {
|
|
49
|
+
if (succeeded)
|
|
50
|
+
return;
|
|
51
|
+
await hook(admitted);
|
|
52
|
+
succeeded = true;
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Session custom-entry type for a best-effort post-dispatch cleanup
|
|
58
|
+
* diagnostic (#840 r9 判词 class 1). Every auto-resume attempt dispatches
|
|
59
|
+
* with dummyIo (src/public-cli/auto-resume.ts), so an io.stderr-only
|
|
60
|
+
* diagnostic never reaches a real caller — the dossier is the durable
|
|
61
|
+
* channel that does (失败诚实宪法 真因必须落痕).
|
|
62
|
+
*/
|
|
63
|
+
export const POST_ADMISSION_CLEANUP_DIAGNOSTIC_ENTRY_TYPE = "ak_post_admission_cleanup_diagnostic";
|
|
64
|
+
/**
|
|
65
|
+
* Best-effort post-dispatch diagnostic that must still leave a real trace
|
|
66
|
+
* even though the attempt's own io may be dummyIo (#840 r9 判词 class 1).
|
|
67
|
+
* Single authoritative durable channel — a dossier custom entry via
|
|
68
|
+
* sessionAppender — not a standing duplicate. Only when that one write
|
|
69
|
+
* itself fails does this fall back to a plain run-artifacts file (reusing
|
|
70
|
+
* auto-resume.ts's existing dispatch-error retention helper — no new
|
|
71
|
+
* mechanism), so an unhealthy session never leaves this diagnostic with
|
|
72
|
+
* zero durable trace (#840 bounce class 2); a healthy session never gets a
|
|
73
|
+
* redundant second copy (#840 bounce class 1: 同一业务规则只保留一个权威实现).
|
|
74
|
+
*/
|
|
75
|
+
async function recordBestEffortPostDispatchDiagnostic(admitted, env, diagnostic, io) {
|
|
76
|
+
io.stderr(formatCliDiagnostic(diagnostic));
|
|
77
|
+
const payload = { diagnostic, recordedAt: new Date().toISOString() };
|
|
78
|
+
try {
|
|
79
|
+
await env.sessionAppender(env.principalAuthority, admitted.principal, POST_ADMISSION_CLEANUP_DIAGNOSTIC_ENTRY_TYPE, payload);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
catch (appendError) {
|
|
83
|
+
try {
|
|
84
|
+
const artifactsDir = await ensureRealArtifactsDirectory(admitted.runDirectory);
|
|
85
|
+
await writeFile(join(artifactsDir, `post-admission-diagnostic-${randomUUID()}.json`), `${JSON.stringify({ version: 1, ...payload }, null, 2)}\n`, { encoding: "utf8", flag: "wx" });
|
|
86
|
+
}
|
|
87
|
+
catch (artifactError) {
|
|
88
|
+
io.stderr(formatCliDiagnostic(`post-dispatch diagnostic durable retention failed on both channels (best-effort continue): dossier=${describeErrorIdentity(appendError)}; artifact=${describeErrorIdentity(artifactError)}`));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
31
92
|
/** Previous main-session host recorded on invocation.json, if any. */
|
|
32
93
|
async function readInvocationHost(runDirectory) {
|
|
33
94
|
try {
|
|
@@ -64,7 +125,7 @@ export async function resolveResumeMethodMaterialAdapters(input) {
|
|
|
64
125
|
return { kind: "terminal", ...terminal };
|
|
65
126
|
}
|
|
66
127
|
}
|
|
67
|
-
export async function presentControlledFailure(admitted, failureInput, adapters, authority, io) {
|
|
128
|
+
export async function presentControlledFailure(admitted, failureInput, adapters, authority, io, persistRunState = true) {
|
|
68
129
|
const hasThrown = Object.hasOwn(failureInput, "thrown");
|
|
69
130
|
const resumeObservation = await resolveControlledFailureResumeObservation({
|
|
70
131
|
runDirectory: admitted.runDirectory,
|
|
@@ -115,11 +176,8 @@ export async function presentControlledFailure(admitted, failureInput, adapters,
|
|
|
115
176
|
const sessionPrincipalAvailable = await authority.isAvailable(admitted.principal);
|
|
116
177
|
resumable = sessionPrincipalAvailable && typedHttp429 !== undefined;
|
|
117
178
|
}
|
|
118
|
-
if (
|
|
119
|
-
await
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
await markRunTerminal(admitted.runDirectory);
|
|
179
|
+
if (persistRunState) {
|
|
180
|
+
await persistReturnedRunState(admitted, authority);
|
|
123
181
|
}
|
|
124
182
|
const terminal = await settleFailureTerminalResult(admitted, failure, authority, resumable
|
|
125
183
|
? { resume: { command: renderResumeCommand(admitted.runId) } }
|
|
@@ -131,13 +189,37 @@ export async function presentControlledFailure(admitted, failureInput, adapters,
|
|
|
131
189
|
terminal,
|
|
132
190
|
};
|
|
133
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* presentControlledFailure, called only where the host turn has already
|
|
194
|
+
* genuinely started (#840 r9 判词 class 1 boundary). If presentControlledFailure
|
|
195
|
+
* itself fails, this never fabricates a replacement terminal (ADR 0080
|
|
196
|
+
* single-settlement-disposition — presentControlledFailure /
|
|
197
|
+
* settleFailureTerminalResult stays the one authority); it re-throws a
|
|
198
|
+
* TurnDispatchedFailure so runWithAutoResumeLoop still learns the turn
|
|
199
|
+
* started and selects a resume payload on the next attempt, while settling
|
|
200
|
+
* the true cause through its own existing dispatch-exception machinery once
|
|
201
|
+
* the retry budget is exhausted.
|
|
202
|
+
*/
|
|
203
|
+
async function settleAfterTurnStarted(admitted, failureInput, adapters, authority, io, persistRunState) {
|
|
204
|
+
try {
|
|
205
|
+
return (await presentControlledFailure(admitted, failureInput, adapters, authority, io, persistRunState));
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
throw new TurnDispatchedFailure(error);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
134
211
|
export async function dispatchPostAdmissionTurn(input) {
|
|
135
212
|
const { admitted, env, io, request, lease, adapters, effectiveEngine } = input;
|
|
213
|
+
const persistRunState = input.persistRunState !== false;
|
|
214
|
+
const deferredPersist = persistRunState ? {} : { needsPersist: true };
|
|
136
215
|
const shouldPresent = adapters.shouldPresentSettled ?? ((terminal) => isLawfulTypedTerminalOutcome(terminal.roleOutcome));
|
|
137
216
|
try {
|
|
138
217
|
const missingCredential = missingCredentialPreDispatchFailure(env.model, env.credentials);
|
|
139
218
|
if (missingCredential !== undefined) {
|
|
140
|
-
return
|
|
219
|
+
return {
|
|
220
|
+
...(await presentControlledFailure(admitted, missingCredential, adapters, env.principalAuthority, io, persistRunState)),
|
|
221
|
+
...deferredPersist,
|
|
222
|
+
};
|
|
141
223
|
}
|
|
142
224
|
// #617 DK-4: capture previous invocation host before markRunRunning overwrites it.
|
|
143
225
|
// Single authority projectHostTransitionPriorNative classifies the prior native volume.
|
|
@@ -161,28 +243,45 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
161
243
|
}
|
|
162
244
|
catch (error) {
|
|
163
245
|
// prior-native IO is on the public one-shot path — controlled failure, not bare throw.
|
|
164
|
-
return
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
246
|
+
return {
|
|
247
|
+
...(await presentControlledFailure(admitted, {
|
|
248
|
+
timedOut: false,
|
|
249
|
+
code: null,
|
|
250
|
+
stderr: "",
|
|
251
|
+
thrown: error,
|
|
252
|
+
}, adapters, env.principalAuthority, io, persistRunState)),
|
|
253
|
+
...deferredPersist,
|
|
254
|
+
};
|
|
170
255
|
}
|
|
171
|
-
await markRunRunning(admitted.runDirectory, env.model, effectiveEngine, env.host);
|
|
172
256
|
await clearTypedProviderHttpObservation(admitted.runDirectory);
|
|
173
|
-
//
|
|
174
|
-
//
|
|
257
|
+
// Per-attempt hygiene: stale Reviewer rejection pages must not ride into
|
|
258
|
+
// auto-resume. ENOENT-safe for every seat.
|
|
259
|
+
await clearReviewerDispatchRejection(admitted.runDirectory);
|
|
260
|
+
// The authoritative host write (markRunRunning) is delayed to just before
|
|
261
|
+
// executeTurn — not merely past beforeDispatch (#840 r9 判词 class 2). Any
|
|
262
|
+
// pre-turn retry (beforeDispatch, dossier projection, continuation
|
|
263
|
+
// assembly) must still see the prior invocation host on its next attempt;
|
|
264
|
+
// committing env.host earlier would make readInvocationHost read back the
|
|
265
|
+
// new host on the retry and silently drop hostTransition.
|
|
266
|
+
// Failures settle the run (presentControlledFailure); they must not leave it
|
|
267
|
+
// permanently running. Call-local auto-resume retries this hook until it
|
|
268
|
+
// succeeds; only an exhausted nested station child skips the parent loop
|
|
269
|
+
// (#840 父子不层叠).
|
|
175
270
|
if (adapters.beforeDispatch !== undefined) {
|
|
176
271
|
try {
|
|
177
272
|
await adapters.beforeDispatch(admitted);
|
|
178
273
|
}
|
|
179
274
|
catch (error) {
|
|
180
|
-
|
|
275
|
+
const settled = (await presentControlledFailure(admitted, {
|
|
181
276
|
timedOut: false,
|
|
182
277
|
code: null,
|
|
183
278
|
stderr: "",
|
|
184
279
|
thrown: error,
|
|
185
|
-
}, adapters, env.principalAuthority, io));
|
|
280
|
+
}, adapters, env.principalAuthority, io, persistRunState));
|
|
281
|
+
if (error instanceof StationChildExhaustedError) {
|
|
282
|
+
return { ...settled, skipAutoResume: true, ...deferredPersist };
|
|
283
|
+
}
|
|
284
|
+
return { ...settled, ...deferredPersist };
|
|
186
285
|
}
|
|
187
286
|
}
|
|
188
287
|
// Turn request is assembled after beforeDispatch so this turn sees whatever it
|
|
@@ -192,6 +291,9 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
192
291
|
// alike. System refs append their own neutral section; caller frozen
|
|
193
292
|
// attachments and the seat's own prompt bytes are never rewritten.
|
|
194
293
|
let turnRequest = env.signal === undefined ? request : { ...request, signal: env.signal };
|
|
294
|
+
if (env.stationChild !== undefined) {
|
|
295
|
+
turnRequest = { ...turnRequest, stationChild: env.stationChild };
|
|
296
|
+
}
|
|
195
297
|
if (hostTransition !== undefined) {
|
|
196
298
|
turnRequest = { ...turnRequest, hostTransition };
|
|
197
299
|
}
|
|
@@ -206,23 +308,47 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
206
308
|
continuation: appendContinuationSection(turnRequest.continuation, dossierSection),
|
|
207
309
|
};
|
|
208
310
|
}
|
|
311
|
+
// Authoritative host write happens here, at the real dispatch boundary —
|
|
312
|
+
// immediately before the turn actually starts, after every retryable
|
|
313
|
+
// pre-turn step above has succeeded on this attempt (#840 r9 判词 class 2).
|
|
314
|
+
await markRunRunning(admitted.runDirectory, env.model, effectiveEngine, env.host);
|
|
209
315
|
let result;
|
|
210
316
|
try {
|
|
211
317
|
result = await env.roleTurnHost.executeTurn(turnRequest);
|
|
212
318
|
}
|
|
213
319
|
catch (error) {
|
|
214
|
-
|
|
320
|
+
const settled = await settleAfterTurnStarted(admitted, {
|
|
215
321
|
timedOut: false,
|
|
216
322
|
code: null,
|
|
217
323
|
stderr: "",
|
|
218
324
|
thrown: error,
|
|
219
|
-
}, adapters, env.principalAuthority, io)
|
|
325
|
+
}, adapters, env.principalAuthority, io, persistRunState);
|
|
326
|
+
return { ...settled, turnDispatched: true, ...deferredPersist };
|
|
220
327
|
}
|
|
328
|
+
// Everything below runs after the host turn genuinely started (#840 r9
|
|
329
|
+
// 判词 class 1 boundary — 覆盖 executeTurn 已启动后至返回带 turnDispatched
|
|
330
|
+
// 结果前的全部异常). Each fallible step routes any failure through the
|
|
331
|
+
// single existing settlement authority (presentControlledFailure /
|
|
332
|
+
// settleFailureTerminalResult) rather than a second one (ADR 0080
|
|
333
|
+
// single-settlement-disposition) — never by fabricating a terminal here.
|
|
334
|
+
// A cleanup step that runs only after a real settlement (clearCurrentCourt)
|
|
335
|
+
// is protected by logging and keeping that already-obtained result, never
|
|
336
|
+
// by discarding it (#840 已交劳动只整理终局不重做). A failure inside the
|
|
337
|
+
// settlement authority itself (presentControlledFailure's own reads) goes
|
|
338
|
+
// through settleAfterTurnStarted: still no fabricated terminal, but the
|
|
339
|
+
// re-thrown TurnDispatchedFailure still tells runWithAutoResumeLoop the
|
|
340
|
+
// turn genuinely started, so the next retry sends a resume payload — the
|
|
341
|
+
// true cause settles through the loop's own existing dispatch-exception
|
|
342
|
+
// machinery once the retry budget is exhausted.
|
|
221
343
|
try {
|
|
222
344
|
await writeFile(join(admitted.runDirectory, "stderr.log"), result.stderr, "utf8");
|
|
223
345
|
}
|
|
224
|
-
catch {
|
|
225
|
-
//
|
|
346
|
+
catch (error) {
|
|
347
|
+
// Best-effort: the turn's own stderr capture is secondary to lawful /
|
|
348
|
+
// controlled-failure settlement below, but the failure itself must
|
|
349
|
+
// still leave a real trace (失败诚实宪法 真因必须落痕) — durably, since
|
|
350
|
+
// every auto-resume attempt's io is dummyIo (#840 r9 判词 class 1).
|
|
351
|
+
await recordBestEffortPostDispatchDiagnostic(admitted, env, `stderr.log write failed (best-effort continue): ${describeErrorIdentity(error)}`, io);
|
|
226
352
|
}
|
|
227
353
|
let settled;
|
|
228
354
|
// Same-ticket re-summons carry courtAttemptId — settle only that attempt so a
|
|
@@ -230,55 +356,111 @@ export async function dispatchPostAdmissionTurn(input) {
|
|
|
230
356
|
const courtScope = request.courtAttemptId === undefined || request.courtAttemptId.length === 0
|
|
231
357
|
? undefined
|
|
232
358
|
: { courtAttemptId: request.courtAttemptId };
|
|
359
|
+
// Single complete boundary (#840 r9 判词 class 1): trySettle, its
|
|
360
|
+
// shouldPresent gate, and the accepted-settlement cleanup all settle
|
|
361
|
+
// through this one catch. shouldPresent used to sit outside every
|
|
362
|
+
// TurnDispatchedFailure wrap, so a throw from it looked like an ordinary
|
|
363
|
+
// pre-turn throw to the caller (turnStartedBeforeThrow stayed false) and
|
|
364
|
+
// replayed the initial payload even though the host turn had already
|
|
365
|
+
// genuinely run.
|
|
366
|
+
let settledOutcome;
|
|
233
367
|
try {
|
|
234
368
|
settled = await adapters.trySettle(admitted, env.principalAuthority, courtScope);
|
|
369
|
+
if (settled !== undefined && shouldPresent(settled)) {
|
|
370
|
+
// This court sealed — drop open-court pointer (bare resume no longer continues it).
|
|
371
|
+
if (settled.roleOutcome.kind === "accepted" &&
|
|
372
|
+
request.courtAttemptId !== undefined &&
|
|
373
|
+
request.courtAttemptId.length > 0) {
|
|
374
|
+
try {
|
|
375
|
+
await clearCurrentCourt(admitted.runDirectory, request.courtAttemptId);
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
// Settlement already sealed accepted — a cleanup failure here must
|
|
379
|
+
// not erase that fact or make the caller replay this court's
|
|
380
|
+
// summons over already-delivered work (#840 已交劳动只整理终局不重做).
|
|
381
|
+
// A later bare resume self-heals: buildRequestAfterLease finds the
|
|
382
|
+
// open court already sealed and clears it then (documented
|
|
383
|
+
// continue-under-failure contract, not a swallow — 失败诚实宪法 真因
|
|
384
|
+
// 必须落痕) — durably, since every auto-resume attempt's io here is
|
|
385
|
+
// dummyIo (#840 r9 判词 class 1).
|
|
386
|
+
await recordBestEffortPostDispatchDiagnostic(admitted, env, `current-court cleanup failed after accepted settlement (best-effort continue, self-heals on next resume): ${describeErrorIdentity(error)}`, io);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
settledOutcome = {
|
|
390
|
+
exitCode: exitCodeForTerminalOutcome(settled.roleOutcome),
|
|
391
|
+
admitted,
|
|
392
|
+
terminal: settled,
|
|
393
|
+
turnDispatched: true,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
235
396
|
}
|
|
236
397
|
catch (error) {
|
|
237
|
-
// Settle throw is a real failure fact —
|
|
238
|
-
|
|
398
|
+
// Settle (or its shouldPresent gate) throw is a real failure fact —
|
|
399
|
+
// never swallow into undefined.
|
|
400
|
+
const settledFailure = await settleAfterTurnStarted(admitted, {
|
|
239
401
|
timedOut: false,
|
|
240
402
|
code: result.code,
|
|
241
403
|
stderr: result.stderr,
|
|
242
404
|
thrown: error,
|
|
243
|
-
}, adapters, env.principalAuthority, io)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
405
|
+
}, adapters, env.principalAuthority, io, persistRunState);
|
|
406
|
+
return { ...settledFailure, turnDispatched: true, ...deferredPersist };
|
|
407
|
+
}
|
|
408
|
+
if (settledOutcome !== undefined) {
|
|
409
|
+
// Lawful persist + present is the caller's stop seam (auto-resume loop /
|
|
410
|
+
// manual resume), not this retried host-turn function.
|
|
411
|
+
return { ...settledOutcome, ...deferredPersist };
|
|
412
|
+
}
|
|
413
|
+
// Any exception while resolving the failure facts below — including the
|
|
414
|
+
// session-file decode itself — still happened after the host turn
|
|
415
|
+
// genuinely started (#840 r9 判词 class 1). Fold it into the same single
|
|
416
|
+
// controlled-failure settlement instead of losing turnDispatched to an
|
|
417
|
+
// uncaught throw.
|
|
418
|
+
let failureInput;
|
|
419
|
+
try {
|
|
420
|
+
const sessionFile = admitted.principal !== undefined
|
|
421
|
+
? env.principalAuthority.decode(admitted.principal).sessionFile
|
|
422
|
+
: "";
|
|
423
|
+
const runnerKnownFailure = adapters.resolveRunnerKnownFailure !== undefined && sessionFile !== ""
|
|
424
|
+
? await adapters.resolveRunnerKnownFailure({ result, sessionFile })
|
|
425
|
+
: result.knownFailure;
|
|
426
|
+
const credentialFailure = postRunMissingCredentialFailure(result, env.model, env.credentials);
|
|
427
|
+
const resolution = await resolveAuditedRunnerFailureResolution({
|
|
428
|
+
runner: runnerKnownFailure,
|
|
429
|
+
sessionFile,
|
|
430
|
+
credential: credentialFailure,
|
|
431
|
+
runDirectory: admitted.runDirectory,
|
|
432
|
+
});
|
|
433
|
+
failureInput = {
|
|
434
|
+
timedOut: result.timedOut,
|
|
435
|
+
code: result.code,
|
|
436
|
+
stderr: result.stderr,
|
|
437
|
+
...controlledFailureInputFromResolution(resolution),
|
|
258
438
|
};
|
|
259
439
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
credential: credentialFailure,
|
|
271
|
-
runDirectory: admitted.runDirectory,
|
|
272
|
-
});
|
|
273
|
-
return (await presentControlledFailure(admitted, {
|
|
274
|
-
timedOut: result.timedOut,
|
|
275
|
-
code: result.code,
|
|
276
|
-
stderr: result.stderr,
|
|
277
|
-
...controlledFailureInputFromResolution(resolution),
|
|
278
|
-
}, adapters, env.principalAuthority, io));
|
|
440
|
+
catch (error) {
|
|
441
|
+
failureInput = {
|
|
442
|
+
timedOut: false,
|
|
443
|
+
code: result.code,
|
|
444
|
+
stderr: result.stderr,
|
|
445
|
+
thrown: error,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
const failed = await settleAfterTurnStarted(admitted, failureInput, adapters, env.principalAuthority, io, persistRunState);
|
|
449
|
+
return { ...failed, turnDispatched: true, ...deferredPersist };
|
|
279
450
|
}
|
|
280
451
|
finally {
|
|
281
|
-
|
|
452
|
+
try {
|
|
453
|
+
await lease.release();
|
|
454
|
+
}
|
|
455
|
+
catch (error) {
|
|
456
|
+
// lease.release() is documented best-effort and never rejects in the
|
|
457
|
+
// production acquireRunWriterLease implementation (createWriterLease
|
|
458
|
+
// reports cleanup failures via callback, never throws) — this guard is
|
|
459
|
+
// structural only: an uncaught throw from a finally block silently
|
|
460
|
+
// replaces whatever the try already returned, including a properly
|
|
461
|
+
// tagged turnDispatched:true result (#840 r9 判词 class 1 boundary).
|
|
462
|
+
io.stderr(formatCliDiagnostic(`writer lease release failed unexpectedly (best-effort continue): ${describeErrorIdentity(error)}`));
|
|
463
|
+
}
|
|
282
464
|
}
|
|
283
465
|
}
|
|
284
466
|
/**
|
|
@@ -351,8 +533,27 @@ export function resumeTurnRequestProjectionOptions(admitted, request, env, summo
|
|
|
351
533
|
prompt,
|
|
352
534
|
},
|
|
353
535
|
...(request.message === undefined ? {} : { courtAttemptId: randomUUID() }),
|
|
536
|
+
...(env.stationChild === undefined ? {} : { stationChild: env.stationChild }),
|
|
354
537
|
};
|
|
355
538
|
}
|
|
539
|
+
/**
|
|
540
|
+
* Hold the writer lease through after-lease build, then hand off to dispatch.
|
|
541
|
+
* Builder (or any throw before dispatch) must release here — dispatch's finally
|
|
542
|
+
* only runs after this handoff (manual resume and station-child auto-resume).
|
|
543
|
+
*/
|
|
544
|
+
async function dispatchAfterWriterLease(input) {
|
|
545
|
+
let handedOffToDispatch = false;
|
|
546
|
+
try {
|
|
547
|
+
const request = await input.build();
|
|
548
|
+
handedOffToDispatch = true;
|
|
549
|
+
return await input.dispatch(request);
|
|
550
|
+
}
|
|
551
|
+
finally {
|
|
552
|
+
if (!handedOffToDispatch) {
|
|
553
|
+
await input.lease.release();
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
356
557
|
function isAlreadyFrozenSummonsAttachment(runDirectory, attachmentPath) {
|
|
357
558
|
const absolute = isAbsolute(attachmentPath)
|
|
358
559
|
? attachmentPath
|
|
@@ -387,7 +588,8 @@ export async function prepareSummonsResumeMaterials(runDirectory, summons) {
|
|
|
387
588
|
* Shared manual-resume orchestration for seats whose continuation is the
|
|
388
589
|
* package resume envelope (#599 / #633): load once → structural rejection →
|
|
389
590
|
* optional seat afterAdmittedLoad (method material / controlled failure) →
|
|
390
|
-
* seat turn projection →
|
|
591
|
+
* seat turn projection → station-child auto-resume or public manual resume.
|
|
592
|
+
* Seat-owned loader
|
|
391
593
|
* validation, turn builder, and adapters stay on the seat.
|
|
392
594
|
*
|
|
393
595
|
* Court open/recovery transaction (#637): under the existing writer lease,
|
|
@@ -421,8 +623,146 @@ export async function runPostAdmissionSeatResume(input) {
|
|
|
421
623
|
}
|
|
422
624
|
adapters = prepared.adapters;
|
|
423
625
|
}
|
|
424
|
-
|
|
626
|
+
const buildRequestAfterLease = async () => {
|
|
627
|
+
let openCourtAttemptId;
|
|
628
|
+
// Build uses the admitted judged under this lease (rehydrated when open
|
|
629
|
+
// court materials ride). Settlement identity stays on the outer admitted.
|
|
630
|
+
let admittedForBuild = loaded.admitted;
|
|
631
|
+
// Bare resume: read + seal-judge + bound clear only under the held lease.
|
|
632
|
+
if (request.summons === undefined) {
|
|
633
|
+
const openCourt = await readCurrentCourt(admittedForBuild.runDirectory);
|
|
634
|
+
if (openCourt !== undefined) {
|
|
635
|
+
const sealedForOpen = await readSealedSubmission(admittedForBuild.projectRoot, admittedForBuild.runId, {
|
|
636
|
+
home: homeFromRunDirectory(admittedForBuild.runDirectory),
|
|
637
|
+
attemptId: openCourt.courtAttemptId,
|
|
638
|
+
});
|
|
639
|
+
if (sealedForOpen === undefined) {
|
|
640
|
+
// Continue open court: same summons materials + existing courtAttemptId.
|
|
641
|
+
// Caller message (if any) stays on the request — projection keeps it.
|
|
642
|
+
openCourtAttemptId = openCourt.courtAttemptId;
|
|
643
|
+
request = {
|
|
644
|
+
runId: request.runId,
|
|
645
|
+
...(request.message === undefined
|
|
646
|
+
? {}
|
|
647
|
+
: { message: request.message }),
|
|
648
|
+
...(openCourt.summons === undefined
|
|
649
|
+
? {}
|
|
650
|
+
: { summons: openCourt.summons }),
|
|
651
|
+
};
|
|
652
|
+
if (openCourt.summons !== undefined) {
|
|
653
|
+
const reloaded = await input.load(request);
|
|
654
|
+
admittedForBuild = reloaded.admitted;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
else {
|
|
658
|
+
// Open court already sealed — clear only the court id just judged.
|
|
659
|
+
await clearCurrentCourt(admittedForBuild.runDirectory, openCourt.courtAttemptId);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
// Freeze external paths once; rewrite summons to the frozen identity so
|
|
664
|
+
// currentCourt + later bare resume reuse the accepted snapshot.
|
|
665
|
+
if (request.summons !== undefined) {
|
|
666
|
+
const prepared = await prepareSummonsResumeMaterials(admittedForBuild.runDirectory, request.summons);
|
|
667
|
+
if (prepared !== undefined &&
|
|
668
|
+
(request.summons.attachmentPaths?.length ?? 0) > 0) {
|
|
669
|
+
request = {
|
|
670
|
+
...request,
|
|
671
|
+
summons: {
|
|
672
|
+
...request.summons,
|
|
673
|
+
attachmentPaths: prepared.attachments.map((attachment) => attachment.frozenPath),
|
|
674
|
+
},
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
let turnRequest = await input.buildTurnRequest(admittedForBuild, request);
|
|
679
|
+
// Open court continue, or new court for summons / message re-review
|
|
680
|
+
// (clause 0 新庭可再交卷; #833). Bare resume without open court omits id.
|
|
681
|
+
if (openCourtAttemptId !== undefined ||
|
|
682
|
+
request.summons !== undefined ||
|
|
683
|
+
request.message !== undefined) {
|
|
684
|
+
const courtAttemptId = openCourtAttemptId ??
|
|
685
|
+
(turnRequest.courtAttemptId !== undefined &&
|
|
686
|
+
turnRequest.courtAttemptId.length > 0
|
|
687
|
+
? turnRequest.courtAttemptId
|
|
688
|
+
: randomUUID());
|
|
689
|
+
turnRequest = { ...turnRequest, courtAttemptId };
|
|
690
|
+
if (openCourtAttemptId === undefined) {
|
|
691
|
+
const court = {
|
|
692
|
+
courtAttemptId,
|
|
693
|
+
...(request.summons === undefined
|
|
694
|
+
? {}
|
|
695
|
+
: { summons: request.summons }),
|
|
696
|
+
};
|
|
697
|
+
await recordCurrentCourt(admittedForBuild.runDirectory, court);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
return turnRequest;
|
|
701
|
+
};
|
|
702
|
+
// Court recovery / open under lease, then dispatch.
|
|
703
|
+
// Station-child same-ticket/same-parent resume is call-local auto-resume
|
|
704
|
+
// (#840 / #416). Public `ak-role resume` stays one-shot (ADR 0080).
|
|
425
705
|
try {
|
|
706
|
+
if (input.env.stationChild === true) {
|
|
707
|
+
let firstTurn;
|
|
708
|
+
const stationAdapters = withOnceSuccessfulBeforeDispatch(adapters);
|
|
709
|
+
return await runWithAutoResumeLoop({
|
|
710
|
+
admitted: loaded.admitted,
|
|
711
|
+
principalAuthority: input.env.principalAuthority,
|
|
712
|
+
isPrincipalAvailable: resolveHostAwareSessionAvailability(input.env.host, input.env.principalAuthority),
|
|
713
|
+
io: input.io,
|
|
714
|
+
sessionAppender: input.env.sessionAppender,
|
|
715
|
+
autoResumeLimit: input.env.autoResumeLimit,
|
|
716
|
+
buildInitialPayload: () => ({ resumeTurn: false }),
|
|
717
|
+
buildResumePayload: () => ({ resumeTurn: true }),
|
|
718
|
+
// Same as public manual resume: prior-court sealed acceptance is not a
|
|
719
|
+
// redispatch brake (#833). New-court station-child turns still auto-resume.
|
|
720
|
+
dispatch: async (payload, lease, _isFirst, attemptIo) => dispatchAfterWriterLease({
|
|
721
|
+
lease,
|
|
722
|
+
build: async () => {
|
|
723
|
+
// #840 r8 判词 class 2: this call-local retry must keep this
|
|
724
|
+
// court's frozen summons / 交卷 body / attachments verbatim
|
|
725
|
+
// (same object as firstTurn) and project only the minimal
|
|
726
|
+
// host-needed resume trigger — never the manual-resume engine
|
|
727
|
+
// handbook (buildResumeContinuationPrompt), which would replace
|
|
728
|
+
// a 审核循环 same-ticket continuation with a bare outsourcing
|
|
729
|
+
// 「重新读」 envelope (#755 contract, resumeTurnRequestProjectionOptions
|
|
730
|
+
// above). RESUME_TRANSPORT_ENVELOPE is the same package-owned,
|
|
731
|
+
// non-semantic trigger that projection already uses for a
|
|
732
|
+
// same-ticket summons carrying no instruction/attachments.
|
|
733
|
+
if (payload.resumeTurn && firstTurn !== undefined) {
|
|
734
|
+
return {
|
|
735
|
+
...firstTurn,
|
|
736
|
+
continuation: {
|
|
737
|
+
kind: "resume",
|
|
738
|
+
prompt: RESUME_TRANSPORT_ENVELOPE,
|
|
739
|
+
},
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
const turnRequest = await buildRequestAfterLease();
|
|
743
|
+
firstTurn = turnRequest;
|
|
744
|
+
return turnRequest;
|
|
745
|
+
},
|
|
746
|
+
dispatch: (turnRequest) => dispatchPostAdmissionTurn({
|
|
747
|
+
admitted: loaded.admitted,
|
|
748
|
+
env: {
|
|
749
|
+
...input.env,
|
|
750
|
+
...(loaded.admitted.correlationId === undefined
|
|
751
|
+
? {}
|
|
752
|
+
: { correlationId: loaded.admitted.correlationId }),
|
|
753
|
+
},
|
|
754
|
+
io: attemptIo,
|
|
755
|
+
request: turnRequest,
|
|
756
|
+
lease,
|
|
757
|
+
adapters: stationAdapters,
|
|
758
|
+
persistRunState: false,
|
|
759
|
+
...(input.effectiveEngine === undefined
|
|
760
|
+
? {}
|
|
761
|
+
: { effectiveEngine: input.effectiveEngine }),
|
|
762
|
+
}),
|
|
763
|
+
}),
|
|
764
|
+
});
|
|
765
|
+
}
|
|
426
766
|
return await runPostAdmissionManualResume({
|
|
427
767
|
admitted: loaded.admitted,
|
|
428
768
|
env: input.env,
|
|
@@ -431,82 +771,7 @@ export async function runPostAdmissionSeatResume(input) {
|
|
|
431
771
|
...(input.effectiveEngine === undefined
|
|
432
772
|
? {}
|
|
433
773
|
: { effectiveEngine: input.effectiveEngine }),
|
|
434
|
-
buildRequestAfterLease
|
|
435
|
-
let openCourtAttemptId;
|
|
436
|
-
// Build uses the admitted judged under this lease (rehydrated when open
|
|
437
|
-
// court materials ride). Settlement identity stays on the outer admitted.
|
|
438
|
-
let admittedForBuild = loaded.admitted;
|
|
439
|
-
// Bare resume: read + seal-judge + bound clear only under the held lease.
|
|
440
|
-
if (request.summons === undefined) {
|
|
441
|
-
const openCourt = await readCurrentCourt(admittedForBuild.runDirectory);
|
|
442
|
-
if (openCourt !== undefined) {
|
|
443
|
-
const sealedForOpen = await readSealedSubmission(admittedForBuild.projectRoot, admittedForBuild.runId, {
|
|
444
|
-
home: homeFromRunDirectory(admittedForBuild.runDirectory),
|
|
445
|
-
attemptId: openCourt.courtAttemptId,
|
|
446
|
-
});
|
|
447
|
-
if (sealedForOpen === undefined) {
|
|
448
|
-
// Continue open court: same summons materials + existing courtAttemptId.
|
|
449
|
-
// Caller message (if any) stays on the request — projection keeps it.
|
|
450
|
-
openCourtAttemptId = openCourt.courtAttemptId;
|
|
451
|
-
request = {
|
|
452
|
-
runId: request.runId,
|
|
453
|
-
...(request.message === undefined
|
|
454
|
-
? {}
|
|
455
|
-
: { message: request.message }),
|
|
456
|
-
...(openCourt.summons === undefined
|
|
457
|
-
? {}
|
|
458
|
-
: { summons: openCourt.summons }),
|
|
459
|
-
};
|
|
460
|
-
if (openCourt.summons !== undefined) {
|
|
461
|
-
const reloaded = await input.load(request);
|
|
462
|
-
admittedForBuild = reloaded.admitted;
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
else {
|
|
466
|
-
// Open court already sealed — clear only the court id just judged.
|
|
467
|
-
await clearCurrentCourt(admittedForBuild.runDirectory, openCourt.courtAttemptId);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
// Freeze external paths once; rewrite summons to the frozen identity so
|
|
472
|
-
// currentCourt + later bare resume reuse the accepted snapshot.
|
|
473
|
-
if (request.summons !== undefined) {
|
|
474
|
-
const prepared = await prepareSummonsResumeMaterials(admittedForBuild.runDirectory, request.summons);
|
|
475
|
-
if (prepared !== undefined &&
|
|
476
|
-
(request.summons.attachmentPaths?.length ?? 0) > 0) {
|
|
477
|
-
request = {
|
|
478
|
-
...request,
|
|
479
|
-
summons: {
|
|
480
|
-
...request.summons,
|
|
481
|
-
attachmentPaths: prepared.attachments.map((attachment) => attachment.frozenPath),
|
|
482
|
-
},
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
let turnRequest = await input.buildTurnRequest(admittedForBuild, request);
|
|
487
|
-
// Open court continue, or new court for summons / message re-review
|
|
488
|
-
// (clause 0 新庭可再交卷; #833). Bare resume without open court omits id.
|
|
489
|
-
if (openCourtAttemptId !== undefined ||
|
|
490
|
-
request.summons !== undefined ||
|
|
491
|
-
request.message !== undefined) {
|
|
492
|
-
const courtAttemptId = openCourtAttemptId ??
|
|
493
|
-
(turnRequest.courtAttemptId !== undefined &&
|
|
494
|
-
turnRequest.courtAttemptId.length > 0
|
|
495
|
-
? turnRequest.courtAttemptId
|
|
496
|
-
: randomUUID());
|
|
497
|
-
turnRequest = { ...turnRequest, courtAttemptId };
|
|
498
|
-
if (openCourtAttemptId === undefined) {
|
|
499
|
-
const court = {
|
|
500
|
-
courtAttemptId,
|
|
501
|
-
...(request.summons === undefined
|
|
502
|
-
? {}
|
|
503
|
-
: { summons: request.summons }),
|
|
504
|
-
};
|
|
505
|
-
await recordCurrentCourt(admittedForBuild.runDirectory, court);
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
return turnRequest;
|
|
509
|
-
},
|
|
774
|
+
buildRequestAfterLease,
|
|
510
775
|
});
|
|
511
776
|
}
|
|
512
777
|
catch (error) {
|
|
@@ -520,31 +785,30 @@ export async function runPostAdmissionSeatResume(input) {
|
|
|
520
785
|
}
|
|
521
786
|
}
|
|
522
787
|
/**
|
|
523
|
-
* Shared post-admission one-shot path:
|
|
788
|
+
* Shared post-admission one-shot path: folds into runPostAdmissionResumable (#840 / #416).
|
|
789
|
+
* All callable roles share the single auto-resume loop.
|
|
524
790
|
* Initial facades own the durable admitted mark (markRunAdmitted) before
|
|
525
791
|
* entering; manual resume never re-admits.
|
|
526
792
|
*/
|
|
527
793
|
export async function runPostAdmissionOneShot(input) {
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
adapters,
|
|
547
|
-
...(effectiveEngine === undefined ? {} : { effectiveEngine }),
|
|
794
|
+
const engine = input.effectiveEngine ?? input.env.engine;
|
|
795
|
+
return await runPostAdmissionResumable({
|
|
796
|
+
admitted: input.admitted,
|
|
797
|
+
env: input.env,
|
|
798
|
+
io: input.io,
|
|
799
|
+
buildInitialRequest: () => input.request,
|
|
800
|
+
buildResumeRequest: () => ({
|
|
801
|
+
...input.request,
|
|
802
|
+
continuation: {
|
|
803
|
+
kind: "resume",
|
|
804
|
+
prompt: buildResumeContinuationPrompt({
|
|
805
|
+
packageRoot: input.env.packageRoot,
|
|
806
|
+
...(engine === undefined ? {} : { engine }),
|
|
807
|
+
}),
|
|
808
|
+
},
|
|
809
|
+
}),
|
|
810
|
+
adapters: input.adapters,
|
|
811
|
+
...(input.effectiveEngine === undefined ? {} : { effectiveEngine: input.effectiveEngine }),
|
|
548
812
|
});
|
|
549
813
|
}
|
|
550
814
|
/**
|
|
@@ -552,10 +816,12 @@ export async function runPostAdmissionOneShot(input) {
|
|
|
552
816
|
* Initial facades own the durable admitted mark before entering.
|
|
553
817
|
*/
|
|
554
818
|
export async function runPostAdmissionResumable(input) {
|
|
555
|
-
const { admitted, env, io, buildInitialRequest, buildResumeRequest,
|
|
819
|
+
const { admitted, env, io, buildInitialRequest, buildResumeRequest, effectiveEngine } = input;
|
|
820
|
+
const adapters = withOnceSuccessfulBeforeDispatch(input.adapters);
|
|
556
821
|
return runWithAutoResumeLoop({
|
|
557
822
|
admitted,
|
|
558
823
|
principalAuthority: env.principalAuthority,
|
|
824
|
+
isPrincipalAvailable: resolveHostAwareSessionAvailability(env.host, env.principalAuthority),
|
|
559
825
|
io,
|
|
560
826
|
sessionAppender: env.sessionAppender,
|
|
561
827
|
autoResumeLimit: env.autoResumeLimit,
|
|
@@ -572,6 +838,7 @@ export async function runPostAdmissionResumable(input) {
|
|
|
572
838
|
request,
|
|
573
839
|
lease,
|
|
574
840
|
adapters,
|
|
841
|
+
persistRunState: false,
|
|
575
842
|
// #600: every attempt (initial + auto-resume) writes seat engine when present.
|
|
576
843
|
...(effectiveEngine === undefined ? {} : { effectiveEngine }),
|
|
577
844
|
}),
|
|
@@ -581,6 +848,7 @@ export async function runPostAdmissionResumable(input) {
|
|
|
581
848
|
* Manual resume: lease + dispatch. Pass-through to the host — no sealed-accepted
|
|
582
849
|
* short-circuit (#833 / #416). Court open (summons / message / open court) is
|
|
583
850
|
* built under lease when using buildRequestAfterLease; sole-final stays per-attempt.
|
|
851
|
+
* After-lease build shares dispatchAfterWriterLease with station-child auto-resume.
|
|
584
852
|
*/
|
|
585
853
|
export async function runPostAdmissionManualResume(input) {
|
|
586
854
|
const { admitted, env, io, adapters, effectiveEngine, buildRequestAfterLease, } = input;
|
|
@@ -614,19 +882,18 @@ export async function runPostAdmissionManualResume(input) {
|
|
|
614
882
|
}
|
|
615
883
|
throw error;
|
|
616
884
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
885
|
+
const result = await dispatchAfterWriterLease({
|
|
886
|
+
lease,
|
|
887
|
+
build: async () => {
|
|
888
|
+
if (request === undefined) {
|
|
889
|
+
if (buildRequestAfterLease === undefined) {
|
|
890
|
+
throw new Error("runPostAdmissionManualResume requires request or buildRequestAfterLease");
|
|
891
|
+
}
|
|
892
|
+
request = await buildRequestAfterLease();
|
|
625
893
|
}
|
|
626
|
-
request
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
const result = await dispatchPostAdmissionTurn({
|
|
894
|
+
return request;
|
|
895
|
+
},
|
|
896
|
+
dispatch: (turnRequest) => dispatchPostAdmissionTurn({
|
|
630
897
|
admitted,
|
|
631
898
|
env: {
|
|
632
899
|
...env,
|
|
@@ -636,24 +903,24 @@ export async function runPostAdmissionManualResume(input) {
|
|
|
636
903
|
: { correlationId: admitted.correlationId }),
|
|
637
904
|
},
|
|
638
905
|
io,
|
|
639
|
-
request,
|
|
906
|
+
request: turnRequest,
|
|
640
907
|
lease,
|
|
641
908
|
adapters,
|
|
642
909
|
...(effectiveEngine === undefined ? {} : { effectiveEngine }),
|
|
643
|
-
})
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
...(staleWriterLeaseReclaimed === true
|
|
650
|
-
? { staleWriterLeaseReclaimed: true }
|
|
651
|
-
: {}),
|
|
652
|
-
};
|
|
910
|
+
}),
|
|
911
|
+
});
|
|
912
|
+
if (result.terminal !== undefined &&
|
|
913
|
+
isLawfulTypedTerminalOutcome(result.terminal.roleOutcome)) {
|
|
914
|
+
await persistReturnedRunState(admitted, env.principalAuthority, { lawful: true });
|
|
915
|
+
io.stdout(formatTerminalResult(result.terminal));
|
|
653
916
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
await lease.release();
|
|
657
|
-
}
|
|
917
|
+
if (result.terminal !== undefined) {
|
|
918
|
+
result.terminal.autoResumeCount = 0;
|
|
658
919
|
}
|
|
920
|
+
return {
|
|
921
|
+
...result,
|
|
922
|
+
...(staleWriterLeaseReclaimed === true
|
|
923
|
+
? { staleWriterLeaseReclaimed: true }
|
|
924
|
+
: {}),
|
|
925
|
+
};
|
|
659
926
|
}
|