@akagilnc/pi-workflow-roles 0.1.3902 → 0.1.3936
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
|
@@ -29,7 +29,6 @@ import {
|
|
|
29
29
|
buildResumeContinuationPrompt,
|
|
30
30
|
type PublicResumeRequest,
|
|
31
31
|
} from "./run-lifecycle.ts";
|
|
32
|
-
import { clearReviewerDispatchRejection } from "./reviewer-dispatch-rejection.ts";
|
|
33
32
|
import {
|
|
34
33
|
presentStructuralRejection,
|
|
35
34
|
readEngineDetourInfrastructureFailure,
|
|
@@ -84,7 +83,6 @@ function reviewerAdapters(
|
|
|
84
83
|
methodMaterial?: PackagedMethodSkillMaterial,
|
|
85
84
|
): PostAdmissionAdapters<AdmittedReviewerInvocation> {
|
|
86
85
|
return {
|
|
87
|
-
beforeDispatch: (admitted) => clearReviewerDispatchRejection(admitted.runDirectory),
|
|
88
86
|
trySettle: (admitted, authority, scope) =>
|
|
89
87
|
methodMaterial === undefined
|
|
90
88
|
? Promise.resolve(undefined)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One host-adapter table and one seat-keyed selection (#617 / ADR 0082).
|
|
3
|
+
* Public CLI composition root and nested public summons share this seam —
|
|
4
|
+
* the selected parent adapter is never a child-host override.
|
|
5
|
+
*/
|
|
6
|
+
import type { DurablePrincipalAuthority, RoleTurnHost } from "../host-contracts.ts";
|
|
7
|
+
import { packagedExternalHostNames } from "../host-descriptions.ts";
|
|
8
|
+
import { createPiRoleTurnHost } from "../pi/role-turn-host.ts";
|
|
9
|
+
import type { EffectiveSeat } from "./config.ts";
|
|
10
|
+
import {
|
|
11
|
+
observeLaunchedRolePackageIdentity,
|
|
12
|
+
recordLaunchedPiIdentity,
|
|
13
|
+
recordLaunchedRolePackageIdentity,
|
|
14
|
+
} from "./invocation.ts";
|
|
15
|
+
import { createLazyProductionExternalHost } from "./load-production-external-host.ts";
|
|
16
|
+
import type { PublicCallableRole } from "./registry.ts";
|
|
17
|
+
|
|
18
|
+
export type HostSelectionFailure = {
|
|
19
|
+
readonly kind: "host-unregistered" | "host-model-mismatch";
|
|
20
|
+
readonly host: string;
|
|
21
|
+
readonly seat: PublicCallableRole;
|
|
22
|
+
readonly model: string;
|
|
23
|
+
readonly registeredHosts: readonly string[];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function formatHostSelectionFailure(failure: HostSelectionFailure): string {
|
|
27
|
+
return `${failure.kind}: ${failure.host}; seat=${failure.seat}; model=${failure.model}; registered: ${failure.registeredHosts.join(", ")}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type NamedRoleTurnHostAdapter = {
|
|
31
|
+
readonly name: string;
|
|
32
|
+
readonly create: (input: { role: PublicCallableRole; model: EffectiveSeat["selection"] }) =>
|
|
33
|
+
| { readonly ok: true; readonly host: RoleTurnHost }
|
|
34
|
+
| { readonly ok: false };
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export class HostSelectionError extends Error {
|
|
38
|
+
constructor(readonly failure: HostSelectionFailure) {
|
|
39
|
+
super(formatHostSelectionFailure(failure));
|
|
40
|
+
this.name = "HostSelectionError";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type RoleTurnHostResolutionInput = {
|
|
45
|
+
/** Pi-adapter inject (tests). Not a selected-host override. */
|
|
46
|
+
readonly roleTurnHost?: RoleTurnHost;
|
|
47
|
+
/** Composition-root adapter table. When omitted, pi + packaged externals. */
|
|
48
|
+
readonly hostAdapters?: readonly NamedRoleTurnHostAdapter[];
|
|
49
|
+
readonly packageRoot: string;
|
|
50
|
+
readonly extraPiArgs?: readonly string[];
|
|
51
|
+
readonly timeoutMs?: number;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export function composeRoleTurnHostAdapters(
|
|
55
|
+
env: RoleTurnHostResolutionInput,
|
|
56
|
+
principalAuthority: DurablePrincipalAuthority,
|
|
57
|
+
): readonly NamedRoleTurnHostAdapter[] {
|
|
58
|
+
const piHost = env.roleTurnHost ?? createPiRoleTurnHost({
|
|
59
|
+
packageRoot: env.packageRoot,
|
|
60
|
+
principalAuthority,
|
|
61
|
+
...(env.extraPiArgs === undefined ? {} : { extraPiArgs: env.extraPiArgs }),
|
|
62
|
+
...(env.timeoutMs === undefined ? {} : { timeoutMs: env.timeoutMs }),
|
|
63
|
+
recordLaunchedPiIdentity,
|
|
64
|
+
recordLaunchedRolePackageIdentity,
|
|
65
|
+
observeLaunchedRolePackageIdentity,
|
|
66
|
+
});
|
|
67
|
+
return env.hostAdapters ?? [
|
|
68
|
+
{ name: "pi", create: () => ({ ok: true as const, host: piHost }) },
|
|
69
|
+
...packagedExternalHostNames().map((name) => ({
|
|
70
|
+
name,
|
|
71
|
+
create: () => ({
|
|
72
|
+
ok: true as const,
|
|
73
|
+
host: createLazyProductionExternalHost({
|
|
74
|
+
packageRoot: env.packageRoot,
|
|
75
|
+
hostName: name,
|
|
76
|
+
principalAuthority,
|
|
77
|
+
}),
|
|
78
|
+
}),
|
|
79
|
+
})),
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function selectRoleTurnHost(
|
|
84
|
+
adapters: readonly NamedRoleTurnHostAdapter[],
|
|
85
|
+
options: {
|
|
86
|
+
role: PublicCallableRole;
|
|
87
|
+
seat: EffectiveSeat;
|
|
88
|
+
},
|
|
89
|
+
): RoleTurnHost {
|
|
90
|
+
const hostName = options.seat.host;
|
|
91
|
+
const adapter = adapters.find((candidate) => candidate.name === hostName);
|
|
92
|
+
const model = options.seat.selection === undefined
|
|
93
|
+
? "unconfigured"
|
|
94
|
+
: `${options.seat.selection.provider}/${options.seat.selection.model}`;
|
|
95
|
+
const registeredHosts = adapters.map(({ name }) => name);
|
|
96
|
+
if (adapter === undefined) {
|
|
97
|
+
throw new HostSelectionError(
|
|
98
|
+
{ kind: "host-unregistered", host: hostName, seat: options.role, model, registeredHosts },
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
const selected = adapter.create({ role: options.role, model: options.seat.selection });
|
|
102
|
+
if (!selected.ok) {
|
|
103
|
+
throw new HostSelectionError(
|
|
104
|
+
{ kind: "host-model-mismatch", host: hostName, seat: options.role, model, registeredHosts },
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
return selected.host;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function resolveRoleTurnHost(
|
|
111
|
+
env: RoleTurnHostResolutionInput,
|
|
112
|
+
options: {
|
|
113
|
+
role: PublicCallableRole;
|
|
114
|
+
seat: EffectiveSeat;
|
|
115
|
+
principalAuthority: DurablePrincipalAuthority;
|
|
116
|
+
},
|
|
117
|
+
): RoleTurnHost {
|
|
118
|
+
return selectRoleTurnHost(
|
|
119
|
+
composeRoleTurnHostAdapters(env, options.principalAuthority),
|
|
120
|
+
{ role: options.role, seat: options.seat },
|
|
121
|
+
);
|
|
122
|
+
}
|
|
@@ -571,14 +571,19 @@ export async function markRunAdmitted(
|
|
|
571
571
|
}
|
|
572
572
|
|
|
573
573
|
/**
|
|
574
|
-
* Shared dispatch execution seam:
|
|
575
|
-
* resume override) and the authoritative
|
|
576
|
-
*
|
|
574
|
+
* Shared dispatch execution seam: transition to running, then record the
|
|
575
|
+
* effective launch model (initial or resume override) and the authoritative
|
|
576
|
+
* seat engine/host onto invocation.json.
|
|
577
577
|
* Role runners must not coordinate lifecycle ledger writes themselves.
|
|
578
578
|
* Engine axis is authoritative here (#617): present string is written; omit/undefined
|
|
579
579
|
* clears any prior engine key so unset-engine + resume does not keep a stale value.
|
|
580
580
|
* (Non-authoritative partial updates still use recordEffectiveInvocationModel directly
|
|
581
581
|
* with `engine: undefined` to preserve.)
|
|
582
|
+
* Host-page write is deliberately the last step (#840 r9 判词 class 2): this
|
|
583
|
+
* function is not atomic, and a caller retrying after it throws must not see
|
|
584
|
+
* the new host already committed while the run-state transition itself never
|
|
585
|
+
* completed — the run-state write happens first, so any failure past that
|
|
586
|
+
* point still leaves the prior invocation host in place for the retry.
|
|
582
587
|
*/
|
|
583
588
|
export async function markRunRunning(
|
|
584
589
|
runDirectory: string,
|
|
@@ -586,13 +591,6 @@ export async function markRunRunning(
|
|
|
586
591
|
effectiveEngine?: string,
|
|
587
592
|
effectiveHost?: string,
|
|
588
593
|
): Promise<void> {
|
|
589
|
-
await recordEffectiveInvocationModel(
|
|
590
|
-
runDirectory,
|
|
591
|
-
effectiveModel,
|
|
592
|
-
// Authoritative seat projection: absent engine ⇒ null (delete).
|
|
593
|
-
effectiveEngine === undefined ? null : effectiveEngine,
|
|
594
|
-
effectiveHost,
|
|
595
|
-
);
|
|
596
594
|
const current = await readRoleRunStateDisk(runDirectory);
|
|
597
595
|
if (current === undefined) {
|
|
598
596
|
throw new Error("cannot mark running: run state missing");
|
|
@@ -611,6 +609,13 @@ export async function markRunRunning(
|
|
|
611
609
|
...(current.phase === undefined ? {} : { phase: current.phase }),
|
|
612
610
|
...(current.currentCourt === undefined ? {} : { currentCourt: current.currentCourt }),
|
|
613
611
|
});
|
|
612
|
+
await recordEffectiveInvocationModel(
|
|
613
|
+
runDirectory,
|
|
614
|
+
effectiveModel,
|
|
615
|
+
// Authoritative seat projection: absent engine ⇒ null (delete).
|
|
616
|
+
effectiveEngine === undefined ? null : effectiveEngine,
|
|
617
|
+
effectiveHost,
|
|
618
|
+
);
|
|
614
619
|
}
|
|
615
620
|
|
|
616
621
|
/** @deprecated #416: 429-only resumable marker; kept for historical runs. */
|
|
@@ -31,6 +31,8 @@ export type RoleTurnRequestProjectionOptions = {
|
|
|
31
31
|
continuation: RoleTurnRequest["continuation"];
|
|
32
32
|
/** #833 resume-with-message court attempt. */
|
|
33
33
|
courtAttemptId?: string;
|
|
34
|
+
/** Station child role run (#840): omit automatic navigator attendance. */
|
|
35
|
+
stationChild?: boolean;
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
export type AdmittedTurnInvocation = {
|
|
@@ -74,5 +76,6 @@ export function projectRoleTurnRequest(
|
|
|
74
76
|
...(options.courtAttemptId === undefined || options.courtAttemptId.length === 0
|
|
75
77
|
? {}
|
|
76
78
|
: { courtAttemptId: options.courtAttemptId }),
|
|
79
|
+
...(options.stationChild === undefined ? {} : { stationChild: options.stationChild }),
|
|
77
80
|
};
|
|
78
81
|
}
|
|
@@ -16,6 +16,7 @@ import type { CliIo } from "./public-cli/cli-io.ts";
|
|
|
16
16
|
import type { CredentialProviders, EffectiveSeat } from "./public-cli/config.ts";
|
|
17
17
|
import type { PublicCallableRole } from "./public-cli/registry.ts";
|
|
18
18
|
import type { RoleTurnHost } from "./host-contracts.ts";
|
|
19
|
+
import type { HostSelectionFailure, NamedRoleTurnHostAdapter } from "./public-cli/role-turn-host-resolution.ts";
|
|
19
20
|
import type { TerminalResult } from "./public-cli/terminal.ts";
|
|
20
21
|
|
|
21
22
|
/** Env published by the parent activation so nested summons never re-derive root. */
|
|
@@ -29,7 +30,8 @@ export type PublicSummonRole =
|
|
|
29
30
|
| "navigator"
|
|
30
31
|
| "gatekeeper"
|
|
31
32
|
| "judge"
|
|
32
|
-
| "doctor"
|
|
33
|
+
| "doctor"
|
|
34
|
+
| "diarist";
|
|
33
35
|
|
|
34
36
|
export type PublicSummonRequest = {
|
|
35
37
|
readonly role: PublicSummonRole;
|
|
@@ -66,12 +68,19 @@ export type PublicSummonRequest = {
|
|
|
66
68
|
*/
|
|
67
69
|
readonly gateReviewInstruction?: string;
|
|
68
70
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
+
* Pi-adapter inject (tests). Used only as the `pi` row when composing the
|
|
72
|
+
* adapter table — never as an override of the child seat's selected host.
|
|
71
73
|
*/
|
|
72
74
|
readonly roleTurnHost?: RoleTurnHost;
|
|
75
|
+
/**
|
|
76
|
+
* Composition-root adapter table (tests / parent CLI env). Child seat selects
|
|
77
|
+
* from this table. Production leaves unset and composes the default table.
|
|
78
|
+
*/
|
|
79
|
+
readonly hostAdapters?: readonly NamedRoleTurnHostAdapter[];
|
|
73
80
|
/** Offline test inject for deterministic run ids (same face as public CLI). */
|
|
74
81
|
readonly createRunId?: () => string;
|
|
82
|
+
/** Typed ticket number handoff for diarist child run (#840). */
|
|
83
|
+
readonly boundTicketNumber?: number;
|
|
75
84
|
};
|
|
76
85
|
|
|
77
86
|
export type PublicSummonResult = {
|
|
@@ -81,6 +90,8 @@ export type PublicSummonResult = {
|
|
|
81
90
|
readonly runDirectory?: string;
|
|
82
91
|
/** Offline diagnostics from nested CLI (structural rejection text). */
|
|
83
92
|
readonly stderr?: string;
|
|
93
|
+
/** Admitted role invocation from nested run. */
|
|
94
|
+
readonly admitted?: import("./public-cli/invocation.ts").AdmittedRoleInvocation;
|
|
84
95
|
};
|
|
85
96
|
|
|
86
97
|
function createCapturingIo(): { io: CliIo; stderrText(): string } {
|
|
@@ -167,6 +178,13 @@ function projectSeatHost(seat: EffectiveSeat): { host?: string } {
|
|
|
167
178
|
return seat.host === undefined ? {} : { host: seat.host };
|
|
168
179
|
}
|
|
169
180
|
|
|
181
|
+
function hostSelectionFailureFromUnknown(error: unknown): HostSelectionFailure | undefined {
|
|
182
|
+
if (typeof error !== "object" || error === null) return undefined;
|
|
183
|
+
if ((error as { name?: unknown }).name !== "HostSelectionError") return undefined;
|
|
184
|
+
const failure = (error as { failure?: HostSelectionFailure }).failure;
|
|
185
|
+
return failure;
|
|
186
|
+
}
|
|
187
|
+
|
|
170
188
|
async function createSummonEnv(options: {
|
|
171
189
|
readonly role: PublicCallableRole;
|
|
172
190
|
readonly home: string;
|
|
@@ -176,59 +194,30 @@ async function createSummonEnv(options: {
|
|
|
176
194
|
readonly credentials: CredentialProviders;
|
|
177
195
|
readonly seat: EffectiveSeat;
|
|
178
196
|
readonly extraPiArgs?: readonly string[];
|
|
197
|
+
readonly roleTurnHost?: RoleTurnHost;
|
|
198
|
+
readonly hostAdapters?: readonly NamedRoleTurnHostAdapter[];
|
|
179
199
|
}) {
|
|
180
|
-
const [{ piDurablePrincipalAuthority }, { appendPiSessionCustomEntry,
|
|
200
|
+
const [{ piDurablePrincipalAuthority }, { appendPiSessionCustomEntry }, { resolveRoleTurnHost }] =
|
|
181
201
|
await Promise.all([
|
|
182
202
|
import("./pi/durable-principal.ts"),
|
|
183
203
|
import("./pi/role-turn-host.ts"),
|
|
204
|
+
import("./public-cli/role-turn-host-resolution.ts"),
|
|
184
205
|
]);
|
|
185
206
|
const principalAuthority = piDurablePrincipalAuthority;
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
async observeLaunchedRolePackageIdentity(root: string, roleEntry: string) {
|
|
197
|
-
const { observeLaunchedRolePackageIdentity } = await import("./public-cli/invocation.ts");
|
|
198
|
-
return observeLaunchedRolePackageIdentity(root, roleEntry);
|
|
207
|
+
// Same host axis table as public CLI (#617 DK-3 / #675 / #840): child seat
|
|
208
|
+
// selects; injected roleTurnHost is the pi adapter only.
|
|
209
|
+
const roleTurnHost = resolveRoleTurnHost(
|
|
210
|
+
{
|
|
211
|
+
packageRoot: options.packageRoot,
|
|
212
|
+
...(options.roleTurnHost === undefined ? {} : { roleTurnHost: options.roleTurnHost }),
|
|
213
|
+
...(options.hostAdapters === undefined ? {} : { hostAdapters: options.hostAdapters }),
|
|
214
|
+
...(options.extraPiArgs === undefined || options.extraPiArgs.length === 0
|
|
215
|
+
? {}
|
|
216
|
+
: { extraPiArgs: options.extraPiArgs }),
|
|
199
217
|
},
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
packageRoot: options.packageRoot,
|
|
203
|
-
principalAuthority,
|
|
204
|
-
...(options.extraPiArgs === undefined || options.extraPiArgs.length === 0
|
|
205
|
-
? {}
|
|
206
|
-
: { extraPiArgs: options.extraPiArgs }),
|
|
207
|
-
...piRecords,
|
|
208
|
-
});
|
|
209
|
-
// Same host axis table as public CLI (#617 DK-3 / #675 / #729): seat.host is
|
|
210
|
-
// a description-table key; pi stays the in-process default adapter.
|
|
218
|
+
{ role: options.role, seat: options.seat, principalAuthority },
|
|
219
|
+
);
|
|
211
220
|
const hostName = options.seat.host ?? "pi";
|
|
212
|
-
let roleTurnHost = piHost;
|
|
213
|
-
if (hostName !== "pi") {
|
|
214
|
-
const { lookupHostFamily } = await import("./host-descriptions.ts");
|
|
215
|
-
const { createLazyProductionExternalHost } = await import(
|
|
216
|
-
"./public-cli/load-production-external-host.ts"
|
|
217
|
-
);
|
|
218
|
-
// #729 / #645: description tables are the sole host authority — family
|
|
219
|
-
// dispatch (ACP vs headless) is data-driven. Unregistered keys fail closed.
|
|
220
|
-
if (lookupHostFamily(hostName) === undefined) {
|
|
221
|
-
throw new Error(
|
|
222
|
-
`public role summons host unregistered: host=${hostName} seat=${options.role}`,
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
// Same lazy external host face as public CLI (#820) — one builder, two callers.
|
|
226
|
-
roleTurnHost = createLazyProductionExternalHost({
|
|
227
|
-
packageRoot: options.packageRoot,
|
|
228
|
-
hostName,
|
|
229
|
-
principalAuthority,
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
221
|
// #788: host is registered above; only then project host-facing provider.
|
|
233
222
|
const { loadHostProvidersTable, projectHostFacingProvider } = await import(
|
|
234
223
|
"./public-cli/host-providers.ts"
|
|
@@ -281,8 +270,9 @@ export async function summonPublicRole(
|
|
|
281
270
|
// Nested summons resolve host on the officer seat only (flag>seat>default pi).
|
|
282
271
|
// Parent run host is not an override channel (#821 / ADR 0082 host-flag-two-channels).
|
|
283
272
|
const seat = resolveEffectiveSeat(config, options.role, credentials);
|
|
284
|
-
|
|
285
|
-
|
|
273
|
+
let summonEnv;
|
|
274
|
+
try {
|
|
275
|
+
summonEnv = await createSummonEnv({
|
|
286
276
|
role: options.role,
|
|
287
277
|
home,
|
|
288
278
|
agentDir,
|
|
@@ -291,7 +281,21 @@ export async function summonPublicRole(
|
|
|
291
281
|
credentials,
|
|
292
282
|
seat,
|
|
293
283
|
...(options.extraPiArgs === undefined ? {} : { extraPiArgs: options.extraPiArgs }),
|
|
294
|
-
|
|
284
|
+
...(options.roleTurnHost === undefined ? {} : { roleTurnHost: options.roleTurnHost }),
|
|
285
|
+
...(options.hostAdapters === undefined ? {} : { hostAdapters: options.hostAdapters }),
|
|
286
|
+
});
|
|
287
|
+
} catch (error) {
|
|
288
|
+
const failure = hostSelectionFailureFromUnknown(error);
|
|
289
|
+
if (failure !== undefined) {
|
|
290
|
+
const { formatHostSelectionFailure } = await import("./public-cli/role-turn-host-resolution.ts");
|
|
291
|
+
return { exitCode: 1, stderr: formatHostSelectionFailure(failure) };
|
|
292
|
+
}
|
|
293
|
+
throw error;
|
|
294
|
+
}
|
|
295
|
+
const env = {
|
|
296
|
+
...summonEnv,
|
|
297
|
+
// Station child role run (#840): omit automatic navigator attendance.
|
|
298
|
+
stationChild: true,
|
|
295
299
|
// Host config passthrough only — same face as public CLI (#422 / #675).
|
|
296
300
|
...(config.autoResumeLimit === undefined
|
|
297
301
|
? {}
|
|
@@ -304,8 +308,10 @@ export async function summonPublicRole(
|
|
|
304
308
|
...(options.gateReviewInstruction === undefined
|
|
305
309
|
? {}
|
|
306
310
|
: { gateReviewInstruction: options.gateReviewInstruction }),
|
|
307
|
-
|
|
308
|
-
|
|
311
|
+
...(options.boundTicketNumber === undefined
|
|
312
|
+
? {}
|
|
313
|
+
: { boundTicketNumber: options.boundTicketNumber }),
|
|
314
|
+
// createRunId is the only remaining env overlay — host is seat-selected above.
|
|
309
315
|
...(options.createRunId === undefined ? {} : { createRunId: options.createRunId }),
|
|
310
316
|
};
|
|
311
317
|
const captured = options.io === undefined ? createCapturingIo() : undefined;
|
|
@@ -391,6 +397,14 @@ export async function summonPublicRole(
|
|
|
391
397
|
result = await runPublicDoctor(options.argv, env, io, parseDoctorArgv);
|
|
392
398
|
break;
|
|
393
399
|
}
|
|
400
|
+
case "diarist": {
|
|
401
|
+
const [{ runPublicDiarist }, { parseDiaristArgv }] = await Promise.all([
|
|
402
|
+
import("./public-cli/diarist-run.ts"),
|
|
403
|
+
import("./public-cli/invocation.ts"),
|
|
404
|
+
]);
|
|
405
|
+
result = await runPublicDiarist(options.argv, env, io, parseDiaristArgv);
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
394
408
|
}
|
|
395
409
|
|
|
396
410
|
const stderr = captured?.stderrText();
|
|
@@ -398,6 +412,7 @@ export async function summonPublicRole(
|
|
|
398
412
|
return {
|
|
399
413
|
exitCode: result.exitCode,
|
|
400
414
|
...(result.terminal === undefined ? {} : { terminal: result.terminal }),
|
|
415
|
+
...(result.admitted === undefined ? {} : { admitted: result.admitted as import("./public-cli/invocation.ts").AdmittedRoleInvocation }),
|
|
401
416
|
...(typeof runDirectory === "string" && runDirectory.trim() !== ""
|
|
402
417
|
? { runDirectory }
|
|
403
418
|
: {}),
|
|
@@ -426,8 +441,9 @@ export async function summonGateOfficer(options: {
|
|
|
426
441
|
* when reask is absent (#786). Never a path pointer substitute.
|
|
427
442
|
*/
|
|
428
443
|
readonly submission?: unknown;
|
|
429
|
-
/**
|
|
444
|
+
/** Pi-adapter inject — forwarded to summonPublicRole (not a parent-host override). */
|
|
430
445
|
readonly roleTurnHost?: RoleTurnHost;
|
|
446
|
+
readonly hostAdapters?: readonly NamedRoleTurnHostAdapter[];
|
|
431
447
|
/** Offline test inject — forwarded to summonPublicRole. */
|
|
432
448
|
readonly createRunId?: () => string;
|
|
433
449
|
}): Promise<PublicSummonResult> {
|
|
@@ -459,6 +475,7 @@ export async function summonGateOfficer(options: {
|
|
|
459
475
|
? {}
|
|
460
476
|
: { gateReviewInstruction }),
|
|
461
477
|
...(options.roleTurnHost === undefined ? {} : { roleTurnHost: options.roleTurnHost }),
|
|
478
|
+
...(options.hostAdapters === undefined ? {} : { hostAdapters: options.hostAdapters }),
|
|
462
479
|
...(options.createRunId === undefined ? {} : { createRunId: options.createRunId }),
|
|
463
480
|
} as const;
|
|
464
481
|
if (options.officer === "notary") {
|
|
@@ -10,6 +10,9 @@ import { packagedRoleInputFlag, packagedRolePhaseFlag } from "./packaged-role-re
|
|
|
10
10
|
export function projectActivationFlags(request: RoleTurnRequest): Map<string, boolean | string> {
|
|
11
11
|
const activation = request.activation;
|
|
12
12
|
const flags = new Map<string, boolean | string>([["ak-role", activation.role]]);
|
|
13
|
+
if (request.stationChild === true) {
|
|
14
|
+
flags.set("ak-station-child", true);
|
|
15
|
+
}
|
|
13
16
|
const inputFlag = packagedRoleInputFlag(activation.role);
|
|
14
17
|
const phaseFlag = packagedRolePhaseFlag(activation.role);
|
|
15
18
|
if ("phase" in activation && phaseFlag !== undefined) flags.set(phaseFlag, activation.phase);
|
package/src/role-runtime.ts
CHANGED
|
@@ -175,6 +175,14 @@ const GLEANER_LEFT_TRANSPORT_FLAGS = Object.freeze([
|
|
|
175
175
|
}),
|
|
176
176
|
] as const);
|
|
177
177
|
|
|
178
|
+
export const STATION_CHILD_FLAG = Object.freeze({
|
|
179
|
+
name: "ak-station-child",
|
|
180
|
+
definition: Object.freeze({
|
|
181
|
+
description: "Station-child role run (omit automatic navigator attendance; #840)",
|
|
182
|
+
type: "boolean" as const,
|
|
183
|
+
}),
|
|
184
|
+
} as const);
|
|
185
|
+
|
|
178
186
|
/**
|
|
179
187
|
* Decode private transport flags into frozen admitted inputs.
|
|
180
188
|
* Envelope-owned; necessary JSON decode only (public --authority-ref owns grammar).
|
|
@@ -1152,6 +1160,8 @@ export function createRoleRuntimeExtension(
|
|
|
1152
1160
|
for (const flag of COLLECTOR_TRANSPORT_FLAGS) {
|
|
1153
1161
|
roleHost.registerFlag(flag.name, flag.definition);
|
|
1154
1162
|
}
|
|
1163
|
+
// Station-child identity (#840): omit navigator attendance. One flag.
|
|
1164
|
+
roleHost.registerFlag(STATION_CHILD_FLAG.name, STATION_CHILD_FLAG.definition);
|
|
1155
1165
|
|
|
1156
1166
|
let admitted = false;
|
|
1157
1167
|
let selectedRole: string | undefined;
|
|
@@ -2003,13 +2013,15 @@ export function createRoleRuntimeExtension(
|
|
|
2003
2013
|
const ledgerHome = resolveActivationLedgerHomeForPath(sessionFile);
|
|
2004
2014
|
const session = durableSessionPointer(ctx.sessionManager);
|
|
2005
2015
|
|
|
2006
|
-
//
|
|
2007
|
-
//
|
|
2016
|
+
// Station children (court diarist, inner-gate summons) omit navigator sidecar (#840).
|
|
2017
|
+
// Top-level public entry legs still attend automatically.
|
|
2008
2018
|
// Navigator seat never re-attaches itself — prepare turns already ARE the navigator
|
|
2009
2019
|
// public activation (prevents summonPublicRole navigator ↔ attendance recursion).
|
|
2020
|
+
const isStationChild = roleHost.getFlag(STATION_CHILD_FLAG.name) === true;
|
|
2010
2021
|
if (
|
|
2011
2022
|
dependencies.createNavigatorAttendance !== undefined
|
|
2012
2023
|
&& entry.role !== "navigator"
|
|
2024
|
+
&& !isStationChild
|
|
2013
2025
|
) {
|
|
2014
2026
|
let work: NavigatorWorkContext;
|
|
2015
2027
|
let contextError: unknown;
|
package/src/session-identity.ts
CHANGED
|
@@ -2,6 +2,11 @@ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
|
|
4
4
|
import type { DurablePrincipal, DurablePrincipalAuthority } from "./host-contracts.ts";
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_ROLE_TURN_HOST,
|
|
7
|
+
lookupHeadlessHostDescription,
|
|
8
|
+
lookupHostDescription,
|
|
9
|
+
} from "./host-descriptions.ts";
|
|
5
10
|
import type { SessionIdentityAuthority } from "./prepared-role-turn.ts";
|
|
6
11
|
|
|
7
12
|
/** Durable session binding stored beside the host-owned session principal. */
|
|
@@ -36,3 +41,32 @@ export function createSessionIdentityAuthority(
|
|
|
36
41
|
},
|
|
37
42
|
};
|
|
38
43
|
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Host-aware resumable-session probe (#840 P1). The shared auto-resume loop
|
|
47
|
+
* (public-cli/auto-resume.ts) asks one question — "can this principal's turn
|
|
48
|
+
* still be resumed" — without itself knowing which concrete host executed it.
|
|
49
|
+
* Pi's own session.jsonl transcript (DurablePrincipalAuthority#isAvailable)
|
|
50
|
+
* answers that only for the pi host; ACP and headless hosts persist their
|
|
51
|
+
* resumable native session id under their own session-identity binding file
|
|
52
|
+
* instead (#729 / #731 / #645) and never write session.jsonl, so the plain
|
|
53
|
+
* pi check always reports them unavailable after one attempt. One lookup per
|
|
54
|
+
* registered host family — no per-host-name branch, no per-seat whitelist.
|
|
55
|
+
*/
|
|
56
|
+
export function resolveHostAwareSessionAvailability(
|
|
57
|
+
host: string | undefined,
|
|
58
|
+
principalAuthority: DurablePrincipalAuthority,
|
|
59
|
+
): (principal: DurablePrincipal) => Promise<boolean> {
|
|
60
|
+
if (host === undefined || host === DEFAULT_ROLE_TURN_HOST) {
|
|
61
|
+
return (principal) => principalAuthority.isAvailable(principal);
|
|
62
|
+
}
|
|
63
|
+
const description = lookupHostDescription(host) ?? lookupHeadlessHostDescription(host);
|
|
64
|
+
if (description === undefined) {
|
|
65
|
+
// Host selection already fails closed on an unregistered name (#510) long
|
|
66
|
+
// before a turn dispatches; mirror the same fail-closed default here
|
|
67
|
+
// rather than guessing at pi's binding for a name pi does not own.
|
|
68
|
+
return () => Promise.resolve(false);
|
|
69
|
+
}
|
|
70
|
+
const sessionIdentity = createSessionIdentityAuthority(principalAuthority, description.sessionBindingFile);
|
|
71
|
+
return async (principal) => (await sessionIdentity.load(principal)) !== undefined;
|
|
72
|
+
}
|