@akagilnc/pi-workflow-roles 0.1.3826 → 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 +22757 -22676
- package/dist/canonical-skill-binding.js +0 -3
- package/dist/engine-detour-tool.js +2 -2
- package/dist/engine-detour.js +23 -7
- package/dist/headless-host/production-host.js +4658 -4593
- package/dist/host-descriptions.js +5 -2
- package/dist/pi/adapter.js +11 -0
- package/dist/pi/role-turn-host.js +44 -93
- package/dist/public-cli/invocation.js +4 -5
- 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 +145 -140
- 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 +12 -10
- package/dist/worker-role.js +14 -12
- 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/canonical-skill-binding.ts +4 -4
- 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-contracts.ts +6 -0
- package/src/host-descriptions.ts +5 -2
- package/src/host-session-record.ts +41 -0
- package/src/package-resources/method-skill-binding.ts +0 -3
- package/src/pi/adapter.ts +13 -0
- package/src/pi/role-turn-host.ts +46 -94
- package/src/prepared-role-turn.ts +68 -0
- package/src/public-cli/cli.ts +11 -20
- package/src/public-cli/invocation.ts +4 -5
- 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} +41 -98
- package/src/role-runtime-dependencies.ts +91 -0
- package/src/role-runtime.ts +21 -11
- package/src/{acp-host/session-identity.ts → session-identity.ts} +6 -6
- package/src/worker-role.ts +14 -13
- /package/src/{acp-host/mcp-relay.mjs → mcp-relay.mjs} +0 -0
package/src/host-contracts.ts
CHANGED
|
@@ -321,6 +321,12 @@ export type HostSkillExpansionEvidence = Readonly<{
|
|
|
321
321
|
/** Host capability declaration (contract verb ④). */
|
|
322
322
|
export type HostCapabilityDeclaration = Readonly<{
|
|
323
323
|
skillExpansion(prompt: string): HostSkillExpansionEvidence | undefined;
|
|
324
|
+
/**
|
|
325
|
+
* Pi-only: recover the plain original request from a native skill turn text
|
|
326
|
+
* (argv may already carry `/skill:<name>` from the Pi adapter). Absent on
|
|
327
|
+
* non-pi hosts so role body never parses Pi slash syntax (ADR 0082).
|
|
328
|
+
*/
|
|
329
|
+
skillOriginalRequest?(name: string, text: string): string;
|
|
324
330
|
}>;
|
|
325
331
|
|
|
326
332
|
/** Host-owned effects used by the shared activation envelope. */
|
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
|
+
}
|
|
@@ -46,9 +46,6 @@ export async function loadPackagedCanonicalSkillBinding<
|
|
|
46
46
|
const binding: CanonicalSkillBinding<Name> = {
|
|
47
47
|
name,
|
|
48
48
|
snapshot,
|
|
49
|
-
invocation(originalRequest) {
|
|
50
|
-
return `/skill:${name} ${originalRequest}`;
|
|
51
|
-
},
|
|
52
49
|
captureExpansion(evidence, originalRequest) {
|
|
53
50
|
return captureCanonicalSkillExpansion(
|
|
54
51
|
name,
|
package/src/pi/adapter.ts
CHANGED
|
@@ -157,6 +157,19 @@ export function createPiRoleHostAdapter(
|
|
|
157
157
|
userMessage,
|
|
158
158
|
});
|
|
159
159
|
},
|
|
160
|
+
/** Recover plain original request from Pi-native `/skill:` turn text (ADR 0082). */
|
|
161
|
+
skillOriginalRequest(name, text) {
|
|
162
|
+
const token = `/skill:${name}`;
|
|
163
|
+
const trimmed = text.trimStart();
|
|
164
|
+
if (
|
|
165
|
+
trimmed === token
|
|
166
|
+
|| trimmed.startsWith(`${token} `)
|
|
167
|
+
|| trimmed.startsWith(`${token}\n`)
|
|
168
|
+
) {
|
|
169
|
+
return text.slice(text.indexOf(token) + token.length).trim();
|
|
170
|
+
}
|
|
171
|
+
return text.trim();
|
|
172
|
+
},
|
|
160
173
|
},
|
|
161
174
|
registerFlag: (name, definition) => pi.registerFlag(name, definition),
|
|
162
175
|
getFlag: (name) => pi.getFlag(name),
|
package/src/pi/role-turn-host.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { execFile, spawn } from "node:child_process";
|
|
7
7
|
import { constants } from "node:fs";
|
|
8
8
|
import { access, appendFile, readFile, realpath } from "node:fs/promises";
|
|
9
|
-
import { delimiter, isAbsolute, join, resolve } from "node:path";
|
|
9
|
+
import { basename, delimiter, dirname, isAbsolute, join, resolve } from "node:path";
|
|
10
10
|
import { platform } from "node:process";
|
|
11
11
|
import { promisify } from "node:util";
|
|
12
12
|
import { randomUUID } from "node:crypto";
|
|
@@ -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[] {
|
|
@@ -168,8 +91,35 @@ function buildMethodArgs(methods: readonly MethodBinding[]): string[] {
|
|
|
168
91
|
}
|
|
169
92
|
|
|
170
93
|
/**
|
|
171
|
-
*
|
|
172
|
-
*
|
|
94
|
+
* Pi-native `/skill:<name>` for a single forced method (ADR 0082 `pi-no-privilege`).
|
|
95
|
+
* Driven by typed RoleTurnRequest.methods — middle-layer decision, Pi-only syntax.
|
|
96
|
+
* Zero/many methods leave the prompt alone (Fixer optional pair stays `--skill` only).
|
|
97
|
+
*/
|
|
98
|
+
export function applyPiNativeSkillInvocation(
|
|
99
|
+
methods: readonly MethodBinding[],
|
|
100
|
+
prompt: string,
|
|
101
|
+
): string {
|
|
102
|
+
const skills = methods.filter((method) => method.kind === "skill");
|
|
103
|
+
if (skills.length !== 1) return prompt;
|
|
104
|
+
const name = basename(dirname(skills[0]!.path));
|
|
105
|
+
if (name.length === 0) return prompt;
|
|
106
|
+
const token = `/skill:${name}`;
|
|
107
|
+
const trimmed = prompt.trimStart();
|
|
108
|
+
if (
|
|
109
|
+
trimmed === token
|
|
110
|
+
|| trimmed.startsWith(`${token} `)
|
|
111
|
+
|| trimmed.startsWith(`${token}\n`)
|
|
112
|
+
) {
|
|
113
|
+
return prompt;
|
|
114
|
+
}
|
|
115
|
+
return prompt.length === 0 ? token : `${token} ${prompt}`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
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).
|
|
173
123
|
*/
|
|
174
124
|
export function buildPiTurnExtraArgs(
|
|
175
125
|
request: RoleTurnRequest,
|
|
@@ -177,13 +127,14 @@ export function buildPiTurnExtraArgs(
|
|
|
177
127
|
extraPiArgs: readonly string[] = [],
|
|
178
128
|
): string[] {
|
|
179
129
|
const { sessionFile, sessionDirectory } = authority.decode(request.principal);
|
|
180
|
-
const
|
|
130
|
+
const rawPrompt =
|
|
181
131
|
request.continuation.kind === "initial" || request.continuation.kind === "resume"
|
|
182
132
|
? request.continuation.prompt
|
|
183
133
|
: (() => {
|
|
184
134
|
const _exhaustive: never = request.continuation;
|
|
185
135
|
return _exhaustive;
|
|
186
136
|
})();
|
|
137
|
+
const prompt = applyPiNativeSkillInvocation(request.methods, rawPrompt);
|
|
187
138
|
return [
|
|
188
139
|
"--no-skills",
|
|
189
140
|
...buildMethodArgs(request.methods),
|
|
@@ -195,7 +146,8 @@ export function buildPiTurnExtraArgs(
|
|
|
195
146
|
"--session-dir",
|
|
196
147
|
sessionDirectory,
|
|
197
148
|
...extraPiArgs,
|
|
198
|
-
|
|
149
|
+
// Envelope assembly = projectActivationFlags; pi only renders argv pairs.
|
|
150
|
+
...activationFlagsToPiArgv(projectActivationFlags(request)),
|
|
199
151
|
"--mode",
|
|
200
152
|
"json",
|
|
201
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;
|
|
@@ -3071,16 +3071,15 @@ export async function admitMergerInvocation(
|
|
|
3071
3071
|
}
|
|
3072
3072
|
|
|
3073
3073
|
/**
|
|
3074
|
-
* Build the
|
|
3075
|
-
*
|
|
3074
|
+
* Build the host-neutral prompt transport for an admitted Merger request.
|
|
3075
|
+
* Method material binds on RoleTurnRequest.methods; Pi-native `/skill:` form
|
|
3076
|
+
* is adapter-internal only (ADR 0082).
|
|
3076
3077
|
*/
|
|
3077
3078
|
export function buildMergerTransportPrompt(
|
|
3078
3079
|
admitted: AdmittedMergerInvocation,
|
|
3079
3080
|
engineMaterial?: EngineSessionMaterial,
|
|
3080
3081
|
): string {
|
|
3081
|
-
const lines: string[] = [
|
|
3082
|
-
`/skill:resolving-merge-conflicts ${admitted.instruction}`,
|
|
3083
|
-
];
|
|
3082
|
+
const lines: string[] = [admitted.instruction];
|
|
3084
3083
|
if (admitted.attachments.length > 0) {
|
|
3085
3084
|
lines.push("");
|
|
3086
3085
|
lines.push("已受理附件(冻结快照路径):");
|
|
@@ -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;
|