@akagilnc/pi-workflow-roles 0.1.3837 → 0.1.3873
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 +24606 -24532
- package/dist/engine-detour-tool.js +2 -2
- package/dist/engine-detour.js +23 -7
- package/dist/headless-host/production-host.js +4197 -4139
- package/dist/host-descriptions.js +5 -2
- package/dist/pi/role-turn-host.js +20 -92
- package/dist/public-cli/load-production-acp-host.js +1 -1
- package/dist/public-cli/load-production-external-host.js +10 -0
- package/dist/public-cli/load-production-headless-host.js +1 -1
- package/dist/public-cli/main.js +95 -102
- package/dist/public-role-summons.js +7 -49
- package/dist/reviewer-agent.js +1 -0
- package/dist/reviewer-child-executor.js +3 -2
- package/dist/role-activation-flags.js +50 -0
- package/dist/role-runtime.js +8 -3
- package/package.json +1 -1
- package/scripts/build-package.mjs +2 -2
- package/src/acp-host/production-host.ts +31 -94
- package/src/acp-host/role-turn-host.ts +178 -263
- package/src/diagnostic-tail.ts +18 -0
- package/src/engine-detour-tool.ts +5 -4
- package/src/engine-detour.ts +44 -9
- package/src/external-host-turn-loop.ts +172 -0
- package/src/headless-host/production-host.ts +10 -7
- package/src/headless-host/role-turn-host.ts +271 -263
- package/src/host-descriptions.ts +5 -2
- package/src/host-session-record.ts +41 -0
- package/src/pi/role-turn-host.ts +18 -93
- package/src/prepared-role-turn.ts +68 -0
- package/src/public-cli/cli.ts +11 -20
- package/src/public-cli/load-production-acp-host.ts +2 -1
- package/src/public-cli/load-production-external-host.ts +17 -0
- package/src/public-cli/load-production-headless-host.ts +2 -1
- package/src/public-role-summons.ts +12 -69
- package/src/reviewer-agent.ts +12 -1
- package/src/reviewer-child-executor.ts +10 -2
- package/src/role-activation-flags.ts +53 -0
- package/src/{acp-host/role-envelope.ts → role-envelope.ts} +28 -84
- package/src/role-runtime-dependencies.ts +91 -0
- package/src/role-runtime.ts +14 -4
- package/src/{acp-host/session-identity.ts → session-identity.ts} +6 -6
- /package/src/{acp-host/mcp-relay.mjs → mcp-relay.mjs} +0 -0
package/src/host-descriptions.ts
CHANGED
|
@@ -68,6 +68,7 @@ export const HOST_DESCRIPTIONS: Readonly<Record<string, AcpHostDescription>> = O
|
|
|
68
68
|
/**
|
|
69
69
|
* Headless CLI family (#645). Claude is the first row; codex (#646) adds another.
|
|
70
70
|
* fixedArgs: print mode, isolation without `--bare` (OAuth stays), full permissions.
|
|
71
|
+
* stream-json + verbose: live host events for sitian records (#811); result is last line.
|
|
71
72
|
* `--setting-sources` empty = load no user/project/local CLAUDE.md/hooks/skills
|
|
72
73
|
* (role envelope is delivered via `--system-prompt` wholesale replace).
|
|
73
74
|
* `--strict-mcp-config` with no `--mcp-config` drops operator MCP + claude.ai connectors.
|
|
@@ -77,8 +78,10 @@ export const HEADLESS_HOST_DESCRIPTIONS: Readonly<Record<string, HeadlessHostDes
|
|
|
77
78
|
binaryFromHome: Object.freeze([".local", "bin", "claude"]),
|
|
78
79
|
sessionBindingFile: "claude-headless-session.json",
|
|
79
80
|
fixedArgs: Object.freeze([
|
|
80
|
-
//
|
|
81
|
-
"--output-format", "json",
|
|
81
|
+
// Live NDJSON events → sitian host-session records (#811); last line is the result receipt.
|
|
82
|
+
"--output-format", "stream-json",
|
|
83
|
+
// Intermediate assistant/tool/system events require verbose with stream-json.
|
|
84
|
+
"--verbose",
|
|
82
85
|
"--permission-mode", "bypassPermissions",
|
|
83
86
|
// Empty sources: no user/project/local operator surface (envelope owns materials).
|
|
84
87
|
"--setting-sources", "",
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-pi host turn events → sitian sole entry (ADR 0065 / 0077 / #811).
|
|
3
|
+
* Adapters capture host-native structured events; this helper only packages
|
|
4
|
+
* the common sitianReport call. session.jsonl stays header-only (#617 DK-4).
|
|
5
|
+
* Write failures propagate (SitianInfrastructureError) — not best-effort.
|
|
6
|
+
*/
|
|
7
|
+
import { sitianReport } from "./sitian-facade.ts";
|
|
8
|
+
|
|
9
|
+
/** Volume category under `<run>/session/<kind>/records.jsonl`. */
|
|
10
|
+
export const HOST_SESSION_RECORD_KIND = "host-session" as const;
|
|
11
|
+
|
|
12
|
+
function eventField(event: unknown, key: string): string | undefined {
|
|
13
|
+
if (typeof event !== "object" || event === null || Array.isArray(event)) return undefined;
|
|
14
|
+
const value = (event as Record<string, unknown>)[key];
|
|
15
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Live write of one host-emitted structured event. Throws on sitian persistence failure. */
|
|
19
|
+
export function reportHostSessionEvent(input: {
|
|
20
|
+
readonly host: string;
|
|
21
|
+
readonly cwd: string;
|
|
22
|
+
readonly sessionParent: string;
|
|
23
|
+
readonly source: string;
|
|
24
|
+
readonly event: unknown;
|
|
25
|
+
readonly identity?: string;
|
|
26
|
+
readonly timestamp?: string;
|
|
27
|
+
}): void {
|
|
28
|
+
const identity = input.identity ?? eventField(input.event, "uuid");
|
|
29
|
+
const timestamp = input.timestamp ?? eventField(input.event, "timestamp");
|
|
30
|
+
sitianReport({
|
|
31
|
+
level: "event",
|
|
32
|
+
kind: HOST_SESSION_RECORD_KIND,
|
|
33
|
+
host: input.host,
|
|
34
|
+
cwd: input.cwd,
|
|
35
|
+
sessionParent: input.sessionParent,
|
|
36
|
+
source: input.source,
|
|
37
|
+
payload: input.event,
|
|
38
|
+
...(identity === undefined ? {} : { identity }),
|
|
39
|
+
...(timestamp === undefined ? {} : { timestamp }),
|
|
40
|
+
});
|
|
41
|
+
}
|
package/src/pi/role-turn-host.ts
CHANGED
|
@@ -15,7 +15,6 @@ import type {
|
|
|
15
15
|
DurablePrincipal,
|
|
16
16
|
DurablePrincipalAuthority,
|
|
17
17
|
MethodBinding,
|
|
18
|
-
RoleTurnActivation,
|
|
19
18
|
RoleTurnHost,
|
|
20
19
|
RoleTurnKnownFailure,
|
|
21
20
|
RoleTurnModelConfig,
|
|
@@ -24,6 +23,7 @@ import type {
|
|
|
24
23
|
} from "../host-contracts.ts";
|
|
25
24
|
import { ExplicitInternalActivationError } from "../host-contracts.ts";
|
|
26
25
|
import { applyEngineChildEnv } from "../engine-detour.ts";
|
|
26
|
+
import { projectActivationFlags } from "../role-activation-flags.ts";
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
/** Package-relative Internal role entrypoint (ADR 0052; same path as public-cli registry). */
|
|
@@ -66,95 +66,18 @@ function buildSeatModelCliArgs(model: RoleTurnModelConfig | undefined): string[]
|
|
|
66
66
|
];
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
"--ak-coder-task",
|
|
80
|
-
activation.taskPath,
|
|
81
|
-
];
|
|
82
|
-
case "fixer":
|
|
83
|
-
return [
|
|
84
|
-
"--ak-role",
|
|
85
|
-
"fixer",
|
|
86
|
-
"--ak-fixer-phase",
|
|
87
|
-
activation.phase,
|
|
88
|
-
"--ak-fix-packet",
|
|
89
|
-
activation.packetPath,
|
|
90
|
-
...(activation.prerequisitesPath === undefined
|
|
91
|
-
? []
|
|
92
|
-
: ["--ak-fixer-prerequisites", activation.prerequisitesPath]),
|
|
93
|
-
];
|
|
94
|
-
case "reviewer":
|
|
95
|
-
return [
|
|
96
|
-
"--ak-role",
|
|
97
|
-
"reviewer",
|
|
98
|
-
"--ak-review-base",
|
|
99
|
-
activation.baseRevision,
|
|
100
|
-
...(activation.authorityRefs.length === 0
|
|
101
|
-
? []
|
|
102
|
-
: ["--ak-review-authority-refs", JSON.stringify([...activation.authorityRefs])]),
|
|
103
|
-
...(activation.ticketNumber === undefined
|
|
104
|
-
? []
|
|
105
|
-
: ["--ak-review-ticket-number", String(activation.ticketNumber)]),
|
|
106
|
-
];
|
|
107
|
-
case "merger":
|
|
108
|
-
return ["--ak-role", "merger", "--ak-merger-input", activation.inputPath];
|
|
109
|
-
case "collector":
|
|
110
|
-
return [
|
|
111
|
-
"--ak-role",
|
|
112
|
-
"collector",
|
|
113
|
-
"--ak-collector-repo",
|
|
114
|
-
activation.repo,
|
|
115
|
-
...(activation.pr === undefined ? [] : ["--ak-collector-pr", activation.pr]),
|
|
116
|
-
...(activation.requestManifestPath === undefined
|
|
117
|
-
? []
|
|
118
|
-
: ["--ak-collector-request-manifest", activation.requestManifestPath]),
|
|
119
|
-
...(activation.waitMs === undefined ? [] : ["--ak-collector-wait-ms", activation.waitMs]),
|
|
120
|
-
];
|
|
121
|
-
case "doctor":
|
|
122
|
-
return ["--ak-role", "doctor", "--ak-doctor-case", activation.casePath];
|
|
123
|
-
case "notary":
|
|
124
|
-
return [
|
|
125
|
-
"--ak-role",
|
|
126
|
-
"notary",
|
|
127
|
-
"--ak-notary-source-run",
|
|
128
|
-
activation.sourceRun,
|
|
129
|
-
...(activation.ticketNumber === undefined
|
|
130
|
-
? []
|
|
131
|
-
: ["--ak-notary-ticket-number", String(activation.ticketNumber)]),
|
|
132
|
-
];
|
|
133
|
-
case "countersign":
|
|
134
|
-
// ticketNumber rides activation/admission/invocation only (#632: no private flag).
|
|
135
|
-
return ["--ak-role", "countersign"];
|
|
136
|
-
case "gleaner-left":
|
|
137
|
-
return [
|
|
138
|
-
"--ak-role",
|
|
139
|
-
"gleaner-left",
|
|
140
|
-
"--ak-gleaner-left-base",
|
|
141
|
-
activation.baseRevision,
|
|
142
|
-
];
|
|
143
|
-
case "inspector":
|
|
144
|
-
return ["--ak-role", "inspector"];
|
|
145
|
-
case "gatekeeper":
|
|
146
|
-
return ["--ak-role", "gatekeeper"];
|
|
147
|
-
case "navigator":
|
|
148
|
-
return ["--ak-role", "navigator"];
|
|
149
|
-
case "auditor":
|
|
150
|
-
return ["--ak-role", "auditor"];
|
|
151
|
-
case "diarist":
|
|
152
|
-
return ["--ak-role", "diarist"];
|
|
153
|
-
default: {
|
|
154
|
-
const _exhaustive: never = activation;
|
|
155
|
-
return _exhaustive;
|
|
156
|
-
}
|
|
69
|
+
/**
|
|
70
|
+
* Pi last hop only: shared envelope flags → controlled-session argv pairs (#819).
|
|
71
|
+
* Flag membership/values stay in projectActivationFlags (middle layer).
|
|
72
|
+
*/
|
|
73
|
+
function activationFlagsToPiArgv(flags: ReadonlyMap<string, boolean | string>): string[] {
|
|
74
|
+
const args: string[] = [];
|
|
75
|
+
for (const [name, value] of flags) {
|
|
76
|
+
if (value === false) continue;
|
|
77
|
+
args.push(`--${name}`);
|
|
78
|
+
if (value !== true) args.push(String(value));
|
|
157
79
|
}
|
|
80
|
+
return args;
|
|
158
81
|
}
|
|
159
82
|
|
|
160
83
|
function buildMethodArgs(methods: readonly MethodBinding[]): string[] {
|
|
@@ -193,9 +116,10 @@ export function applyPiNativeSkillInvocation(
|
|
|
193
116
|
}
|
|
194
117
|
|
|
195
118
|
/**
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
119
|
+
* Pi last-hop argv after `--no-extensions -e entry` (#819).
|
|
120
|
+
* Activation flag membership comes from middle-layer projectActivationFlags;
|
|
121
|
+
* this function only renders session coords, controlled constants, and pairs.
|
|
122
|
+
* Single forced method → Pi-native `/skill:` on the argv prompt (#822).
|
|
199
123
|
*/
|
|
200
124
|
export function buildPiTurnExtraArgs(
|
|
201
125
|
request: RoleTurnRequest,
|
|
@@ -222,7 +146,8 @@ export function buildPiTurnExtraArgs(
|
|
|
222
146
|
"--session-dir",
|
|
223
147
|
sessionDirectory,
|
|
224
148
|
...extraPiArgs,
|
|
225
|
-
|
|
149
|
+
// Envelope assembly = projectActivationFlags; pi only renders argv pairs.
|
|
150
|
+
...activationFlagsToPiArgv(projectActivationFlags(request)),
|
|
226
151
|
"--mode",
|
|
227
152
|
"json",
|
|
228
153
|
...buildSeatModelCliArgs(request.model),
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { RoleTurnKnownFailure, RoleTurnRequest } from "./host-contracts.ts";
|
|
2
|
+
import { renderAgentStartMaterials } from "./agent-start-materials.ts";
|
|
3
|
+
|
|
4
|
+
/** The shared envelope, prepared before session/new (systemPrompt delivery) and
|
|
5
|
+
* able to observe the host's real builtin tool surface once it arrives post-session. */
|
|
6
|
+
export type PreparedRoleTurn = Readonly<{
|
|
7
|
+
mcpServers: readonly Readonly<Record<string, unknown>>[];
|
|
8
|
+
/**
|
|
9
|
+
* Structured system-prompt authority. `body` is the provider-facing prompt
|
|
10
|
+
* bytes; `materials` are typed agent-start reading materials (e.g. Notary
|
|
11
|
+
* session bound) that the adapter folds into the override at the provider
|
|
12
|
+
* boundary. This structure is the authoritative production input of the
|
|
13
|
+
* send path — never a test-only parallel face.
|
|
14
|
+
*/
|
|
15
|
+
systemPrompt: { readonly body: string; readonly materials: readonly unknown[] };
|
|
16
|
+
/** Effective user prompt after host-side input transform (canonical Skill invocation). */
|
|
17
|
+
prompt: string;
|
|
18
|
+
/**
|
|
19
|
+
* Host abort signal armed only by typed infrastructure failure (envelope
|
|
20
|
+
* rememberInfrastructureFailure / non-correctable MCP catch). Lawful
|
|
21
|
+
* context.abort() (seal / non-sole) does not arm it. executeTurn races
|
|
22
|
+
* session/prompt against this so infra declarations terminate even when
|
|
23
|
+
* ACP never resolves (#593).
|
|
24
|
+
*/
|
|
25
|
+
abortSignal?: AbortSignal;
|
|
26
|
+
/** Shared ledger consumes the complete ACP round after session/prompt resolves. */
|
|
27
|
+
closeRound(): Promise<
|
|
28
|
+
| { readonly accepted: true }
|
|
29
|
+
| {
|
|
30
|
+
readonly accepted: false;
|
|
31
|
+
/** Shared envelope provides officer/correctable text; adapters only deliver it (#813). */
|
|
32
|
+
readonly retry: {
|
|
33
|
+
readonly code: string;
|
|
34
|
+
readonly toolCallIds: readonly string[];
|
|
35
|
+
readonly message: string;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
| { readonly accepted: false; readonly failure: RoleTurnKnownFailure }
|
|
39
|
+
>;
|
|
40
|
+
dispose?(): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Headless CLI family (#645): role terminating-tool schema for host-native
|
|
43
|
+
* `--json-schema`. Present for every prepared turn; ACP ignores it.
|
|
44
|
+
*/
|
|
45
|
+
jsonSchema: Readonly<Record<string, unknown>>;
|
|
46
|
+
/** Terminating tool name whose schema is `jsonSchema`. */
|
|
47
|
+
terminatingToolName: string;
|
|
48
|
+
/**
|
|
49
|
+
* Headless CLI family: feed host-native `structured_output` through the same
|
|
50
|
+
* terminating-tool path the MCP relay uses (ledger + gates). ACP ignores it.
|
|
51
|
+
*/
|
|
52
|
+
ingestStructuredOutput(params: unknown): Promise<void>;
|
|
53
|
+
}>;
|
|
54
|
+
|
|
55
|
+
/** Fold structured system-prompt authority into the provider-visible ACP override. */
|
|
56
|
+
export function renderSystemPromptOverride(authority: {
|
|
57
|
+
readonly body: string;
|
|
58
|
+
readonly materials: readonly unknown[];
|
|
59
|
+
}): string {
|
|
60
|
+
return renderAgentStartMaterials(authority.body, authority.materials);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type SessionIdentityAuthority = Readonly<{
|
|
64
|
+
load(principal: RoleTurnRequest["principal"]): Promise<string | undefined>;
|
|
65
|
+
bind(principal: RoleTurnRequest["principal"], sessionId: string): Promise<void>;
|
|
66
|
+
/** Durable principal session path for layout ownership / isAvailable — not a rebuild source (#617 DK-4). */
|
|
67
|
+
resolveSessionFile(principal: RoleTurnRequest["principal"]): string;
|
|
68
|
+
}>;
|
package/src/public-cli/cli.ts
CHANGED
|
@@ -43,9 +43,9 @@ import { seatModelOnly } from "./registry.ts";
|
|
|
43
43
|
import { CliUsageError } from "./cli-errors.ts";
|
|
44
44
|
import type { CliIo } from "./cli-io.ts";
|
|
45
45
|
import type { PostAdmissionEnv } from "./post-admission.ts";
|
|
46
|
-
import type { RoleTurnHost
|
|
46
|
+
import type { RoleTurnHost } from "../host-contracts.ts";
|
|
47
47
|
import { packagedExternalHostNames } from "../host-descriptions.ts";
|
|
48
|
-
import {
|
|
48
|
+
import { createLazyProductionExternalHost } from "./load-production-external-host.ts";
|
|
49
49
|
import {
|
|
50
50
|
createPiRoleTurnHost,
|
|
51
51
|
appendPiSessionCustomEntry,
|
|
@@ -335,29 +335,20 @@ function resolveRoleTurnHost(
|
|
|
335
335
|
recordLaunchedRolePackageIdentity,
|
|
336
336
|
observeLaunchedRolePackageIdentity,
|
|
337
337
|
});
|
|
338
|
-
// Composition-root adapter table: pi (in-process default) + one
|
|
338
|
+
// Composition-root adapter table: pi (in-process default) + one lazy external host per description-table key (#820).
|
|
339
339
|
const adapters: readonly NamedRoleTurnHostAdapter[] = env.hostAdapters ?? [
|
|
340
340
|
{ name: "pi", create: () => ({ ok: true as const, host: piHost }) },
|
|
341
341
|
...packagedExternalHostNames().map((name) => ({
|
|
342
342
|
name,
|
|
343
|
-
create: () => {
|
|
343
|
+
create: () => ({
|
|
344
|
+
ok: true as const,
|
|
344
345
|
// Factory loads outside the public bin static graph (ADR 0052 peer-free discovery).
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
create({
|
|
352
|
-
packageRoot: env.packageRoot,
|
|
353
|
-
principalAuthority: options.principalAuthority,
|
|
354
|
-
}),
|
|
355
|
-
);
|
|
356
|
-
return (await hostPromise).executeTurn(request);
|
|
357
|
-
},
|
|
358
|
-
},
|
|
359
|
-
};
|
|
360
|
-
},
|
|
346
|
+
host: createLazyProductionExternalHost({
|
|
347
|
+
packageRoot: env.packageRoot,
|
|
348
|
+
hostName: name,
|
|
349
|
+
principalAuthority: options.principalAuthority,
|
|
350
|
+
}),
|
|
351
|
+
}),
|
|
361
352
|
})),
|
|
362
353
|
];
|
|
363
354
|
const hostName = options.seat.host;
|
|
@@ -25,6 +25,7 @@ type GenericAcpHostFactory = (options: {
|
|
|
25
25
|
packageRoot: string;
|
|
26
26
|
principalAuthority: DurablePrincipalAuthority;
|
|
27
27
|
description: AcpHostDescription;
|
|
28
|
+
hostName: string;
|
|
28
29
|
}) => RoleTurnHost;
|
|
29
30
|
|
|
30
31
|
/**
|
|
@@ -48,5 +49,5 @@ export async function loadProductionAcpHostFactory(
|
|
|
48
49
|
createProductionAcpRoleTurnHost: GenericAcpHostFactory;
|
|
49
50
|
};
|
|
50
51
|
const create = mod.createProductionAcpRoleTurnHost;
|
|
51
|
-
return (options) => create({ ...options, description });
|
|
52
|
+
return (options) => create({ ...options, description, hostName: host });
|
|
52
53
|
}
|
|
@@ -27,3 +27,20 @@ export async function loadProductionExternalHostFactory(
|
|
|
27
27
|
}
|
|
28
28
|
throw new Error(`unregistered host: ${host}`);
|
|
29
29
|
}
|
|
30
|
+
|
|
31
|
+
/** Lazy production external host — sole builder for CLI adapter table + public summons (#820). */
|
|
32
|
+
export function createLazyProductionExternalHost(options: {
|
|
33
|
+
readonly packageRoot: string;
|
|
34
|
+
readonly hostName: string;
|
|
35
|
+
readonly principalAuthority: DurablePrincipalAuthority;
|
|
36
|
+
}): RoleTurnHost {
|
|
37
|
+
let hostPromise: Promise<RoleTurnHost> | undefined;
|
|
38
|
+
return {
|
|
39
|
+
executeTurn: async (request) => {
|
|
40
|
+
hostPromise ??= loadProductionExternalHostFactory(options.packageRoot, options.hostName).then((create) =>
|
|
41
|
+
create({ packageRoot: options.packageRoot, principalAuthority: options.principalAuthority }),
|
|
42
|
+
);
|
|
43
|
+
return (await hostPromise).executeTurn(request);
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -22,6 +22,7 @@ type GenericHeadlessHostFactory = (options: {
|
|
|
22
22
|
packageRoot: string;
|
|
23
23
|
principalAuthority: DurablePrincipalAuthority;
|
|
24
24
|
description: HeadlessHostDescription;
|
|
25
|
+
hostName: string;
|
|
25
26
|
}) => RoleTurnHost;
|
|
26
27
|
|
|
27
28
|
/**
|
|
@@ -44,5 +45,5 @@ export async function loadProductionHeadlessHostFactory(
|
|
|
44
45
|
createProductionHeadlessRoleTurnHost: GenericHeadlessHostFactory;
|
|
45
46
|
};
|
|
46
47
|
const create = mod.createProductionHeadlessRoleTurnHost;
|
|
47
|
-
return (options) => create({ ...options, description });
|
|
48
|
+
return (options) => create({ ...options, description, hostName: host });
|
|
48
49
|
}
|
|
@@ -65,12 +65,6 @@ export type PublicSummonRequest = {
|
|
|
65
65
|
* Never folded into argv / parent-run lookup keys (ADR 0079 卷宗指针 stays pure).
|
|
66
66
|
*/
|
|
67
67
|
readonly gateReviewInstruction?: string;
|
|
68
|
-
/**
|
|
69
|
-
* Parent-invocation host axis (#617 / #645). Nested institutional legs inherit
|
|
70
|
-
* the live parent `--host` so a single public entry does not require mutating
|
|
71
|
-
* persistent seat host. Seat model/thinking stay on the officer seat table.
|
|
72
|
-
*/
|
|
73
|
-
readonly host?: string;
|
|
74
68
|
/**
|
|
75
69
|
* Offline test inject — same face as public CLI env.roleTurnHost. Production
|
|
76
70
|
* summons leave this unset and use the seat-resolved host.
|
|
@@ -218,7 +212,7 @@ async function createSummonEnv(options: {
|
|
|
218
212
|
let roleTurnHost = piHost;
|
|
219
213
|
if (hostName !== "pi") {
|
|
220
214
|
const { lookupHostFamily } = await import("./host-descriptions.ts");
|
|
221
|
-
const {
|
|
215
|
+
const { createLazyProductionExternalHost } = await import(
|
|
222
216
|
"./public-cli/load-production-external-host.ts"
|
|
223
217
|
);
|
|
224
218
|
// #729 / #645: description tables are the sole host authority — family
|
|
@@ -228,19 +222,12 @@ async function createSummonEnv(options: {
|
|
|
228
222
|
`public role summons host unregistered: host=${hostName} seat=${options.role}`,
|
|
229
223
|
);
|
|
230
224
|
}
|
|
231
|
-
|
|
232
|
-
roleTurnHost = {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
packageRoot: options.packageRoot,
|
|
238
|
-
principalAuthority,
|
|
239
|
-
}),
|
|
240
|
-
);
|
|
241
|
-
return (await hostPromise).executeTurn(request);
|
|
242
|
-
},
|
|
243
|
-
};
|
|
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
|
+
});
|
|
244
231
|
}
|
|
245
232
|
// #788: host is registered above; only then project host-facing provider.
|
|
246
233
|
const { loadHostProvidersTable, projectHostFacingProvider } = await import(
|
|
@@ -291,10 +278,9 @@ export async function summonPublicRole(
|
|
|
291
278
|
const credentials =
|
|
292
279
|
options.credentials ?? (await loadCredentialProviders(agentDir));
|
|
293
280
|
const config = await loadPublicCliConfig(home);
|
|
294
|
-
// Nested
|
|
295
|
-
//
|
|
296
|
-
const
|
|
297
|
-
const seat = resolveEffectiveSeat(config, options.role, credentials, invocation);
|
|
281
|
+
// Nested summons resolve host on the officer seat only (flag>seat>default pi).
|
|
282
|
+
// Parent run host is not an override channel (#821 / ADR 0082 host-flag-two-channels).
|
|
283
|
+
const seat = resolveEffectiveSeat(config, options.role, credentials);
|
|
298
284
|
const env = {
|
|
299
285
|
...(await createSummonEnv({
|
|
300
286
|
role: options.role,
|
|
@@ -420,47 +406,6 @@ export async function summonPublicRole(
|
|
|
420
406
|
}
|
|
421
407
|
|
|
422
408
|
/** Gate officer summons: notary/auditor via --source-run; inspector via pointer instruction. */
|
|
423
|
-
/**
|
|
424
|
-
* Read parent invocation host so nested officers inherit live --host only (#645).
|
|
425
|
-
* Gate source runs necessarily own invocation.json — missing page, bad JSON, or
|
|
426
|
-
* bad shape stay loud (failure-honesty). Absent host field is lawful (seat default).
|
|
427
|
-
*/
|
|
428
|
-
async function parentInvocationHost(sourceRunDirectory: string): Promise<string | undefined> {
|
|
429
|
-
const { readFile } = await import("node:fs/promises");
|
|
430
|
-
const path = join(sourceRunDirectory, "invocation.json");
|
|
431
|
-
let text: string;
|
|
432
|
-
try {
|
|
433
|
-
text = await readFile(path, "utf8");
|
|
434
|
-
} catch (error) {
|
|
435
|
-
const code =
|
|
436
|
-
error instanceof Error && "code" in error
|
|
437
|
-
? (error as NodeJS.ErrnoException).code
|
|
438
|
-
: undefined;
|
|
439
|
-
throw new Error(
|
|
440
|
-
`parent invocation.json required for gate source run at ${path}: ${code ?? (error instanceof Error ? error.message : String(error))}`,
|
|
441
|
-
{ cause: error },
|
|
442
|
-
);
|
|
443
|
-
}
|
|
444
|
-
let raw: unknown;
|
|
445
|
-
try {
|
|
446
|
-
raw = JSON.parse(text) as unknown;
|
|
447
|
-
} catch (error) {
|
|
448
|
-
throw new Error(
|
|
449
|
-
`parent invocation.json unreadable at ${path}: ${error instanceof Error ? error.message : String(error)}`,
|
|
450
|
-
{ cause: error },
|
|
451
|
-
);
|
|
452
|
-
}
|
|
453
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
454
|
-
throw new Error(`parent invocation.json has non-object shape at ${path}`);
|
|
455
|
-
}
|
|
456
|
-
const host = (raw as Record<string, unknown>).host;
|
|
457
|
-
if (host === undefined) return undefined;
|
|
458
|
-
if (typeof host !== "string" || host.trim() === "") {
|
|
459
|
-
throw new Error(`parent invocation.json host must be a non-empty string at ${path}`);
|
|
460
|
-
}
|
|
461
|
-
return host;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
409
|
export async function summonGateOfficer(options: {
|
|
465
410
|
readonly officer: "inspector" | "notary" | "auditor";
|
|
466
411
|
readonly sourceRunDirectory: string;
|
|
@@ -501,9 +446,8 @@ export async function summonGateOfficer(options: {
|
|
|
501
446
|
submission: options.submission,
|
|
502
447
|
});
|
|
503
448
|
}
|
|
504
|
-
//
|
|
505
|
-
//
|
|
506
|
-
const parentHost = await parentInvocationHost(options.sourceRunDirectory);
|
|
449
|
+
// Officer host is seat-owned only (#821). Parent invocation.json.host stays a
|
|
450
|
+
// hostTransition consumer in post-admission — not an officer override channel.
|
|
507
451
|
const common = {
|
|
508
452
|
cwd: options.cwd,
|
|
509
453
|
...(home === undefined ? {} : { home }),
|
|
@@ -514,7 +458,6 @@ export async function summonGateOfficer(options: {
|
|
|
514
458
|
...(gateReviewInstruction === undefined
|
|
515
459
|
? {}
|
|
516
460
|
: { gateReviewInstruction }),
|
|
517
|
-
...(parentHost === undefined ? {} : { host: parentHost }),
|
|
518
461
|
...(options.roleTurnHost === undefined ? {} : { roleTurnHost: options.roleTurnHost }),
|
|
519
462
|
...(options.createRunId === undefined ? {} : { createRunId: options.createRunId }),
|
|
520
463
|
} as const;
|
package/src/reviewer-agent.ts
CHANGED
|
@@ -29,7 +29,17 @@ export class ReviewerDispatchExecutionError extends Error {
|
|
|
29
29
|
this.name = "ReviewerDispatchExecutionError";
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
-
export type
|
|
32
|
+
export type ReviewerDispatchRunOptions = Readonly<{
|
|
33
|
+
context: HostContext;
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
/**
|
|
36
|
+
* Request-scoped RoleHost flag reader (#818).
|
|
37
|
+
* Forwards to axis sub-session so envelope legs arm engine from request.engine,
|
|
38
|
+
* not ambient process.env (same resolveEngineName gate as parent registration).
|
|
39
|
+
*/
|
|
40
|
+
getFlag?: (name: string) => boolean | string | undefined;
|
|
41
|
+
}>;
|
|
42
|
+
export type ReviewerAgentRunner = { run(dispatch: AcceptedReviewerExecution, options: ReviewerDispatchRunOptions): Promise<ReviewerSuccessfulDispatchRunResult>; shutdown(): Promise<void> };
|
|
33
43
|
export type ReviewerAgentFaultPoint = ReviewerWorkspaceFaultPoint;
|
|
34
44
|
type Dependencies = Readonly<{
|
|
35
45
|
fault?(operation: ReviewerAgentFaultPoint): void;
|
|
@@ -81,6 +91,7 @@ export function createReviewerAgentRunner(dependencies: Dependencies = {}): Revi
|
|
|
81
91
|
...(dependencies.packageRoot === undefined
|
|
82
92
|
? {}
|
|
83
93
|
: { packageRoot: dependencies.packageRoot }),
|
|
94
|
+
...(options.getFlag === undefined ? {} : { getFlag: options.getFlag }),
|
|
84
95
|
});
|
|
85
96
|
const disposition = await workspaceOwner.dispose(workspace);
|
|
86
97
|
return [leg.axis, Object.freeze({ status: "successful" as const, report: child.report, usage: child.usage, target: batch.target, prompt: child.prompt, workspaceDisposition: disposition })] as const;
|
|
@@ -9,7 +9,7 @@ import type { Usage } from "@earendil-works/pi-ai";
|
|
|
9
9
|
|
|
10
10
|
import type { HostContext, RoleTurnModelConfig } from "./host-contracts.ts";
|
|
11
11
|
import { createEngineDetourToolDefinition } from "./engine-detour-tool.ts";
|
|
12
|
-
import {
|
|
12
|
+
import { resolveEngineName } from "./engine-detour.ts";
|
|
13
13
|
import {
|
|
14
14
|
appendEngineSessionMaterial,
|
|
15
15
|
engineSessionMaterialFromOptions,
|
|
@@ -101,6 +101,13 @@ export type ReviewerChildExecuteOptions = Readonly<{
|
|
|
101
101
|
credentialScratchParent?: string;
|
|
102
102
|
/** Package root for optional engine method-material on legs (#378). */
|
|
103
103
|
packageRoot?: string;
|
|
104
|
+
/**
|
|
105
|
+
* Request-scoped RoleHost flag reader (#818).
|
|
106
|
+
* Same single gate as registerEngineDetourTool / resolveEngineName:
|
|
107
|
+
* envelope projects request.engine here; absent → pi child-env fallback.
|
|
108
|
+
* Must not read ambient process.env as the primary signal on envelope legs.
|
|
109
|
+
*/
|
|
110
|
+
getFlag?: (name: string) => boolean | string | undefined;
|
|
104
111
|
}>;
|
|
105
112
|
|
|
106
113
|
/**
|
|
@@ -145,7 +152,8 @@ export async function executeReviewerChild(
|
|
|
145
152
|
const selection = await parentSelection(context);
|
|
146
153
|
const { openPiInProcessSession } = await import("./pi/in-process-session.ts");
|
|
147
154
|
const { createRecordSession } = await import("./archivist-record-entry.ts");
|
|
148
|
-
|
|
155
|
+
// One gate with parent registration (#818): request flag, else pi child env.
|
|
156
|
+
const engineName = resolveEngineName(options.getFlag);
|
|
149
157
|
const engineMaterial =
|
|
150
158
|
engineName === undefined
|
|
151
159
|
? undefined
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middle-layer role activation → host flag map (#819 / ADR 0082).
|
|
3
|
+
* Sole envelope-assembly definition for activation flags across host families.
|
|
4
|
+
* Host adapters only deliver these flags (ACP/headless getFlag map; pi argv pairs).
|
|
5
|
+
*/
|
|
6
|
+
import type { RoleTurnRequest } from "./host-contracts.ts";
|
|
7
|
+
import { packagedRoleInputFlag, packagedRolePhaseFlag } from "./packaged-role-registry.ts";
|
|
8
|
+
|
|
9
|
+
/** Project closed RoleTurnActivation onto the shared host flag map. */
|
|
10
|
+
export function projectActivationFlags(request: RoleTurnRequest): Map<string, boolean | string> {
|
|
11
|
+
const activation = request.activation;
|
|
12
|
+
const flags = new Map<string, boolean | string>([["ak-role", activation.role]]);
|
|
13
|
+
const inputFlag = packagedRoleInputFlag(activation.role);
|
|
14
|
+
const phaseFlag = packagedRolePhaseFlag(activation.role);
|
|
15
|
+
if ("phase" in activation && phaseFlag !== undefined) flags.set(phaseFlag, activation.phase);
|
|
16
|
+
if (inputFlag !== undefined) {
|
|
17
|
+
const path = "taskPath" in activation ? activation.taskPath
|
|
18
|
+
: "packetPath" in activation ? activation.packetPath
|
|
19
|
+
: "casePath" in activation ? activation.casePath
|
|
20
|
+
: "inputPath" in activation ? activation.inputPath
|
|
21
|
+
: "sourceRun" in activation ? activation.sourceRun
|
|
22
|
+
: undefined;
|
|
23
|
+
if (path !== undefined) flags.set(inputFlag, path);
|
|
24
|
+
}
|
|
25
|
+
if (activation.role === "fixer" && activation.prerequisitesPath !== undefined) {
|
|
26
|
+
flags.set("ak-fixer-prerequisites", activation.prerequisitesPath);
|
|
27
|
+
}
|
|
28
|
+
if (activation.role === "reviewer") {
|
|
29
|
+
flags.set("ak-review-base", activation.baseRevision);
|
|
30
|
+
flags.set("ak-review-authority-refs", JSON.stringify(activation.authorityRefs));
|
|
31
|
+
if (activation.ticketNumber !== undefined) {
|
|
32
|
+
flags.set("ak-review-ticket-number", String(activation.ticketNumber));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// countersign ticketNumber stays on activation/admission/invocation only —
|
|
36
|
+
// no private transport flag (inner-gate material path deleted in #632).
|
|
37
|
+
if (activation.role === "notary" && activation.ticketNumber !== undefined) {
|
|
38
|
+
flags.set("ak-notary-ticket-number", String(activation.ticketNumber));
|
|
39
|
+
}
|
|
40
|
+
if (activation.role === "gleaner-left") {
|
|
41
|
+
flags.set("ak-gleaner-left-base", activation.baseRevision);
|
|
42
|
+
}
|
|
43
|
+
if (activation.role === "collector") {
|
|
44
|
+
flags.set("ak-collector-repo", activation.repo);
|
|
45
|
+
// #676 D1: pr optional at admission; omit flag when role binds from materials.
|
|
46
|
+
if (activation.pr !== undefined) flags.set("ak-collector-pr", activation.pr);
|
|
47
|
+
if (activation.requestManifestPath !== undefined) {
|
|
48
|
+
flags.set("ak-collector-request-manifest", activation.requestManifestPath);
|
|
49
|
+
}
|
|
50
|
+
if (activation.waitMs !== undefined) flags.set("ak-collector-wait-ms", activation.waitMs);
|
|
51
|
+
}
|
|
52
|
+
return flags;
|
|
53
|
+
}
|