@akagilnc/pi-workflow-roles 0.1.5303 → 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 +577 -604
- package/dist/analyst-ledger.js +16 -1
- package/dist/analyst-metric-families/acceptance-success-rework.js +16 -15
- package/dist/analyst-metric-families/b2-frame-buckets-actions.js +3 -8
- package/dist/analyst-metric-families/gate-cycles.js +2 -8
- package/dist/analyst-metric-families/leg-wall-clock.js +4 -13
- package/dist/analyst-metric-families/round-timeline.js +1 -4
- package/dist/analyst-model-groups.js +1 -7
- package/dist/analyst-page.js +4 -13
- package/dist/headless-host/description.js +0 -11
- package/dist/headless-host/production-host.js +679 -718
- 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 +291 -313
- 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/analyst-ledger.ts +20 -1
- package/src/analyst-metric-families/acceptance-success-rework.ts +5 -15
- package/src/analyst-metric-families/b2-frame-buckets-actions.ts +3 -7
- package/src/analyst-metric-families/gate-cycles.ts +2 -8
- package/src/analyst-metric-families/leg-wall-clock.ts +4 -16
- package/src/analyst-metric-families/round-timeline.ts +1 -5
- package/src/analyst-model-groups.ts +1 -10
- package/src/analyst-page.ts +4 -16
- 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
|
@@ -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;
|