@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
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { packagedExternalHostNames } from "../host-descriptions.js";
|
|
2
|
+
import { createPiRoleTurnHost } from "../pi/role-turn-host.js";
|
|
3
|
+
import { observeLaunchedRolePackageIdentity, recordLaunchedPiIdentity, recordLaunchedRolePackageIdentity, } from "./invocation.js";
|
|
4
|
+
import { createLazyProductionExternalHost } from "./load-production-external-host.js";
|
|
5
|
+
export function formatHostSelectionFailure(failure) {
|
|
6
|
+
return `${failure.kind}: ${failure.host}; seat=${failure.seat}; model=${failure.model}; registered: ${failure.registeredHosts.join(", ")}`;
|
|
7
|
+
}
|
|
8
|
+
export class HostSelectionError extends Error {
|
|
9
|
+
failure;
|
|
10
|
+
constructor(failure) {
|
|
11
|
+
super(formatHostSelectionFailure(failure));
|
|
12
|
+
this.failure = failure;
|
|
13
|
+
this.name = "HostSelectionError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function composeRoleTurnHostAdapters(env, principalAuthority) {
|
|
17
|
+
const piHost = env.roleTurnHost ?? createPiRoleTurnHost({
|
|
18
|
+
packageRoot: env.packageRoot,
|
|
19
|
+
principalAuthority,
|
|
20
|
+
...(env.extraPiArgs === undefined ? {} : { extraPiArgs: env.extraPiArgs }),
|
|
21
|
+
...(env.timeoutMs === undefined ? {} : { timeoutMs: env.timeoutMs }),
|
|
22
|
+
recordLaunchedPiIdentity,
|
|
23
|
+
recordLaunchedRolePackageIdentity,
|
|
24
|
+
observeLaunchedRolePackageIdentity,
|
|
25
|
+
});
|
|
26
|
+
return env.hostAdapters ?? [
|
|
27
|
+
{ name: "pi", create: () => ({ ok: true, host: piHost }) },
|
|
28
|
+
...packagedExternalHostNames().map((name) => ({
|
|
29
|
+
name,
|
|
30
|
+
create: () => ({
|
|
31
|
+
ok: true,
|
|
32
|
+
host: createLazyProductionExternalHost({
|
|
33
|
+
packageRoot: env.packageRoot,
|
|
34
|
+
hostName: name,
|
|
35
|
+
principalAuthority,
|
|
36
|
+
}),
|
|
37
|
+
}),
|
|
38
|
+
})),
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
export function selectRoleTurnHost(adapters, options) {
|
|
42
|
+
const hostName = options.seat.host;
|
|
43
|
+
const adapter = adapters.find((candidate) => candidate.name === hostName);
|
|
44
|
+
const model = options.seat.selection === undefined
|
|
45
|
+
? "unconfigured"
|
|
46
|
+
: `${options.seat.selection.provider}/${options.seat.selection.model}`;
|
|
47
|
+
const registeredHosts = adapters.map(({ name }) => name);
|
|
48
|
+
if (adapter === undefined) {
|
|
49
|
+
throw new HostSelectionError({ kind: "host-unregistered", host: hostName, seat: options.role, model, registeredHosts });
|
|
50
|
+
}
|
|
51
|
+
const selected = adapter.create({ role: options.role, model: options.seat.selection });
|
|
52
|
+
if (!selected.ok) {
|
|
53
|
+
throw new HostSelectionError({ kind: "host-model-mismatch", host: hostName, seat: options.role, model, registeredHosts });
|
|
54
|
+
}
|
|
55
|
+
return selected.host;
|
|
56
|
+
}
|
|
57
|
+
export function resolveRoleTurnHost(env, options) {
|
|
58
|
+
return selectRoleTurnHost(composeRoleTurnHostAdapters(env, options.principalAuthority), { role: options.role, seat: options.seat });
|
|
59
|
+
}
|
|
@@ -335,19 +335,21 @@ export async function markRunAdmitted(admitted, authority) {
|
|
|
335
335
|
});
|
|
336
336
|
}
|
|
337
337
|
/**
|
|
338
|
-
* Shared dispatch execution seam:
|
|
339
|
-
* resume override) and the authoritative
|
|
340
|
-
*
|
|
338
|
+
* Shared dispatch execution seam: transition to running, then record the
|
|
339
|
+
* effective launch model (initial or resume override) and the authoritative
|
|
340
|
+
* seat engine/host onto invocation.json.
|
|
341
341
|
* Role runners must not coordinate lifecycle ledger writes themselves.
|
|
342
342
|
* Engine axis is authoritative here (#617): present string is written; omit/undefined
|
|
343
343
|
* clears any prior engine key so unset-engine + resume does not keep a stale value.
|
|
344
344
|
* (Non-authoritative partial updates still use recordEffectiveInvocationModel directly
|
|
345
345
|
* with `engine: undefined` to preserve.)
|
|
346
|
+
* Host-page write is deliberately the last step (#840 r9 判词 class 2): this
|
|
347
|
+
* function is not atomic, and a caller retrying after it throws must not see
|
|
348
|
+
* the new host already committed while the run-state transition itself never
|
|
349
|
+
* completed — the run-state write happens first, so any failure past that
|
|
350
|
+
* point still leaves the prior invocation host in place for the retry.
|
|
346
351
|
*/
|
|
347
352
|
export async function markRunRunning(runDirectory, effectiveModel, effectiveEngine, effectiveHost) {
|
|
348
|
-
await recordEffectiveInvocationModel(runDirectory, effectiveModel,
|
|
349
|
-
// Authoritative seat projection: absent engine ⇒ null (delete).
|
|
350
|
-
effectiveEngine === undefined ? null : effectiveEngine, effectiveHost);
|
|
351
353
|
const current = await readRoleRunStateDisk(runDirectory);
|
|
352
354
|
if (current === undefined) {
|
|
353
355
|
throw new Error("cannot mark running: run state missing");
|
|
@@ -366,6 +368,9 @@ export async function markRunRunning(runDirectory, effectiveModel, effectiveEngi
|
|
|
366
368
|
...(current.phase === undefined ? {} : { phase: current.phase }),
|
|
367
369
|
...(current.currentCourt === undefined ? {} : { currentCourt: current.currentCourt }),
|
|
368
370
|
});
|
|
371
|
+
await recordEffectiveInvocationModel(runDirectory, effectiveModel,
|
|
372
|
+
// Authoritative seat projection: absent engine ⇒ null (delete).
|
|
373
|
+
effectiveEngine === undefined ? null : effectiveEngine, effectiveHost);
|
|
369
374
|
}
|
|
370
375
|
/** @deprecated #416: 429-only resumable marker; kept for historical runs. */
|
|
371
376
|
export async function markRunResumable(runDirectory, observation) {
|
|
@@ -24,5 +24,6 @@ export function projectRoleTurnRequest(admitted, roleDetails, options) {
|
|
|
24
24
|
...(options.courtAttemptId === undefined || options.courtAttemptId.length === 0
|
|
25
25
|
? {}
|
|
26
26
|
: { courtAttemptId: options.courtAttemptId }),
|
|
27
|
+
...(options.stationChild === undefined ? {} : { stationChild: options.stationChild }),
|
|
27
28
|
};
|
|
28
29
|
}
|
|
@@ -70,48 +70,29 @@ function projectSeatEngine(seat) {
|
|
|
70
70
|
function projectSeatHost(seat) {
|
|
71
71
|
return seat.host === void 0 ? {} : { host: seat.host };
|
|
72
72
|
}
|
|
73
|
+
function hostSelectionFailureFromUnknown(error) {
|
|
74
|
+
if (typeof error !== "object" || error === null) return void 0;
|
|
75
|
+
if (error.name !== "HostSelectionError") return void 0;
|
|
76
|
+
const failure = error.failure;
|
|
77
|
+
return failure;
|
|
78
|
+
}
|
|
73
79
|
async function createSummonEnv(options) {
|
|
74
|
-
const [{ piDurablePrincipalAuthority }, { appendPiSessionCustomEntry,
|
|
80
|
+
const [{ piDurablePrincipalAuthority }, { appendPiSessionCustomEntry }, { resolveRoleTurnHost }] = await Promise.all([
|
|
75
81
|
import("./pi/durable-principal.js"),
|
|
76
|
-
import("./pi/role-turn-host.js")
|
|
82
|
+
import("./pi/role-turn-host.js"),
|
|
83
|
+
import("./public-cli/role-turn-host-resolution.js")
|
|
77
84
|
]);
|
|
78
85
|
const principalAuthority = piDurablePrincipalAuthority;
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const { recordLaunchedRolePackageIdentity } = await import("./public-cli/invocation.js");
|
|
86
|
-
return recordLaunchedRolePackageIdentity(runDirectory, identity);
|
|
86
|
+
const roleTurnHost = resolveRoleTurnHost(
|
|
87
|
+
{
|
|
88
|
+
packageRoot: options.packageRoot,
|
|
89
|
+
...options.roleTurnHost === void 0 ? {} : { roleTurnHost: options.roleTurnHost },
|
|
90
|
+
...options.hostAdapters === void 0 ? {} : { hostAdapters: options.hostAdapters },
|
|
91
|
+
...options.extraPiArgs === void 0 || options.extraPiArgs.length === 0 ? {} : { extraPiArgs: options.extraPiArgs }
|
|
87
92
|
},
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return observeLaunchedRolePackageIdentity(root, roleEntry);
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
const piHost = createPiRoleTurnHost({
|
|
94
|
-
packageRoot: options.packageRoot,
|
|
95
|
-
principalAuthority,
|
|
96
|
-
...options.extraPiArgs === void 0 || options.extraPiArgs.length === 0 ? {} : { extraPiArgs: options.extraPiArgs },
|
|
97
|
-
...piRecords
|
|
98
|
-
});
|
|
93
|
+
{ role: options.role, seat: options.seat, principalAuthority }
|
|
94
|
+
);
|
|
99
95
|
const hostName = options.seat.host ?? "pi";
|
|
100
|
-
let roleTurnHost = piHost;
|
|
101
|
-
if (hostName !== "pi") {
|
|
102
|
-
const { lookupHostFamily } = await import("./host-descriptions.js");
|
|
103
|
-
const { createLazyProductionExternalHost } = await import("./public-cli/load-production-external-host.js");
|
|
104
|
-
if (lookupHostFamily(hostName) === void 0) {
|
|
105
|
-
throw new Error(
|
|
106
|
-
`public role summons host unregistered: host=${hostName} seat=${options.role}`
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
roleTurnHost = createLazyProductionExternalHost({
|
|
110
|
-
packageRoot: options.packageRoot,
|
|
111
|
-
hostName,
|
|
112
|
-
principalAuthority
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
96
|
const { loadHostProvidersTable, projectHostFacingProvider } = await import("./public-cli/host-providers.js");
|
|
116
97
|
const hostFacingSelection = options.seat.selection === void 0 ? void 0 : projectHostFacingProvider(
|
|
117
98
|
options.seat.selection,
|
|
@@ -145,8 +126,9 @@ async function summonPublicRole(options) {
|
|
|
145
126
|
const credentials = options.credentials ?? await loadCredentialProviders(agentDir);
|
|
146
127
|
const config = await loadPublicCliConfig(home);
|
|
147
128
|
const seat = resolveEffectiveSeat(config, options.role, credentials);
|
|
148
|
-
|
|
149
|
-
|
|
129
|
+
let summonEnv;
|
|
130
|
+
try {
|
|
131
|
+
summonEnv = await createSummonEnv({
|
|
150
132
|
role: options.role,
|
|
151
133
|
home,
|
|
152
134
|
agentDir,
|
|
@@ -154,8 +136,22 @@ async function summonPublicRole(options) {
|
|
|
154
136
|
packageRoot,
|
|
155
137
|
credentials,
|
|
156
138
|
seat,
|
|
157
|
-
...options.extraPiArgs === void 0 ? {} : { extraPiArgs: options.extraPiArgs }
|
|
158
|
-
|
|
139
|
+
...options.extraPiArgs === void 0 ? {} : { extraPiArgs: options.extraPiArgs },
|
|
140
|
+
...options.roleTurnHost === void 0 ? {} : { roleTurnHost: options.roleTurnHost },
|
|
141
|
+
...options.hostAdapters === void 0 ? {} : { hostAdapters: options.hostAdapters }
|
|
142
|
+
});
|
|
143
|
+
} catch (error) {
|
|
144
|
+
const failure = hostSelectionFailureFromUnknown(error);
|
|
145
|
+
if (failure !== void 0) {
|
|
146
|
+
const { formatHostSelectionFailure } = await import("./public-cli/role-turn-host-resolution.js");
|
|
147
|
+
return { exitCode: 1, stderr: formatHostSelectionFailure(failure) };
|
|
148
|
+
}
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
const env = {
|
|
152
|
+
...summonEnv,
|
|
153
|
+
// Station child role run (#840): omit automatic navigator attendance.
|
|
154
|
+
stationChild: true,
|
|
159
155
|
// Host config passthrough only — same face as public CLI (#422 / #675).
|
|
160
156
|
...config.autoResumeLimit === void 0 ? {} : { autoResumeLimit: config.autoResumeLimit },
|
|
161
157
|
// Parent cancellation reaches the nested activation's own turn dispatch.
|
|
@@ -164,8 +160,8 @@ async function summonPublicRole(options) {
|
|
|
164
160
|
...options.reviewReask === void 0 ? {} : { reviewReask: options.reviewReask },
|
|
165
161
|
// #786: verbatim submission body on same-parent resume (not conclusion re-ask).
|
|
166
162
|
...options.gateReviewInstruction === void 0 ? {} : { gateReviewInstruction: options.gateReviewInstruction },
|
|
167
|
-
|
|
168
|
-
|
|
163
|
+
...options.boundTicketNumber === void 0 ? {} : { boundTicketNumber: options.boundTicketNumber },
|
|
164
|
+
// createRunId is the only remaining env overlay — host is seat-selected above.
|
|
169
165
|
...options.createRunId === void 0 ? {} : { createRunId: options.createRunId }
|
|
170
166
|
};
|
|
171
167
|
const captured = options.io === void 0 ? createCapturingIo() : void 0;
|
|
@@ -246,12 +242,21 @@ async function summonPublicRole(options) {
|
|
|
246
242
|
result = await runPublicDoctor(options.argv, env, io, parseDoctorArgv);
|
|
247
243
|
break;
|
|
248
244
|
}
|
|
245
|
+
case "diarist": {
|
|
246
|
+
const [{ runPublicDiarist }, { parseDiaristArgv }] = await Promise.all([
|
|
247
|
+
import("./public-cli/diarist-run.js"),
|
|
248
|
+
import("./public-cli/invocation.js")
|
|
249
|
+
]);
|
|
250
|
+
result = await runPublicDiarist(options.argv, env, io, parseDiaristArgv);
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
249
253
|
}
|
|
250
254
|
const stderr = captured?.stderrText();
|
|
251
255
|
const runDirectory = result.admitted?.runDirectory;
|
|
252
256
|
return {
|
|
253
257
|
exitCode: result.exitCode,
|
|
254
258
|
...result.terminal === void 0 ? {} : { terminal: result.terminal },
|
|
259
|
+
...result.admitted === void 0 ? {} : { admitted: result.admitted },
|
|
255
260
|
...typeof runDirectory === "string" && runDirectory.trim() !== "" ? { runDirectory } : {},
|
|
256
261
|
...stderr === void 0 || stderr === "" ? {} : { stderr }
|
|
257
262
|
};
|
|
@@ -278,6 +283,7 @@ async function summonGateOfficer(options) {
|
|
|
278
283
|
...options.reask === void 0 ? {} : { reviewReask: options.reask },
|
|
279
284
|
...gateReviewInstruction === void 0 ? {} : { gateReviewInstruction },
|
|
280
285
|
...options.roleTurnHost === void 0 ? {} : { roleTurnHost: options.roleTurnHost },
|
|
286
|
+
...options.hostAdapters === void 0 ? {} : { hostAdapters: options.hostAdapters },
|
|
281
287
|
...options.createRunId === void 0 ? {} : { createRunId: options.createRunId }
|
|
282
288
|
};
|
|
283
289
|
if (options.officer === "notary") {
|
|
@@ -3,6 +3,9 @@ import { packagedRoleInputFlag, packagedRolePhaseFlag } from "./packaged-role-re
|
|
|
3
3
|
export function projectActivationFlags(request) {
|
|
4
4
|
const activation = request.activation;
|
|
5
5
|
const flags = new Map([["ak-role", activation.role]]);
|
|
6
|
+
if (request.stationChild === true) {
|
|
7
|
+
flags.set("ak-station-child", true);
|
|
8
|
+
}
|
|
6
9
|
const inputFlag = packagedRoleInputFlag(activation.role);
|
|
7
10
|
const phaseFlag = packagedRolePhaseFlag(activation.role);
|
|
8
11
|
if ("phase" in activation && phaseFlag !== undefined)
|
package/dist/role-runtime.js
CHANGED
|
@@ -91,6 +91,13 @@ const GLEANER_LEFT_TRANSPORT_FLAGS = Object.freeze([
|
|
|
91
91
|
definition: GLEANER_LEFT_BASE_FLAG.definition,
|
|
92
92
|
}),
|
|
93
93
|
]);
|
|
94
|
+
export const STATION_CHILD_FLAG = Object.freeze({
|
|
95
|
+
name: "ak-station-child",
|
|
96
|
+
definition: Object.freeze({
|
|
97
|
+
description: "Station-child role run (omit automatic navigator attendance; #840)",
|
|
98
|
+
type: "boolean",
|
|
99
|
+
}),
|
|
100
|
+
});
|
|
94
101
|
/**
|
|
95
102
|
* Decode private transport flags into frozen admitted inputs.
|
|
96
103
|
* Envelope-owned; necessary JSON decode only (public --authority-ref owns grammar).
|
|
@@ -674,6 +681,8 @@ export function createRoleRuntimeExtension(dependencies) {
|
|
|
674
681
|
for (const flag of COLLECTOR_TRANSPORT_FLAGS) {
|
|
675
682
|
roleHost.registerFlag(flag.name, flag.definition);
|
|
676
683
|
}
|
|
684
|
+
// Station-child identity (#840): omit navigator attendance. One flag.
|
|
685
|
+
roleHost.registerFlag(STATION_CHILD_FLAG.name, STATION_CHILD_FLAG.definition);
|
|
677
686
|
let admitted = false;
|
|
678
687
|
let selectedRole;
|
|
679
688
|
/** Live Reviewer parent activation for envelope agent_start prompt assembly. */
|
|
@@ -1473,12 +1482,14 @@ export function createRoleRuntimeExtension(dependencies) {
|
|
|
1473
1482
|
const sessionFile = ctx.sessionManager.getSessionFile?.() || ctx.sessionManager.getSessionDir?.();
|
|
1474
1483
|
const ledgerHome = resolveActivationLedgerHomeForPath(sessionFile);
|
|
1475
1484
|
const session = durableSessionPointer(ctx.sessionManager);
|
|
1476
|
-
//
|
|
1477
|
-
//
|
|
1485
|
+
// Station children (court diarist, inner-gate summons) omit navigator sidecar (#840).
|
|
1486
|
+
// Top-level public entry legs still attend automatically.
|
|
1478
1487
|
// Navigator seat never re-attaches itself — prepare turns already ARE the navigator
|
|
1479
1488
|
// public activation (prevents summonPublicRole navigator ↔ attendance recursion).
|
|
1489
|
+
const isStationChild = roleHost.getFlag(STATION_CHILD_FLAG.name) === true;
|
|
1480
1490
|
if (dependencies.createNavigatorAttendance !== undefined
|
|
1481
|
-
&& entry.role !== "navigator"
|
|
1491
|
+
&& entry.role !== "navigator"
|
|
1492
|
+
&& !isStationChild) {
|
|
1482
1493
|
let work;
|
|
1483
1494
|
let contextError;
|
|
1484
1495
|
if (dependencies.loadNavigatorWorkContext === undefined) {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { DEFAULT_ROLE_TURN_HOST, lookupHeadlessHostDescription, lookupHostDescription, } from "./host-descriptions.js";
|
|
4
|
+
/** Durable session binding stored beside the host-owned session principal. */
|
|
5
|
+
export function createSessionIdentityAuthority(authority, sessionBindingFile) {
|
|
6
|
+
const bindingPath = (principal) => join(authority.decode(principal).sessionDirectory, sessionBindingFile);
|
|
7
|
+
return {
|
|
8
|
+
resolveSessionFile(principal) {
|
|
9
|
+
return authority.decode(principal).sessionFile;
|
|
10
|
+
},
|
|
11
|
+
async load(principal) {
|
|
12
|
+
try {
|
|
13
|
+
const value = JSON.parse(await readFile(bindingPath(principal), "utf8"));
|
|
14
|
+
if (typeof value !== "object" || value === null || typeof value.sessionId !== "string") {
|
|
15
|
+
throw new Error("durable session binding is invalid");
|
|
16
|
+
}
|
|
17
|
+
return value.sessionId;
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
if (error.code === "ENOENT")
|
|
21
|
+
return undefined;
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
async bind(principal, sessionId) {
|
|
26
|
+
const target = bindingPath(principal);
|
|
27
|
+
await mkdir(dirname(target), { recursive: true });
|
|
28
|
+
const temporary = `${target}.${process.pid}.tmp`;
|
|
29
|
+
await writeFile(temporary, `${JSON.stringify({ sessionId })}\n`, { encoding: "utf8", mode: 0o600 });
|
|
30
|
+
await rename(temporary, target);
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Host-aware resumable-session probe (#840 P1). The shared auto-resume loop
|
|
36
|
+
* (public-cli/auto-resume.ts) asks one question — "can this principal's turn
|
|
37
|
+
* still be resumed" — without itself knowing which concrete host executed it.
|
|
38
|
+
* Pi's own session.jsonl transcript (DurablePrincipalAuthority#isAvailable)
|
|
39
|
+
* answers that only for the pi host; ACP and headless hosts persist their
|
|
40
|
+
* resumable native session id under their own session-identity binding file
|
|
41
|
+
* instead (#729 / #731 / #645) and never write session.jsonl, so the plain
|
|
42
|
+
* pi check always reports them unavailable after one attempt. One lookup per
|
|
43
|
+
* registered host family — no per-host-name branch, no per-seat whitelist.
|
|
44
|
+
*/
|
|
45
|
+
export function resolveHostAwareSessionAvailability(host, principalAuthority) {
|
|
46
|
+
if (host === undefined || host === DEFAULT_ROLE_TURN_HOST) {
|
|
47
|
+
return (principal) => principalAuthority.isAvailable(principal);
|
|
48
|
+
}
|
|
49
|
+
const description = lookupHostDescription(host) ?? lookupHeadlessHostDescription(host);
|
|
50
|
+
if (description === undefined) {
|
|
51
|
+
// Host selection already fails closed on an unregistered name (#510) long
|
|
52
|
+
// before a turn dispatches; mirror the same fail-closed default here
|
|
53
|
+
// rather than guessing at pi's binding for a name pi does not own.
|
|
54
|
+
return () => Promise.resolve(false);
|
|
55
|
+
}
|
|
56
|
+
const sessionIdentity = createSessionIdentityAuthority(principalAuthority, description.sessionBindingFile);
|
|
57
|
+
return async (principal) => (await sessionIdentity.load(principal)) !== undefined;
|
|
58
|
+
}
|
package/package.json
CHANGED
|
@@ -124,7 +124,12 @@ async function usageFromSummonedSession(summoned: PublicSummonResult): Promise<U
|
|
|
124
124
|
async function projectAuditorTerminal(summoned: PublicSummonResult): Promise<ComplianceDecision> {
|
|
125
125
|
const outcome = summoned.terminal?.roleOutcome;
|
|
126
126
|
if (outcome === undefined) {
|
|
127
|
-
|
|
127
|
+
const detail = summoned.stderr?.trim();
|
|
128
|
+
throw new Error(
|
|
129
|
+
detail && detail.length > 0
|
|
130
|
+
? `Auditor public summon produced no terminal (exit ${summoned.exitCode}: ${detail})`
|
|
131
|
+
: `Auditor public summon produced no terminal (exit ${summoned.exitCode})`,
|
|
132
|
+
);
|
|
128
133
|
}
|
|
129
134
|
const usage = await usageFromSummonedSession(summoned);
|
|
130
135
|
if (outcome.kind === "no_receipt") {
|
package/src/host-contracts.ts
CHANGED
|
@@ -183,6 +183,8 @@ export type RoleTurnRequest = {
|
|
|
183
183
|
* and resume-with-message set this; bare resume without an open court omits it.
|
|
184
184
|
*/
|
|
185
185
|
readonly courtAttemptId?: string;
|
|
186
|
+
/** Station child role run (#840): omit automatic navigator attendance. */
|
|
187
|
+
readonly stationChild?: boolean;
|
|
186
188
|
};
|
|
187
189
|
|
|
188
190
|
/** Turn result — only fields upper layers currently consume. */
|