@akagilnc/pi-workflow-roles 0.1.3572 → 0.1.3621
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acp-host/production-host.js +20239 -10556
- package/dist/analyst-gate-cycles-read.js +387 -0
- package/dist/archivist-record-entry.js +22 -1
- package/dist/audit-escalation.js +6 -0
- package/dist/auditor-soul.js +74 -0
- package/dist/collector-config.js +86 -0
- package/dist/collector-evidence.js +316 -0
- package/dist/collector-github.js +527 -0
- package/dist/collector-ledger.js +853 -0
- package/dist/collector-tool-schemas.js +33 -0
- package/dist/compliance-transport.js +104 -52
- package/dist/diarist-mechanical.js +411 -0
- package/dist/diarist-ticket-resolution.js +177 -0
- package/dist/diarist.js +311 -0
- package/dist/doctor-auditor.js +25 -0
- package/dist/doctor-contracts.js +2 -0
- package/dist/doctor-evidence.js +142 -0
- package/dist/gatekeeper-role.js +104 -81
- package/dist/host-transition-prior-native.js +78 -0
- package/dist/institutional-resolution.js +1 -95
- package/dist/judge-auditor.js +26 -0
- package/dist/ledger-session-read.js +227 -0
- package/dist/merger-git-state.js +78 -0
- package/dist/navigator-attendance.js +15 -7
- package/dist/navigator-public-session.js +170 -0
- package/dist/navigator-session-contracts.js +36 -40
- package/dist/notary-source-run.js +122 -0
- package/dist/package-contracts/auditor-output.js +66 -0
- package/dist/package-contracts/evidence-child-output.js +34 -0
- package/dist/package-contracts/judge-output.js +2 -0
- package/dist/package-contracts/terminating-tools.js +25 -2
- package/dist/package-resources/method-skill.js +254 -0
- package/dist/packaged-role-registry.js +36 -0
- package/dist/pi/durable-principal.js +61 -0
- package/dist/pi/in-process-session.js +25 -6
- package/dist/pi/known-failure.js +52 -0
- package/dist/pi/role-turn-host.js +430 -0
- package/dist/public-cli/auto-resume.js +414 -0
- package/dist/public-cli/cli-errors.js +8 -0
- package/dist/public-cli/cli-io.js +1 -0
- package/dist/public-cli/command-renderer.js +5 -0
- package/dist/public-cli/doctor-run.js +84 -0
- package/dist/public-cli/inspector-run.js +136 -0
- package/dist/public-cli/instruction-seat-run.js +256 -0
- package/dist/public-cli/invocation.js +2225 -0
- package/dist/public-cli/judge-run.js +118 -0
- package/dist/public-cli/load-production-acp-host.js +40 -0
- package/dist/public-cli/main.js +1140 -652
- package/dist/public-cli/notary-run.js +161 -0
- package/dist/public-cli/option-definitions.js +1192 -0
- package/dist/public-cli/post-admission.js +667 -0
- package/dist/public-cli/public-run-credentials.js +49 -0
- package/dist/public-cli/registry.js +4 -1
- package/dist/public-cli/reviewer-dispatch-rejection.js +77 -0
- package/dist/public-cli/run-lifecycle.js +1344 -0
- package/dist/public-cli/seat-ticket-binding.js +113 -0
- package/dist/public-cli/settlement.js +3441 -0
- package/dist/public-cli/terminal.js +135 -0
- package/dist/public-cli/turn-request.js +25 -0
- package/dist/public-role-summons.js +301 -0
- package/dist/receipt-delivery-policy.js +10 -0
- package/dist/reviewer-child-executor.js +80 -8
- package/dist/reviewer-execution-ledger.js +2 -1
- package/dist/run-terminal-artifacts.js +195 -0
- package/dist/run-ticket-number.js +40 -0
- package/dist/session-assistant-usage.js +107 -0
- package/dist/shape-unreadable-failure.js +34 -0
- package/dist/submission-errors.js +3 -0
- package/dist/submission-ledger.js +388 -0
- package/dist/ticket-provenance-contracts.js +115 -0
- package/dist/ticket-provenance.js +340 -0
- package/extensions/role-runtime.ts +7 -0
- package/package.json +1 -1
- package/scripts/build-package.mjs +2 -1
- package/souls/doctor-auditor.md +1 -0
- package/souls/evidence-child.md +1 -0
- package/src/acp-host/production-host.ts +3 -0
- package/src/acp-host/role-envelope.ts +2 -2
- package/src/acp-host/role-turn-host.ts +9 -1
- package/src/analyst-gate-cycles-read.ts +37 -2
- package/src/archivist-record-entry.ts +45 -1
- package/src/audit-escalation.ts +17 -0
- package/src/auditor-role.ts +22 -0
- package/src/auditor-soul.ts +42 -0
- package/src/compliance-transport.ts +177 -66
- package/src/doctor-auditor.ts +10 -14
- package/src/doctor-contracts.ts +1 -0
- package/src/doctor-role.ts +2 -2
- package/src/evidence-child-role.ts +22 -0
- package/src/gatekeeper-pass-envelope.ts +109 -0
- package/src/gatekeeper-role.ts +145 -104
- package/src/host-contracts.ts +9 -1
- package/src/institutional-resolution.ts +8 -163
- package/src/judge-auditor.ts +10 -15
- package/src/judge-role.ts +8 -0
- package/src/navigator-attendance.ts +25 -9
- package/src/navigator-public-session.ts +217 -0
- package/src/navigator-session-contracts.ts +61 -54
- package/src/notary-source-run.ts +3 -1
- package/src/package-contracts/auditor-output.ts +82 -0
- package/src/package-contracts/evidence-child-output.ts +51 -0
- package/src/package-contracts/judge-output.ts +1 -0
- package/src/package-contracts/reviewer-output.ts +2 -2
- package/src/package-contracts/terminating-tools.ts +31 -1
- package/src/packaged-role-registry.ts +43 -0
- package/src/pi/adapter.ts +2 -1
- package/src/pi/in-process-session.ts +38 -12
- package/src/pi/role-turn-host.ts +34 -0
- package/src/public-cli/cli.ts +14 -6
- package/src/public-cli/instruction-seat-run.ts +289 -50
- package/src/public-cli/invocation.ts +66 -25
- package/src/public-cli/option-definitions.ts +61 -0
- package/src/public-cli/post-admission.ts +7 -1
- package/src/public-cli/registry.ts +4 -1
- package/src/public-cli/run-lifecycle.ts +22 -3
- package/src/public-cli/settlement.ts +120 -5
- package/src/public-cli/terminal.ts +2 -0
- package/src/public-role-summons.ts +431 -0
- package/src/receipt-delivery-policy.ts +10 -0
- package/src/reviewer-agent.ts +1 -1
- package/src/reviewer-child-executor.ts +114 -12
- package/src/reviewer-execution-ledger.ts +3 -2
- package/src/role-runtime.ts +158 -17
- package/src/session-assistant-usage.ts +119 -0
- package/src/session-opening-materials.ts +1 -1
- package/src/shape-unreadable-failure.ts +49 -0
- package/src/submission-errors.ts +3 -0
- package/dist/evidence-child-executor.js +0 -829
- package/src/evidence-child-executor.ts +0 -1140
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified post-admission Role lifecycle coordinator (stages ③–⑤; ADR 0018 / #505 / #517 / #526).
|
|
3
|
+
* Owns writer lease → running → ③ dispatch → ④ tool loop / gates → ⑤ settle / fail →
|
|
4
|
+
* terminal → release. The durable admitted mark (markRunAdmitted) is owned by the
|
|
5
|
+
* initial role facades before entering; manual resume never re-admits.
|
|
6
|
+
* Role runners supply only turn request projection and narrow settlement adapters.
|
|
7
|
+
*/
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
9
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
10
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
11
|
+
import { buildResumeContinuationPrompt, } from "./run-lifecycle.js";
|
|
12
|
+
import { CliUsageError } from "./cli-errors.js";
|
|
13
|
+
import { buildInstructionTransportPrompt, freezeAttachmentsIntoRun, } from "./invocation.js";
|
|
14
|
+
import { pathContainedIn } from "../activation-ledger-topology.js";
|
|
15
|
+
import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.js";
|
|
16
|
+
import { projectHostTransitionPriorNative } from "../host-transition-prior-native.js";
|
|
17
|
+
import { missingCredentialPreDispatchFailure, postRunMissingCredentialFailure, } from "./public-run-credentials.js";
|
|
18
|
+
import { acquireRunWriterLease, clearCurrentCourt, clearTypedProviderHttpObservation, markRunResumable, markRunRunning, markRunTerminal, readCurrentCourt, recordCurrentCourt, renderResumeCommand, RunWriterLeaseHeldError, } from "./run-lifecycle.js";
|
|
19
|
+
import { homeFromRunDirectory } from "../activation-ledger-topology.js";
|
|
20
|
+
import { readSealedSubmission } from "../submission-ledger.js";
|
|
21
|
+
import { classifyPostAdmissionFailure, controlledFailureInputFromResolution, exitCodeForTerminalOutcome, explicitInternalKnownFailureClassificationInput, formatCliDiagnostic, formatTerminalResult, inspectJudgeSession, isLawfulTypedTerminalOutcome, presentFailureTerminal, presentStructuralRejection, resolveAuditedRunnerFailureResolution, resolveControlledFailureResumeObservation, settleFailureTerminalResult, sealedAcceptanceRedispatchDisposition, } from "./settlement.js";
|
|
22
|
+
import {} from "./terminal.js";
|
|
23
|
+
import { runWithAutoResumeLoop } from "./auto-resume.js";
|
|
24
|
+
/** Previous main-session host recorded on invocation.json, if any. */
|
|
25
|
+
async function readInvocationHost(runDirectory) {
|
|
26
|
+
try {
|
|
27
|
+
const raw = JSON.parse(await readFile(join(runDirectory, "invocation.json"), "utf8"));
|
|
28
|
+
return typeof raw.host === "string" && raw.host.trim() !== "" ? raw.host : undefined;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (error.code === "ENOENT")
|
|
32
|
+
return undefined;
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export async function presentControlledFailure(admitted, failureInput, adapters, authority, io) {
|
|
37
|
+
const hasThrown = Object.hasOwn(failureInput, "thrown");
|
|
38
|
+
const resumeObservation = await resolveControlledFailureResumeObservation({
|
|
39
|
+
runDirectory: admitted.runDirectory,
|
|
40
|
+
...(failureInput.typedHttpObservationSettled === true
|
|
41
|
+
? {
|
|
42
|
+
typedHttpObservationSettled: true,
|
|
43
|
+
...(failureInput.typedHttpObservation === undefined
|
|
44
|
+
? {}
|
|
45
|
+
: { typedHttpObservation: failureInput.typedHttpObservation }),
|
|
46
|
+
}
|
|
47
|
+
: {}),
|
|
48
|
+
});
|
|
49
|
+
const knownFailure = failureInput.knownFailure ?? resumeObservation.observationReadFailure;
|
|
50
|
+
const session = !hasThrown &&
|
|
51
|
+
!failureInput.timedOut &&
|
|
52
|
+
knownFailure === undefined &&
|
|
53
|
+
failureInput.knownCause === undefined &&
|
|
54
|
+
admitted.principal !== undefined
|
|
55
|
+
? await inspectJudgeSession(authority.decode(admitted.principal).sessionFile)
|
|
56
|
+
: undefined;
|
|
57
|
+
// knownFailure channel owns details when present; otherwise caller knownDetails.
|
|
58
|
+
const fromKnownFailure = explicitInternalKnownFailureClassificationInput(knownFailure);
|
|
59
|
+
const failure = classifyPostAdmissionFailure({
|
|
60
|
+
timedOut: failureInput.timedOut,
|
|
61
|
+
code: failureInput.code,
|
|
62
|
+
stderr: failureInput.stderr,
|
|
63
|
+
...(hasThrown ? { thrown: failureInput.thrown } : {}),
|
|
64
|
+
...(failureInput.knownDetails === undefined
|
|
65
|
+
? {}
|
|
66
|
+
: { knownDetails: failureInput.knownDetails }),
|
|
67
|
+
...fromKnownFailure,
|
|
68
|
+
...(failureInput.knownCause === undefined
|
|
69
|
+
? {}
|
|
70
|
+
: { knownCause: failureInput.knownCause }),
|
|
71
|
+
...(failureInput.knownIdentity === undefined
|
|
72
|
+
? {}
|
|
73
|
+
: { knownIdentity: failureInput.knownIdentity }),
|
|
74
|
+
...(failureInput.knownDiagnostic === undefined
|
|
75
|
+
? {}
|
|
76
|
+
: { knownDiagnostic: failureInput.knownDiagnostic }),
|
|
77
|
+
...(session === undefined ? {} : { session }),
|
|
78
|
+
});
|
|
79
|
+
// #665 / #416: resume hint is seat-uniform — principal available && typed 429 → show.
|
|
80
|
+
// Do not fork the public terminal face on per-seat hasLawful / isResumableRole (ADR 0040).
|
|
81
|
+
let resumable = false;
|
|
82
|
+
const typedHttp429 = resumeObservation.typedHttp429;
|
|
83
|
+
if (admitted.principal !== undefined) {
|
|
84
|
+
const sessionPrincipalAvailable = await authority.isAvailable(admitted.principal);
|
|
85
|
+
resumable = sessionPrincipalAvailable && typedHttp429 !== undefined;
|
|
86
|
+
}
|
|
87
|
+
if (resumable && typedHttp429 !== undefined) {
|
|
88
|
+
await markRunResumable(admitted.runDirectory, typedHttp429);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
await markRunTerminal(admitted.runDirectory);
|
|
92
|
+
}
|
|
93
|
+
const terminal = await settleFailureTerminalResult(admitted, failure, authority, resumable
|
|
94
|
+
? { resume: { command: renderResumeCommand(admitted.runId) } }
|
|
95
|
+
: {});
|
|
96
|
+
presentFailureTerminal(terminal, io);
|
|
97
|
+
return {
|
|
98
|
+
exitCode: exitCodeForTerminalOutcome(terminal.roleOutcome),
|
|
99
|
+
admitted,
|
|
100
|
+
terminal,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
export async function dispatchPostAdmissionTurn(input) {
|
|
104
|
+
const { admitted, env, io, request, lease, adapters, effectiveEngine } = input;
|
|
105
|
+
const shouldPresent = adapters.shouldPresentSettled ?? ((terminal) => isLawfulTypedTerminalOutcome(terminal.roleOutcome));
|
|
106
|
+
try {
|
|
107
|
+
const missingCredential = missingCredentialPreDispatchFailure(env.model, env.credentials);
|
|
108
|
+
if (missingCredential !== undefined) {
|
|
109
|
+
return (await presentControlledFailure(admitted, missingCredential, adapters, env.principalAuthority, io));
|
|
110
|
+
}
|
|
111
|
+
// #617 DK-4: capture previous invocation host before markRunRunning overwrites it.
|
|
112
|
+
// Single authority projectHostTransitionPriorNative classifies the prior native volume.
|
|
113
|
+
// Same-run resume (#637) keeps host identity on the run's invocation page.
|
|
114
|
+
let previousHost;
|
|
115
|
+
const liveHost = env.host;
|
|
116
|
+
const principalCoordinates = admitted.principal === undefined
|
|
117
|
+
? undefined
|
|
118
|
+
: env.principalAuthority.decode(admitted.principal);
|
|
119
|
+
let turnRequest;
|
|
120
|
+
try {
|
|
121
|
+
previousHost = await readInvocationHost(admitted.runDirectory);
|
|
122
|
+
const hostTransition = previousHost !== undefined && liveHost !== undefined && principalCoordinates !== undefined
|
|
123
|
+
? await projectHostTransitionPriorNative({
|
|
124
|
+
previousHost,
|
|
125
|
+
liveHost,
|
|
126
|
+
piSessionFile: principalCoordinates.sessionFile,
|
|
127
|
+
})
|
|
128
|
+
: undefined;
|
|
129
|
+
turnRequest = env.signal === undefined ? request : { ...request, signal: env.signal };
|
|
130
|
+
if (hostTransition !== undefined) {
|
|
131
|
+
turnRequest = { ...turnRequest, hostTransition };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
// prior-native IO is on the public one-shot path — controlled failure, not bare throw.
|
|
136
|
+
return (await presentControlledFailure(admitted, {
|
|
137
|
+
timedOut: false,
|
|
138
|
+
code: null,
|
|
139
|
+
stderr: "",
|
|
140
|
+
thrown: error,
|
|
141
|
+
}, adapters, env.principalAuthority, io));
|
|
142
|
+
}
|
|
143
|
+
await markRunRunning(admitted.runDirectory, env.model, effectiveEngine, env.host);
|
|
144
|
+
await clearTypedProviderHttpObservation(admitted.runDirectory);
|
|
145
|
+
// beforeDispatch (seat-owned pre-turn work) runs after running is marked —
|
|
146
|
+
// its failures must settle the run, not leave it permanently running.
|
|
147
|
+
if (adapters.beforeDispatch !== undefined) {
|
|
148
|
+
try {
|
|
149
|
+
await adapters.beforeDispatch(admitted);
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
return (await presentControlledFailure(admitted, {
|
|
153
|
+
timedOut: false,
|
|
154
|
+
code: null,
|
|
155
|
+
stderr: "",
|
|
156
|
+
thrown: error,
|
|
157
|
+
}, adapters, env.principalAuthority, io));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
let result;
|
|
161
|
+
try {
|
|
162
|
+
result = await env.roleTurnHost.executeTurn(turnRequest);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
return (await presentControlledFailure(admitted, {
|
|
166
|
+
timedOut: false,
|
|
167
|
+
code: null,
|
|
168
|
+
stderr: "",
|
|
169
|
+
thrown: error,
|
|
170
|
+
}, adapters, env.principalAuthority, io));
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
await writeFile(join(admitted.runDirectory, "stderr.log"), result.stderr, "utf8");
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
// continue to lawful / controlled-failure settlement
|
|
177
|
+
}
|
|
178
|
+
let settled;
|
|
179
|
+
// Same-ticket re-summons carry courtAttemptId — settle only that attempt so a
|
|
180
|
+
// prior sealed pass cannot wash this turn's missing/escalated/failed result.
|
|
181
|
+
const courtScope = request.courtAttemptId === undefined || request.courtAttemptId.length === 0
|
|
182
|
+
? undefined
|
|
183
|
+
: { courtAttemptId: request.courtAttemptId };
|
|
184
|
+
try {
|
|
185
|
+
settled = await adapters.trySettle(admitted, env.principalAuthority, courtScope);
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
// Settle throw is a real failure fact — never swallow into undefined.
|
|
189
|
+
return (await presentControlledFailure(admitted, {
|
|
190
|
+
timedOut: false,
|
|
191
|
+
code: result.code,
|
|
192
|
+
stderr: result.stderr,
|
|
193
|
+
thrown: error,
|
|
194
|
+
}, adapters, env.principalAuthority, io));
|
|
195
|
+
}
|
|
196
|
+
if (settled !== undefined && shouldPresent(settled)) {
|
|
197
|
+
// This court sealed — drop open-court pointer so bare resume is run-scoped idempotent.
|
|
198
|
+
if (settled.roleOutcome.kind === "accepted" &&
|
|
199
|
+
request.courtAttemptId !== undefined &&
|
|
200
|
+
request.courtAttemptId.length > 0) {
|
|
201
|
+
await clearCurrentCourt(admitted.runDirectory, request.courtAttemptId);
|
|
202
|
+
}
|
|
203
|
+
await markRunTerminal(admitted.runDirectory);
|
|
204
|
+
io.stdout(formatTerminalResult(settled));
|
|
205
|
+
return {
|
|
206
|
+
exitCode: exitCodeForTerminalOutcome(settled.roleOutcome),
|
|
207
|
+
admitted,
|
|
208
|
+
terminal: settled,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
const sessionFile = admitted.principal !== undefined
|
|
212
|
+
? env.principalAuthority.decode(admitted.principal).sessionFile
|
|
213
|
+
: "";
|
|
214
|
+
const runnerKnownFailure = adapters.resolveRunnerKnownFailure !== undefined && sessionFile !== ""
|
|
215
|
+
? await adapters.resolveRunnerKnownFailure({ result, sessionFile })
|
|
216
|
+
: result.knownFailure;
|
|
217
|
+
const credentialFailure = postRunMissingCredentialFailure(result, env.model, env.credentials);
|
|
218
|
+
const resolution = await resolveAuditedRunnerFailureResolution({
|
|
219
|
+
runner: runnerKnownFailure,
|
|
220
|
+
sessionFile,
|
|
221
|
+
credential: credentialFailure,
|
|
222
|
+
runDirectory: admitted.runDirectory,
|
|
223
|
+
});
|
|
224
|
+
return (await presentControlledFailure(admitted, {
|
|
225
|
+
timedOut: result.timedOut,
|
|
226
|
+
code: result.code,
|
|
227
|
+
stderr: result.stderr,
|
|
228
|
+
...controlledFailureInputFromResolution(resolution),
|
|
229
|
+
}, adapters, env.principalAuthority, io));
|
|
230
|
+
}
|
|
231
|
+
finally {
|
|
232
|
+
await lease.release();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Shared resume continuation projection (#471 / #600 / #633 / #637): seat-table
|
|
237
|
+
* model/engine/timeout axes, restored correlation, and either
|
|
238
|
+
* - manual resume: package envelope / optional caller message (unchanged), or
|
|
239
|
+
* - same-ticket summons: this turn's instruction + frozen attachment paths.
|
|
240
|
+
* Caller message and summons materials each keep their place: a resume message
|
|
241
|
+
* keeps manual-resume prompt semantics while open-court frozen attachments still
|
|
242
|
+
* ride; summons instruction is only the prompt base when no caller message.
|
|
243
|
+
* Seats add only their activation projection. Call prepareSummonsResumeMaterials
|
|
244
|
+
* first when request.summons carries attachment paths.
|
|
245
|
+
*/
|
|
246
|
+
export function resumeTurnRequestProjectionOptions(admitted, request, env, summonsPrepared) {
|
|
247
|
+
const engineMaterial = engineSessionMaterialFromOptions({
|
|
248
|
+
...(env.engine === undefined ? {} : { engine: env.engine }),
|
|
249
|
+
packageRoot: env.packageRoot,
|
|
250
|
+
});
|
|
251
|
+
let prompt;
|
|
252
|
+
if (request.message !== undefined) {
|
|
253
|
+
// Manual resume caller-message semantics stay authoritative for the prompt:
|
|
254
|
+
// message present → base bytes unchanged (including blank/whitespace).
|
|
255
|
+
// Open-court frozen attachments still continue when present; attachment
|
|
256
|
+
// projection must not re-interpret the caller message as instructionEmpty.
|
|
257
|
+
if (summonsPrepared !== undefined &&
|
|
258
|
+
summonsPrepared.attachments.length > 0) {
|
|
259
|
+
prompt = buildInstructionTransportPrompt({
|
|
260
|
+
instruction: request.message,
|
|
261
|
+
instructionEmpty: false,
|
|
262
|
+
attachments: summonsPrepared.attachments,
|
|
263
|
+
}, engineMaterial);
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
prompt = buildResumeContinuationPrompt({
|
|
267
|
+
packageRoot: env.packageRoot,
|
|
268
|
+
...(env.engine === undefined ? {} : { engine: env.engine }),
|
|
269
|
+
message: request.message,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
else if (summonsPrepared !== undefined) {
|
|
274
|
+
prompt = buildInstructionTransportPrompt(summonsPrepared, engineMaterial);
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
prompt = buildResumeContinuationPrompt({
|
|
278
|
+
packageRoot: env.packageRoot,
|
|
279
|
+
...(env.engine === undefined ? {} : { engine: env.engine }),
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
packageRoot: env.packageRoot,
|
|
284
|
+
home: env.home,
|
|
285
|
+
agentDir: env.agentDir,
|
|
286
|
+
...(env.model === undefined ? {} : { model: env.model }),
|
|
287
|
+
...(env.engine === undefined ? {} : { engine: env.engine }),
|
|
288
|
+
...(env.timeoutMs === undefined ? {} : { timeoutMs: env.timeoutMs }),
|
|
289
|
+
...(admitted.correlationId === undefined && env.correlationId === undefined
|
|
290
|
+
? {}
|
|
291
|
+
: { correlationId: admitted.correlationId ?? env.correlationId }),
|
|
292
|
+
continuation: {
|
|
293
|
+
kind: "resume",
|
|
294
|
+
prompt,
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function isAlreadyFrozenSummonsAttachment(runDirectory, attachmentPath) {
|
|
299
|
+
const absolute = isAbsolute(attachmentPath)
|
|
300
|
+
? attachmentPath
|
|
301
|
+
: resolve(attachmentPath);
|
|
302
|
+
return pathContainedIn(join(runDirectory, "attachments"), absolute);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Freeze same-ticket summons attachments into the retained run directory (#637).
|
|
306
|
+
* No-op materials (no paths / instruction-only) skip the freeze.
|
|
307
|
+
* Paths already under this run's attachments/ are the accepted freeze identity —
|
|
308
|
+
* reuse them; do not re-freeze from external originals on bare resume.
|
|
309
|
+
* Manual resume never calls this — old attachment semantics stay intact.
|
|
310
|
+
*/
|
|
311
|
+
export async function prepareSummonsResumeMaterials(runDirectory, summons) {
|
|
312
|
+
if (summons === undefined)
|
|
313
|
+
return undefined;
|
|
314
|
+
if (summons.instruction === undefined && (summons.attachmentPaths?.length ?? 0) === 0) {
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
const instruction = summons.instruction ?? "";
|
|
318
|
+
const instructionEmpty = summons.instructionEmpty ?? instruction.trim() === "";
|
|
319
|
+
let attachments = [];
|
|
320
|
+
if (summons.attachmentPaths !== undefined && summons.attachmentPaths.length > 0) {
|
|
321
|
+
const alreadyFrozen = summons.attachmentPaths.every((path) => isAlreadyFrozenSummonsAttachment(runDirectory, path));
|
|
322
|
+
attachments = alreadyFrozen
|
|
323
|
+
? summons.attachmentPaths.map((frozenPath) => ({ frozenPath }))
|
|
324
|
+
: await freezeAttachmentsIntoRun(summons.attachmentPaths, runDirectory);
|
|
325
|
+
}
|
|
326
|
+
return { instruction, instructionEmpty, attachments };
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Shared manual-resume orchestration for seats whose continuation is the
|
|
330
|
+
* package resume envelope (#599 / #633): load → structural rejection → seat
|
|
331
|
+
* turn projection → runPostAdmissionManualResume. Seat-owned loader validation,
|
|
332
|
+
* turn builder, and adapters stay on the seat.
|
|
333
|
+
*
|
|
334
|
+
* Court open/recovery transaction (#637): under the existing writer lease,
|
|
335
|
+
* read currentCourt, judge seal, clear (bound to the judged court id), freeze,
|
|
336
|
+
* and record. No pre-lease clear or stale court-snapshot consumption.
|
|
337
|
+
*/
|
|
338
|
+
export async function runPostAdmissionSeatResume(input) {
|
|
339
|
+
let request = input.request;
|
|
340
|
+
// Load once for runDirectory / structural rejection; court identity is judged
|
|
341
|
+
// only after the writer lease is held (below).
|
|
342
|
+
let loaded;
|
|
343
|
+
try {
|
|
344
|
+
loaded = await input.load(request);
|
|
345
|
+
}
|
|
346
|
+
catch (error) {
|
|
347
|
+
if (error instanceof CliUsageError) {
|
|
348
|
+
presentStructuralRejection(error, input.io);
|
|
349
|
+
return { exitCode: 2 };
|
|
350
|
+
}
|
|
351
|
+
throw error;
|
|
352
|
+
}
|
|
353
|
+
// Entire court recovery / open path runs after lease. forceContinuation skips
|
|
354
|
+
// the pre-lease sealed short-circuit; when the after-lease builder leaves no
|
|
355
|
+
// courtAttemptId, manual resume still presents run-scoped sealed idempotence
|
|
356
|
+
// under the same held lease.
|
|
357
|
+
try {
|
|
358
|
+
return await runPostAdmissionManualResume({
|
|
359
|
+
admitted: loaded.admitted,
|
|
360
|
+
env: input.env,
|
|
361
|
+
io: input.io,
|
|
362
|
+
adapters: input.adapters,
|
|
363
|
+
...(input.effectiveEngine === undefined
|
|
364
|
+
? {}
|
|
365
|
+
: { effectiveEngine: input.effectiveEngine }),
|
|
366
|
+
forceContinuation: true,
|
|
367
|
+
buildRequestAfterLease: async () => {
|
|
368
|
+
let openCourtAttemptId;
|
|
369
|
+
// Build uses the admitted judged under this lease (rehydrated when open
|
|
370
|
+
// court materials ride). Settlement identity stays on the outer admitted.
|
|
371
|
+
let admittedForBuild = loaded.admitted;
|
|
372
|
+
// Bare resume: read + seal-judge + bound clear only under the held lease.
|
|
373
|
+
if (request.summons === undefined) {
|
|
374
|
+
const openCourt = await readCurrentCourt(admittedForBuild.runDirectory);
|
|
375
|
+
if (openCourt !== undefined) {
|
|
376
|
+
const sealedForOpen = await readSealedSubmission(admittedForBuild.projectRoot, admittedForBuild.runId, {
|
|
377
|
+
home: homeFromRunDirectory(admittedForBuild.runDirectory),
|
|
378
|
+
attemptId: openCourt.courtAttemptId,
|
|
379
|
+
});
|
|
380
|
+
if (sealedForOpen === undefined) {
|
|
381
|
+
// Continue open court: same summons materials + existing courtAttemptId.
|
|
382
|
+
// Caller message (if any) stays on the request — projection keeps it.
|
|
383
|
+
openCourtAttemptId = openCourt.courtAttemptId;
|
|
384
|
+
request = {
|
|
385
|
+
runId: request.runId,
|
|
386
|
+
...(request.message === undefined
|
|
387
|
+
? {}
|
|
388
|
+
: { message: request.message }),
|
|
389
|
+
...(openCourt.summons === undefined
|
|
390
|
+
? {}
|
|
391
|
+
: { summons: openCourt.summons }),
|
|
392
|
+
};
|
|
393
|
+
if (openCourt.summons !== undefined) {
|
|
394
|
+
const reloaded = await input.load(request);
|
|
395
|
+
admittedForBuild = reloaded.admitted;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
// Open court already sealed — clear only the court id just judged.
|
|
400
|
+
await clearCurrentCourt(admittedForBuild.runDirectory, openCourt.courtAttemptId);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
// Freeze external paths once; rewrite summons to the frozen identity so
|
|
405
|
+
// currentCourt + later bare resume reuse the accepted snapshot.
|
|
406
|
+
if (request.summons !== undefined) {
|
|
407
|
+
const prepared = await prepareSummonsResumeMaterials(admittedForBuild.runDirectory, request.summons);
|
|
408
|
+
if (prepared !== undefined &&
|
|
409
|
+
(request.summons.attachmentPaths?.length ?? 0) > 0) {
|
|
410
|
+
request = {
|
|
411
|
+
...request,
|
|
412
|
+
summons: {
|
|
413
|
+
...request.summons,
|
|
414
|
+
attachmentPaths: prepared.attachments.map((attachment) => attachment.frozenPath),
|
|
415
|
+
},
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
let turnRequest = await input.buildTurnRequest(admittedForBuild, request);
|
|
420
|
+
// Court path only when continuing an open court or opening a new summons court.
|
|
421
|
+
// Bare resume with no open court (or cleared sealed pointer) keeps no
|
|
422
|
+
// courtAttemptId so run-scoped sealed idempotence can present under lease.
|
|
423
|
+
if (openCourtAttemptId !== undefined || request.summons !== undefined) {
|
|
424
|
+
const courtAttemptId = openCourtAttemptId ??
|
|
425
|
+
(turnRequest.courtAttemptId !== undefined &&
|
|
426
|
+
turnRequest.courtAttemptId.length > 0
|
|
427
|
+
? turnRequest.courtAttemptId
|
|
428
|
+
: randomUUID());
|
|
429
|
+
turnRequest = { ...turnRequest, courtAttemptId };
|
|
430
|
+
if (openCourtAttemptId === undefined) {
|
|
431
|
+
const court = {
|
|
432
|
+
courtAttemptId,
|
|
433
|
+
...(request.summons === undefined
|
|
434
|
+
? {}
|
|
435
|
+
: { summons: request.summons }),
|
|
436
|
+
};
|
|
437
|
+
await recordCurrentCourt(admittedForBuild.runDirectory, court);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return turnRequest;
|
|
441
|
+
},
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
catch (error) {
|
|
445
|
+
// Open-court rehydrate load under lease may still surface seat structural
|
|
446
|
+
// rejection (e.g. notary rejects caller message) — same exit face as pre-lease.
|
|
447
|
+
if (error instanceof CliUsageError) {
|
|
448
|
+
presentStructuralRejection(error, input.io);
|
|
449
|
+
return { exitCode: 2 };
|
|
450
|
+
}
|
|
451
|
+
throw error;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Shared post-admission one-shot path: writer lease, then turn dispatch.
|
|
456
|
+
* Initial facades own the durable admitted mark (markRunAdmitted) before
|
|
457
|
+
* entering; manual resume never re-admits.
|
|
458
|
+
*/
|
|
459
|
+
export async function runPostAdmissionOneShot(input) {
|
|
460
|
+
const { admitted, env, io, request, adapters, effectiveEngine } = input;
|
|
461
|
+
let lease;
|
|
462
|
+
try {
|
|
463
|
+
lease = await acquireRunWriterLease(admitted.runDirectory, (diagnostic) => io.stderr(diagnostic));
|
|
464
|
+
}
|
|
465
|
+
catch (error) {
|
|
466
|
+
if (error instanceof RunWriterLeaseHeldError) {
|
|
467
|
+
presentStructuralRejection(error, io);
|
|
468
|
+
return { exitCode: 2, admitted };
|
|
469
|
+
}
|
|
470
|
+
throw error;
|
|
471
|
+
}
|
|
472
|
+
return await dispatchPostAdmissionTurn({
|
|
473
|
+
admitted,
|
|
474
|
+
env,
|
|
475
|
+
io,
|
|
476
|
+
request,
|
|
477
|
+
lease,
|
|
478
|
+
adapters,
|
|
479
|
+
...(effectiveEngine === undefined ? {} : { effectiveEngine }),
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Shared post-admission resumable path with auto-resume retry loop.
|
|
484
|
+
* Initial facades own the durable admitted mark before entering.
|
|
485
|
+
*/
|
|
486
|
+
export async function runPostAdmissionResumable(input) {
|
|
487
|
+
const { admitted, env, io, buildInitialRequest, buildResumeRequest, adapters, effectiveEngine } = input;
|
|
488
|
+
return runWithAutoResumeLoop({
|
|
489
|
+
admitted,
|
|
490
|
+
principalAuthority: env.principalAuthority,
|
|
491
|
+
io,
|
|
492
|
+
sessionAppender: env.sessionAppender,
|
|
493
|
+
autoResumeLimit: env.autoResumeLimit,
|
|
494
|
+
buildInitialPayload: buildInitialRequest,
|
|
495
|
+
buildResumePayload: buildResumeRequest,
|
|
496
|
+
sealedAcceptanceDisposition: () => sealedAcceptanceRedispatchDisposition(admitted),
|
|
497
|
+
dispatch: (request, lease, _isFirst, attemptIo) => dispatchPostAdmissionTurn({
|
|
498
|
+
admitted,
|
|
499
|
+
env: {
|
|
500
|
+
...env,
|
|
501
|
+
...(admitted.correlationId === undefined ? {} : { correlationId: admitted.correlationId }),
|
|
502
|
+
},
|
|
503
|
+
io: attemptIo,
|
|
504
|
+
request,
|
|
505
|
+
lease,
|
|
506
|
+
adapters,
|
|
507
|
+
// #600: every attempt (initial + auto-resume) writes seat engine when present.
|
|
508
|
+
...(effectiveEngine === undefined ? {} : { effectiveEngine }),
|
|
509
|
+
}),
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Single authority for manual-resume run-scoped sealed-accepted presentation
|
|
514
|
+
* (#599 / #648 / #672 / #637). Used both before lease (eager request path) and
|
|
515
|
+
* after court-recovery builder under lease when no courtAttemptId remains.
|
|
516
|
+
* Returns undefined to fall through to dispatch; never rebuilds the gate.
|
|
517
|
+
*/
|
|
518
|
+
async function presentSealedAcceptedManualResumeIfAny(input) {
|
|
519
|
+
const { admitted, env, io, adapters, shouldPresent } = input;
|
|
520
|
+
try {
|
|
521
|
+
const existing = await adapters.trySettle(admitted, env.principalAuthority);
|
|
522
|
+
if (existing !== undefined &&
|
|
523
|
+
existing.roleOutcome.kind === "accepted" &&
|
|
524
|
+
shouldPresent(existing)) {
|
|
525
|
+
existing.autoResumeCount = 0;
|
|
526
|
+
io.stdout(formatTerminalResult(existing));
|
|
527
|
+
return {
|
|
528
|
+
exitCode: exitCodeForTerminalOutcome(existing.roleOutcome),
|
|
529
|
+
admitted,
|
|
530
|
+
terminal: existing,
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
catch (error) {
|
|
535
|
+
// Settlement-owned sealed disposition: sealed accepted + publication/settle
|
|
536
|
+
// throw fail closed without redispatch; authority failure preserves cause.
|
|
537
|
+
const disposition = await sealedAcceptanceRedispatchDisposition(admitted);
|
|
538
|
+
if (disposition.kind === "block") {
|
|
539
|
+
return (await presentControlledFailure(admitted, {
|
|
540
|
+
timedOut: false,
|
|
541
|
+
code: null,
|
|
542
|
+
stderr: "",
|
|
543
|
+
thrown: disposition.reason === "authority-failed"
|
|
544
|
+
? disposition.cause
|
|
545
|
+
: error,
|
|
546
|
+
}, adapters, env.principalAuthority, io));
|
|
547
|
+
}
|
|
548
|
+
// Pre-dispatch settle failure without a sealed accepted projection is not
|
|
549
|
+
// proof of seal; fall through to dispatch so the attempt path can settle
|
|
550
|
+
// or fail honestly.
|
|
551
|
+
}
|
|
552
|
+
return undefined;
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Shared post-admission manual resume path: acquire writer lease and dispatch turn.
|
|
556
|
+
* When the submission ledger is already sealed, project that accepted terminal
|
|
557
|
+
* idempotently — do not dispatch a doomed turn that would append
|
|
558
|
+
* post-seal-anomaly and erase the sealed read (#599; keep #416 open load).
|
|
559
|
+
* Same-ticket re-summons (#637) pass forceContinuation + courtAttemptId so a new
|
|
560
|
+
* court turn still runs with this summons' materials despite a prior sealed
|
|
561
|
+
* acceptance, while submission-ledger sole-final stays per-attempt.
|
|
562
|
+
*/
|
|
563
|
+
export async function runPostAdmissionManualResume(input) {
|
|
564
|
+
const { admitted, env, io, adapters, effectiveEngine, forceContinuation, buildRequestAfterLease, } = input;
|
|
565
|
+
let request = input.request;
|
|
566
|
+
// #617 DK-3: manual resume writes the live seat/env model (same as new legs).
|
|
567
|
+
const effectiveModel = env.model;
|
|
568
|
+
const shouldPresent = adapters.shouldPresentSettled ??
|
|
569
|
+
((terminal) => isLawfulTypedTerminalOutcome(terminal.roleOutcome));
|
|
570
|
+
const sealedIdempotenceInput = {
|
|
571
|
+
admitted,
|
|
572
|
+
env,
|
|
573
|
+
io,
|
|
574
|
+
adapters,
|
|
575
|
+
shouldPresent,
|
|
576
|
+
};
|
|
577
|
+
// Sealed accepted receipt only — audit_escalation / residual failure must not
|
|
578
|
+
// short-circuit; those still need a real continuation turn.
|
|
579
|
+
// Same-ticket re-summons / court recovery forceContinuation skips the pre-lease
|
|
580
|
+
// face; post-builder reuses the same presenter when no courtAttemptId remains.
|
|
581
|
+
if (forceContinuation !== true && request !== undefined) {
|
|
582
|
+
const presented = await presentSealedAcceptedManualResumeIfAny(sealedIdempotenceInput);
|
|
583
|
+
if (presented !== undefined)
|
|
584
|
+
return presented;
|
|
585
|
+
}
|
|
586
|
+
let lease;
|
|
587
|
+
let staleWriterLeaseReclaimed;
|
|
588
|
+
try {
|
|
589
|
+
lease = await acquireRunWriterLease(admitted.runDirectory, (diagnostic, kind) => {
|
|
590
|
+
// Record the typed fact before the fallible sink: if io.stderr throws
|
|
591
|
+
// (acquire deliberately swallows diagnostic-sink failures), the reclaim
|
|
592
|
+
// still happened and must stay observable.
|
|
593
|
+
if (kind === "stale-reclaimed")
|
|
594
|
+
staleWriterLeaseReclaimed = true;
|
|
595
|
+
io.stderr(diagnostic);
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
catch (error) {
|
|
599
|
+
if (error instanceof RunWriterLeaseHeldError) {
|
|
600
|
+
io.stderr(formatCliDiagnostic(error.message));
|
|
601
|
+
// A held rejection after our own reclaim must still carry the fact that
|
|
602
|
+
// this caller reclaimed the stale lock — e.g. another resumer re-locked
|
|
603
|
+
// before our retry create (#629).
|
|
604
|
+
return {
|
|
605
|
+
exitCode: 1,
|
|
606
|
+
...(staleWriterLeaseReclaimed === true
|
|
607
|
+
? { staleWriterLeaseReclaimed: true }
|
|
608
|
+
: {}),
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
throw error;
|
|
612
|
+
}
|
|
613
|
+
// Court open/recovery under held lease until dispatch owns release (finally
|
|
614
|
+
// below). Builder, sealed presenter, and any throw on this seam must release
|
|
615
|
+
// here — dispatch's finally only runs after handoff.
|
|
616
|
+
let handedOffToDispatch = false;
|
|
617
|
+
try {
|
|
618
|
+
if (request === undefined) {
|
|
619
|
+
if (buildRequestAfterLease === undefined) {
|
|
620
|
+
throw new Error("runPostAdmissionManualResume requires request or buildRequestAfterLease");
|
|
621
|
+
}
|
|
622
|
+
request = await buildRequestAfterLease();
|
|
623
|
+
if (request.courtAttemptId === undefined ||
|
|
624
|
+
request.courtAttemptId.length === 0) {
|
|
625
|
+
const presented = await presentSealedAcceptedManualResumeIfAny(sealedIdempotenceInput);
|
|
626
|
+
if (presented !== undefined) {
|
|
627
|
+
return {
|
|
628
|
+
...presented,
|
|
629
|
+
...(staleWriterLeaseReclaimed === true
|
|
630
|
+
? { staleWriterLeaseReclaimed: true }
|
|
631
|
+
: {}),
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
handedOffToDispatch = true;
|
|
637
|
+
const result = await dispatchPostAdmissionTurn({
|
|
638
|
+
admitted,
|
|
639
|
+
env: {
|
|
640
|
+
...env,
|
|
641
|
+
...(effectiveModel === undefined ? {} : { model: effectiveModel }),
|
|
642
|
+
...(admitted.correlationId === undefined
|
|
643
|
+
? {}
|
|
644
|
+
: { correlationId: admitted.correlationId }),
|
|
645
|
+
},
|
|
646
|
+
io,
|
|
647
|
+
request,
|
|
648
|
+
lease,
|
|
649
|
+
adapters,
|
|
650
|
+
...(effectiveEngine === undefined ? {} : { effectiveEngine }),
|
|
651
|
+
});
|
|
652
|
+
if (result.terminal !== undefined) {
|
|
653
|
+
result.terminal.autoResumeCount = 0;
|
|
654
|
+
}
|
|
655
|
+
return {
|
|
656
|
+
...result,
|
|
657
|
+
...(staleWriterLeaseReclaimed === true
|
|
658
|
+
? { staleWriterLeaseReclaimed: true }
|
|
659
|
+
: {}),
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
finally {
|
|
663
|
+
if (!handedOffToDispatch) {
|
|
664
|
+
await lease.release();
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|