@akagilnc/pi-workflow-roles 0.1.5307 → 0.1.5312
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/description.js +0 -11
- package/dist/acp-host/production-host.js +440 -441
- package/dist/headless-host/description.js +0 -11
- package/dist/headless-host/production-host.js +533 -546
- package/dist/host-descriptions.js +13 -0
- package/dist/prepared-role-turn.js +4 -0
- package/dist/public-cli/machine-method-skills.js +5 -5
- package/dist/public-cli/main.js +154 -150
- package/package.json +1 -1
- package/src/acp-host/description.ts +2 -12
- package/src/acp-host/production-host.ts +3 -2
- package/src/acp-host/role-turn-host.ts +7 -42
- package/src/external-host-turn-loop.ts +36 -16
- package/src/headless-host/description.ts +3 -18
- package/src/headless-host/production-host.ts +3 -2
- package/src/headless-host/role-turn-host.ts +10 -68
- package/src/host-descriptions.ts +18 -0
- package/src/prepared-role-turn.ts +8 -0
- package/src/public-cli/machine-method-skills.ts +5 -5
package/package.json
CHANGED
|
@@ -4,13 +4,10 @@
|
|
|
4
4
|
* optional seat-profile soul — is data here; the lifecycle in role-turn-host.ts
|
|
5
5
|
* stays one copy (#732).
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import type { HostIdentityDescription } from "../host-descriptions.ts";
|
|
9
8
|
import type { SeatProfileSoul } from "./seat-profile-soul.ts";
|
|
10
9
|
|
|
11
|
-
export type AcpHostDescription = Readonly<{
|
|
12
|
-
/** Binary path segments relative to the operator home. */
|
|
13
|
-
binaryFromHome: readonly string[];
|
|
10
|
+
export type AcpHostDescription = HostIdentityDescription & Readonly<{
|
|
14
11
|
argv: Readonly<{
|
|
15
12
|
prefix: readonly string[];
|
|
16
13
|
suffix: readonly string[];
|
|
@@ -26,8 +23,6 @@ export type AcpHostDescription = Readonly<{
|
|
|
26
23
|
* `provider:model` (hermes).
|
|
27
24
|
*/
|
|
28
25
|
modelPassing: "argv" | "set_model";
|
|
29
|
-
/** Durable ACP binding filename written beside the session principal. */
|
|
30
|
-
sessionBindingFile: string;
|
|
31
26
|
/**
|
|
32
27
|
* When set, the production factory ensures a seat profile whose SOUL.md is a
|
|
33
28
|
* symlink to the packaged role soul, and prefixes argv with `flag <name>`.
|
|
@@ -36,11 +31,6 @@ export type AcpHostDescription = Readonly<{
|
|
|
36
31
|
seatProfileSoul?: SeatProfileSoul;
|
|
37
32
|
}>;
|
|
38
33
|
|
|
39
|
-
/** Absolute agent binary for one operator home. */
|
|
40
|
-
export function resolveAcpBinary(description: AcpHostDescription, operatorHome: string): string {
|
|
41
|
-
return join(operatorHome, ...description.binaryFromHome);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
34
|
/** Stdio argv: optional profile flag, thinking flag, prefix, model, suffix. */
|
|
45
35
|
export function acpStdioArgs(
|
|
46
36
|
description: AcpHostDescription,
|
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
import { randomUUID } from "node:crypto";
|
|
10
10
|
|
|
11
11
|
import type { DurablePrincipalAuthority, RoleTurnHost, RoleTurnRequest } from "../host-contracts.ts";
|
|
12
|
+
import { resolveHostBinary } from "../host-descriptions.ts";
|
|
12
13
|
import { prepareRoleEnvelope } from "../role-envelope.ts";
|
|
13
14
|
import type { RoleRuntimeDependencies } from "../role-runtime.ts";
|
|
14
15
|
import { createRoleRuntimeDependencies } from "../role-runtime-dependencies.ts";
|
|
15
16
|
import { createSessionIdentityAuthority } from "../session-identity.ts";
|
|
16
|
-
import { acpStdioArgs,
|
|
17
|
+
import { acpStdioArgs, type AcpHostDescription } from "./description.ts";
|
|
17
18
|
import {
|
|
18
19
|
connectAcpStdio,
|
|
19
20
|
createAcpRoleTurnHost,
|
|
@@ -74,7 +75,7 @@ export function createProductionAcpRoleTurnHost(options: ProductionAcpHostOption
|
|
|
74
75
|
role: request.activation.role,
|
|
75
76
|
});
|
|
76
77
|
return connectAcpStdio({
|
|
77
|
-
binary:
|
|
78
|
+
binary: resolveHostBinary(description, request.home),
|
|
78
79
|
args: acpStdioArgs(
|
|
79
80
|
description,
|
|
80
81
|
request.model,
|
|
@@ -2,11 +2,13 @@ import { join } from "node:path";
|
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { createInterface } from "node:readline";
|
|
4
4
|
|
|
5
|
-
import type { RoleTurnHost,
|
|
5
|
+
import type { RoleTurnHost, RoleTurnRequest, RoleTurnResult } from "../host-contracts.ts";
|
|
6
6
|
import {
|
|
7
7
|
createSerializedRoleTurnHost,
|
|
8
8
|
driveExternalRoleTurnRounds,
|
|
9
|
+
externalHostFailure as failure,
|
|
9
10
|
raceAgainstHostAbort,
|
|
11
|
+
withExternalHostCleanupFailure,
|
|
10
12
|
} from "../external-host-turn-loop.ts";
|
|
11
13
|
|
|
12
14
|
import {
|
|
@@ -19,6 +21,7 @@ import {
|
|
|
19
21
|
} from "../package-contracts/navigator-output.ts";
|
|
20
22
|
import {
|
|
21
23
|
renderSystemPromptOverride,
|
|
24
|
+
resolveBoundHostSessionId,
|
|
22
25
|
type PreparedRoleTurn,
|
|
23
26
|
type SessionIdentityAuthority,
|
|
24
27
|
} from "../prepared-role-turn.ts";
|
|
@@ -96,41 +99,6 @@ export type AcpRoleTurnHostConfig = Readonly<{
|
|
|
96
99
|
prepare(request: RoleTurnRequest): Promise<PreparedRoleTurn>;
|
|
97
100
|
}>;
|
|
98
101
|
|
|
99
|
-
function failure(
|
|
100
|
-
cause: "activation" | "session" | "output",
|
|
101
|
-
name: string,
|
|
102
|
-
code: string,
|
|
103
|
-
details?: Readonly<Record<string, unknown>>,
|
|
104
|
-
diagnostic?: string,
|
|
105
|
-
): RoleTurnResult {
|
|
106
|
-
return {
|
|
107
|
-
code: null,
|
|
108
|
-
stderr: "",
|
|
109
|
-
timedOut: false,
|
|
110
|
-
knownFailure: {
|
|
111
|
-
cause,
|
|
112
|
-
identity: { name, code },
|
|
113
|
-
...(diagnostic === undefined ? {} : { diagnostic }),
|
|
114
|
-
...(details === undefined ? {} : { details }),
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/** Success→dispose failure; existing failure keeps primary cause + cleanup detail. */
|
|
120
|
-
function withCleanupFailure(outcome: RoleTurnResult, cleanupError: unknown): RoleTurnResult {
|
|
121
|
-
const message = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
|
|
122
|
-
if (outcome.knownFailure === undefined) {
|
|
123
|
-
return failure("session", "AcpDisposeFailure", "dispose-failed", { cleanupError: message }, message);
|
|
124
|
-
}
|
|
125
|
-
return {
|
|
126
|
-
...outcome,
|
|
127
|
-
knownFailure: {
|
|
128
|
-
...outcome.knownFailure,
|
|
129
|
-
details: { ...(outcome.knownFailure.details ?? {}), cleanupError: message },
|
|
130
|
-
},
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
102
|
type RpcReply = { readonly id?: unknown; readonly method?: unknown; readonly params?: unknown; readonly result?: unknown; readonly error?: unknown };
|
|
135
103
|
|
|
136
104
|
function acpError(code: string, message: string, cause?: unknown): Error & { readonly code: string } {
|
|
@@ -259,7 +227,7 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
259
227
|
let sessionOpened = false;
|
|
260
228
|
let accepted = false;
|
|
261
229
|
const sessionParent = config.sessionIdentity.resolveSessionFile(request.principal);
|
|
262
|
-
// Mutable so dispose failure can outrank a clean turn
|
|
230
|
+
// Mutable so dispose failure can outrank a clean turn.
|
|
263
231
|
let outcome: RoleTurnResult = failure("session", "AcpNoOutcome", "no-outcome");
|
|
264
232
|
try {
|
|
265
233
|
if (prepared.mcpServers.length === 0) {
|
|
@@ -324,10 +292,7 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
324
292
|
};
|
|
325
293
|
let sessionReady = true;
|
|
326
294
|
if (request.continuation.kind === "resume") {
|
|
327
|
-
const
|
|
328
|
-
const boundSessionId = explicitHostSessionId !== undefined && explicitHostSessionId !== ""
|
|
329
|
-
? explicitHostSessionId
|
|
330
|
-
: await config.sessionIdentity.load(request.principal);
|
|
295
|
+
const boundSessionId = await resolveBoundHostSessionId(request, config.sessionIdentity);
|
|
331
296
|
if (boundSessionId !== undefined && boundSessionId !== "") {
|
|
332
297
|
sessionId = boundSessionId;
|
|
333
298
|
sessionId = await loadSession(boundSessionId);
|
|
@@ -446,7 +411,7 @@ export function createAcpRoleTurnHost(config: AcpRoleTurnHostConfig): RoleTurnHo
|
|
|
446
411
|
try {
|
|
447
412
|
await prepared.dispose?.();
|
|
448
413
|
} catch (cleanupError) {
|
|
449
|
-
outcome =
|
|
414
|
+
outcome = withExternalHostCleanupFailure(outcome, cleanupError, "AcpDisposeFailure");
|
|
450
415
|
}
|
|
451
416
|
}
|
|
452
417
|
return outcome;
|
|
@@ -9,25 +9,11 @@ import type {
|
|
|
9
9
|
RoleTurnRequest,
|
|
10
10
|
RoleTurnResult,
|
|
11
11
|
} from "./host-contracts.ts";
|
|
12
|
+
import type { PreparedRoleTurn } from "./prepared-role-turn.ts";
|
|
12
13
|
|
|
13
14
|
export const EXTERNAL_ROLE_TURN_ROUND_LIMIT = 8 as const;
|
|
14
15
|
|
|
15
|
-
export type ExternalPreparedTurn =
|
|
16
|
-
readonly prompt: string;
|
|
17
|
-
readonly abortSignal?: AbortSignal;
|
|
18
|
-
closeRound(): Promise<
|
|
19
|
-
| { readonly accepted: true }
|
|
20
|
-
| {
|
|
21
|
-
readonly accepted: false;
|
|
22
|
-
readonly retry: {
|
|
23
|
-
readonly code: string;
|
|
24
|
-
readonly toolCallIds: readonly string[];
|
|
25
|
-
readonly message: string;
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
| { readonly accepted: false; readonly failure: RoleTurnKnownFailure }
|
|
29
|
-
>;
|
|
30
|
-
}>;
|
|
16
|
+
export type ExternalPreparedTurn = Pick<PreparedRoleTurn, "prompt" | "abortSignal" | "closeRound">;
|
|
31
17
|
|
|
32
18
|
export type ExternalHostRoundOutcome =
|
|
33
19
|
| { readonly status: "delivered"; readonly stderr?: string }
|
|
@@ -96,6 +82,40 @@ export function raceAgainstHostAbort<T>(
|
|
|
96
82
|
});
|
|
97
83
|
}
|
|
98
84
|
|
|
85
|
+
export function externalHostFailure(
|
|
86
|
+
cause: NonNullable<RoleTurnKnownFailure["cause"]>,
|
|
87
|
+
name: string,
|
|
88
|
+
code: string,
|
|
89
|
+
details?: Readonly<Record<string, unknown>>,
|
|
90
|
+
diagnostic?: string,
|
|
91
|
+
): RoleTurnResult {
|
|
92
|
+
return asFailure({
|
|
93
|
+
cause,
|
|
94
|
+
identity: { name, code },
|
|
95
|
+
...(diagnostic === undefined ? {} : { diagnostic }),
|
|
96
|
+
...(details === undefined ? {} : { details }),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** A dispose failure replaces success; a primary failure retains its cause. */
|
|
101
|
+
export function withExternalHostCleanupFailure(
|
|
102
|
+
outcome: RoleTurnResult,
|
|
103
|
+
cleanupError: unknown,
|
|
104
|
+
name: string,
|
|
105
|
+
): RoleTurnResult {
|
|
106
|
+
const message = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
|
|
107
|
+
if (outcome.knownFailure === undefined) {
|
|
108
|
+
return externalHostFailure("session", name, "dispose-failed", { cleanupError: message }, message);
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
...outcome,
|
|
112
|
+
knownFailure: {
|
|
113
|
+
...outcome.knownFailure,
|
|
114
|
+
details: { ...(outcome.knownFailure.details ?? {}), cleanupError: message },
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
99
119
|
function asFailure(knownFailure: RoleTurnKnownFailure, stderr = ""): RoleTurnResult {
|
|
100
120
|
return { code: null, stderr, timedOut: false, knownFailure };
|
|
101
121
|
}
|
|
@@ -4,17 +4,10 @@
|
|
|
4
4
|
* Claude print-mode and codex exec differ enough that #752 host-specific
|
|
5
5
|
* assembly lives here as sibling helpers — not a third unified abstraction.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
type HeadlessHostBase = Readonly<{
|
|
10
|
-
/** Binary path segments relative to the operator home. */
|
|
11
|
-
binaryFromHome: readonly string[];
|
|
12
|
-
/** Durable session-id binding filename beside the session principal. */
|
|
13
|
-
sessionBindingFile: string;
|
|
14
|
-
}>;
|
|
7
|
+
import type { HostIdentityDescription } from "../host-descriptions.ts";
|
|
15
8
|
|
|
16
9
|
/** Claude Code print-mode (#645). */
|
|
17
|
-
export type ClaudePrintHostDescription =
|
|
10
|
+
export type ClaudePrintHostDescription = HostIdentityDescription & Readonly<{
|
|
18
11
|
protocol: "claude-print";
|
|
19
12
|
/**
|
|
20
13
|
* Host-native print-mode flags that never change per turn (no prompt).
|
|
@@ -48,7 +41,7 @@ export type ClaudePrintHostDescription = HeadlessHostBase & Readonly<{
|
|
|
48
41
|
* Protocol differences (JSONL, resume subcommand, schema file, `-c` MCP) stay
|
|
49
42
|
* in codex-specific helpers — description only carries identity + binary path.
|
|
50
43
|
*/
|
|
51
|
-
export type CodexExecHostDescription =
|
|
44
|
+
export type CodexExecHostDescription = HostIdentityDescription & Readonly<{
|
|
52
45
|
protocol: "codex-exec";
|
|
53
46
|
}>;
|
|
54
47
|
|
|
@@ -66,14 +59,6 @@ export function isCodexExecDescription(
|
|
|
66
59
|
return description.protocol === "codex-exec";
|
|
67
60
|
}
|
|
68
61
|
|
|
69
|
-
/** Absolute agent binary for one operator home. */
|
|
70
|
-
export function resolveHeadlessBinary(
|
|
71
|
-
description: HeadlessHostDescription,
|
|
72
|
-
operatorHome: string,
|
|
73
|
-
): string {
|
|
74
|
-
return join(operatorHome, ...description.binaryFromHome);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
62
|
/**
|
|
78
63
|
* Build one Claude print-mode argv for a single process turn.
|
|
79
64
|
* Shape: `<promptFlag> <fixedArgs…> <system/schema/mcp/model/effort/session…>`.
|
|
@@ -12,10 +12,11 @@
|
|
|
12
12
|
import { randomUUID } from "node:crypto";
|
|
13
13
|
|
|
14
14
|
import type { DurablePrincipalAuthority, RoleTurnHost } from "../host-contracts.ts";
|
|
15
|
+
import { resolveHostBinary } from "../host-descriptions.ts";
|
|
15
16
|
import { createRoleRuntimeDependencies } from "../role-runtime-dependencies.ts";
|
|
16
17
|
import { prepareRoleEnvelope } from "../role-envelope.ts";
|
|
17
18
|
import { createSessionIdentityAuthority } from "../session-identity.ts";
|
|
18
|
-
import {
|
|
19
|
+
import type { HeadlessHostDescription } from "./description.ts";
|
|
19
20
|
import { createHeadlessRoleTurnHost } from "./role-turn-host.ts";
|
|
20
21
|
|
|
21
22
|
export type ProductionHeadlessHostOptions = Readonly<{
|
|
@@ -46,7 +47,7 @@ export function createProductionHeadlessRoleTurnHost(
|
|
|
46
47
|
description,
|
|
47
48
|
hostName,
|
|
48
49
|
sessionIdentity,
|
|
49
|
-
binary:
|
|
50
|
+
binary: resolveHostBinary(description, operatorHome),
|
|
50
51
|
env: {
|
|
51
52
|
...process.env,
|
|
52
53
|
AK_PACKAGE_ROOT: packageRoot,
|
|
@@ -13,11 +13,14 @@ import type { RoleTurnHost, RoleTurnRequest, RoleTurnResult } from "../host-cont
|
|
|
13
13
|
import {
|
|
14
14
|
createSerializedRoleTurnHost,
|
|
15
15
|
driveExternalRoleTurnRounds,
|
|
16
|
+
externalHostFailure as failure,
|
|
16
17
|
hostAbortedError,
|
|
17
18
|
isHostAbortedError,
|
|
19
|
+
withExternalHostCleanupFailure,
|
|
18
20
|
} from "../external-host-turn-loop.ts";
|
|
19
21
|
import {
|
|
20
22
|
renderSystemPromptOverride,
|
|
23
|
+
resolveBoundHostSessionId,
|
|
21
24
|
type PreparedRoleTurn,
|
|
22
25
|
type SessionIdentityAuthority,
|
|
23
26
|
} from "../prepared-role-turn.ts";
|
|
@@ -48,26 +51,6 @@ export type HeadlessRoleTurnHostConfig = Readonly<{
|
|
|
48
51
|
env?: NodeJS.ProcessEnv;
|
|
49
52
|
}>;
|
|
50
53
|
|
|
51
|
-
function failure(
|
|
52
|
-
cause: "activation" | "session" | "output" | "provider",
|
|
53
|
-
name: string,
|
|
54
|
-
code: string,
|
|
55
|
-
details?: Readonly<Record<string, unknown>>,
|
|
56
|
-
diagnostic?: string,
|
|
57
|
-
): RoleTurnResult {
|
|
58
|
-
return {
|
|
59
|
-
code: null,
|
|
60
|
-
stderr: "",
|
|
61
|
-
timedOut: false,
|
|
62
|
-
knownFailure: {
|
|
63
|
-
cause,
|
|
64
|
-
identity: { name, code },
|
|
65
|
-
...(diagnostic === undefined ? {} : { diagnostic }),
|
|
66
|
-
...(details === undefined ? {} : { details }),
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
54
|
/** One headless CLI result envelope (stream-json last line, or single json doc). */
|
|
72
55
|
export type HeadlessCliResult = Readonly<{
|
|
73
56
|
session_id?: string;
|
|
@@ -93,39 +76,18 @@ function isHeadlessResultCandidate(value: unknown): value is HeadlessCliResult {
|
|
|
93
76
|
}
|
|
94
77
|
|
|
95
78
|
/**
|
|
96
|
-
* Parse
|
|
97
|
-
*
|
|
98
|
-
* line is the typed receipt. A single-document `json` body still parses so a
|
|
99
|
-
* misconfigured description yields a typed miss rather than a silent empty parse.
|
|
100
|
-
* Callers must not retain the full stream — only the rolling result candidate.
|
|
79
|
+
* Parse the rolling result-candidate line retained by spawnHeadlessTurn.
|
|
80
|
+
* The full stream is delivered live, not passed to this parser.
|
|
101
81
|
*/
|
|
102
82
|
export function parseHeadlessCliStdout(stdout: string): HeadlessCliResult | undefined {
|
|
103
83
|
const trimmed = stdout.trim();
|
|
104
84
|
if (trimmed === "") return undefined;
|
|
105
85
|
try {
|
|
106
|
-
const
|
|
107
|
-
|
|
86
|
+
const value = JSON.parse(trimmed) as unknown;
|
|
87
|
+
return isHeadlessResultCandidate(value) ? value : undefined;
|
|
108
88
|
} catch {
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
// stream-json: keep the last result line (structured_output / is_error live here).
|
|
112
|
-
let last: HeadlessCliResult | undefined;
|
|
113
|
-
for (const line of trimmed.split("\n")) {
|
|
114
|
-
const text = line.trim();
|
|
115
|
-
if (text === "") continue;
|
|
116
|
-
try {
|
|
117
|
-
const value = JSON.parse(text) as unknown;
|
|
118
|
-
if (!isPlainObject(value)) continue;
|
|
119
|
-
const record = value as HeadlessCliResult & { type?: unknown };
|
|
120
|
-
// Multi-line stream: only explicit result / structured_output lines (not bare objects).
|
|
121
|
-
if (record.type === "result" || record.structured_output !== undefined) {
|
|
122
|
-
last = record;
|
|
123
|
-
}
|
|
124
|
-
} catch {
|
|
125
|
-
// skip non-JSON noise lines
|
|
126
|
-
}
|
|
89
|
+
return undefined;
|
|
127
90
|
}
|
|
128
|
-
return last;
|
|
129
91
|
}
|
|
130
92
|
|
|
131
93
|
/**
|
|
@@ -380,21 +342,6 @@ function spawnHeadlessTurn(options: {
|
|
|
380
342
|
});
|
|
381
343
|
}
|
|
382
344
|
|
|
383
|
-
/** Success→dispose failure; existing failure keeps primary cause + cleanup detail. */
|
|
384
|
-
function withCleanupFailure(outcome: RoleTurnResult, cleanupError: unknown): RoleTurnResult {
|
|
385
|
-
const message = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
|
|
386
|
-
if (outcome.knownFailure === undefined) {
|
|
387
|
-
return failure("session", "HeadlessDisposeFailure", "dispose-failed", { cleanupError: message }, message);
|
|
388
|
-
}
|
|
389
|
-
return {
|
|
390
|
-
...outcome,
|
|
391
|
-
knownFailure: {
|
|
392
|
-
...outcome.knownFailure,
|
|
393
|
-
details: { ...(outcome.knownFailure.details ?? {}), cleanupError: message },
|
|
394
|
-
},
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
|
|
398
345
|
function terminalFromSpawned(
|
|
399
346
|
spawned: { code: number | null; stderr: string; timedOut: boolean },
|
|
400
347
|
knownFailure: NonNullable<RoleTurnResult["knownFailure"]>,
|
|
@@ -469,12 +416,7 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
469
416
|
try {
|
|
470
417
|
// Claude mints a package UUID for --session-id; codex waits for thread.started.
|
|
471
418
|
// Public explicit resume already read the stored native id. Auto-resume omits it.
|
|
472
|
-
|
|
473
|
-
? request.continuation.hostSessionId
|
|
474
|
-
: undefined;
|
|
475
|
-
let sessionId = explicitHostSessionId !== undefined && explicitHostSessionId !== ""
|
|
476
|
-
? explicitHostSessionId
|
|
477
|
-
: await config.sessionIdentity.load(request.principal);
|
|
419
|
+
let sessionId = await resolveBoundHostSessionId(request, config.sessionIdentity);
|
|
478
420
|
if (request.continuation.kind === "resume" && (sessionId === undefined || sessionId === "")) {
|
|
479
421
|
outcome = failure(
|
|
480
422
|
"session",
|
|
@@ -821,7 +763,7 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
821
763
|
try {
|
|
822
764
|
await prepared.dispose?.();
|
|
823
765
|
} catch (cleanupError) {
|
|
824
|
-
outcome =
|
|
766
|
+
outcome = withExternalHostCleanupFailure(outcome, cleanupError, "HeadlessDisposeFailure");
|
|
825
767
|
}
|
|
826
768
|
}
|
|
827
769
|
return outcome;
|
package/src/host-descriptions.ts
CHANGED
|
@@ -6,9 +6,27 @@
|
|
|
6
6
|
* pi is the in-process default, not a row.
|
|
7
7
|
* Unregistered names fail closed (#510); these tables do not fallback.
|
|
8
8
|
*/
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
|
|
9
11
|
import type { AcpHostDescription } from "./acp-host/description.ts";
|
|
10
12
|
import type { HeadlessHostDescription } from "./headless-host/description.ts";
|
|
11
13
|
|
|
14
|
+
/** Shared identity fields consumed by both external host families. */
|
|
15
|
+
export type HostIdentityDescription = Readonly<{
|
|
16
|
+
/** Binary path segments relative to the operator home. */
|
|
17
|
+
binaryFromHome: readonly string[];
|
|
18
|
+
/** Durable host session binding filename beside the session principal. */
|
|
19
|
+
sessionBindingFile: string;
|
|
20
|
+
}>;
|
|
21
|
+
|
|
22
|
+
/** Resolve a host description's agent binary under the operator home. */
|
|
23
|
+
export function resolveHostBinary(
|
|
24
|
+
description: HostIdentityDescription,
|
|
25
|
+
operatorHome: string,
|
|
26
|
+
): string {
|
|
27
|
+
return join(operatorHome, ...description.binaryFromHome);
|
|
28
|
+
}
|
|
29
|
+
|
|
12
30
|
export const DEFAULT_ROLE_TURN_HOST = "pi" as const;
|
|
13
31
|
|
|
14
32
|
export type HostFamily = "acp" | "headless";
|
|
@@ -60,6 +60,14 @@ export function renderSystemPromptOverride(authority: {
|
|
|
60
60
|
return renderAgentStartMaterials(authority.body, authority.materials);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
export async function resolveBoundHostSessionId(
|
|
64
|
+
request: RoleTurnRequest,
|
|
65
|
+
identity: SessionIdentityAuthority,
|
|
66
|
+
): Promise<string | undefined> {
|
|
67
|
+
const explicit = request.continuation.kind === "resume" ? request.continuation.hostSessionId : undefined;
|
|
68
|
+
return explicit !== undefined && explicit !== "" ? explicit : identity.load(request.principal);
|
|
69
|
+
}
|
|
70
|
+
|
|
63
71
|
export type SessionIdentityAuthority = Readonly<{
|
|
64
72
|
load(principal: RoleTurnRequest["principal"]): Promise<string | undefined>;
|
|
65
73
|
bind(principal: RoleTurnRequest["principal"], sessionId: string): Promise<void>;
|
|
@@ -2,7 +2,7 @@ import { accessSync, constants, statSync } from "node:fs";
|
|
|
2
2
|
import { lstat, mkdir, symlink } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
|
-
import { HEADLESS_HOST_DESCRIPTIONS, HOST_DESCRIPTIONS } from "../host-descriptions.ts";
|
|
5
|
+
import { HEADLESS_HOST_DESCRIPTIONS, HOST_DESCRIPTIONS, resolveHostBinary } from "../host-descriptions.ts";
|
|
6
6
|
import { packagedMethodSkillNames } from "../packaged-role-registry.ts";
|
|
7
7
|
|
|
8
8
|
/** Install source only. Which names are required comes from the role registry. */
|
|
@@ -126,11 +126,11 @@ export async function runMachineSkillSetup(home: string, stdout: (text: string)
|
|
|
126
126
|
|
|
127
127
|
for (const host of ["claude-code", "hermes"] as const) {
|
|
128
128
|
const root = linkedSkillRoot(home, host)!;
|
|
129
|
-
const
|
|
130
|
-
? HEADLESS_HOST_DESCRIPTIONS.claude
|
|
131
|
-
: HOST_DESCRIPTIONS.hermes
|
|
129
|
+
const description = host === "claude-code"
|
|
130
|
+
? HEADLESS_HOST_DESCRIPTIONS.claude!
|
|
131
|
+
: HOST_DESCRIPTIONS.hermes!;
|
|
132
132
|
try {
|
|
133
|
-
accessSync(
|
|
133
|
+
accessSync(resolveHostBinary(description, home), constants.X_OK);
|
|
134
134
|
} catch (error) {
|
|
135
135
|
if (["ENOENT", "ENOTDIR", "EACCES"].includes((error as NodeJS.ErrnoException).code ?? "")) continue;
|
|
136
136
|
throw error;
|