@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.
Files changed (35) hide show
  1. package/dist/acp-host/description.js +0 -11
  2. package/dist/acp-host/production-host.js +577 -604
  3. package/dist/analyst-ledger.js +16 -1
  4. package/dist/analyst-metric-families/acceptance-success-rework.js +16 -15
  5. package/dist/analyst-metric-families/b2-frame-buckets-actions.js +3 -8
  6. package/dist/analyst-metric-families/gate-cycles.js +2 -8
  7. package/dist/analyst-metric-families/leg-wall-clock.js +4 -13
  8. package/dist/analyst-metric-families/round-timeline.js +1 -4
  9. package/dist/analyst-model-groups.js +1 -7
  10. package/dist/analyst-page.js +4 -13
  11. package/dist/headless-host/description.js +0 -11
  12. package/dist/headless-host/production-host.js +679 -718
  13. package/dist/host-descriptions.js +13 -0
  14. package/dist/prepared-role-turn.js +4 -0
  15. package/dist/public-cli/machine-method-skills.js +5 -5
  16. package/dist/public-cli/main.js +291 -313
  17. package/package.json +1 -1
  18. package/src/acp-host/description.ts +2 -12
  19. package/src/acp-host/production-host.ts +3 -2
  20. package/src/acp-host/role-turn-host.ts +7 -42
  21. package/src/analyst-ledger.ts +20 -1
  22. package/src/analyst-metric-families/acceptance-success-rework.ts +5 -15
  23. package/src/analyst-metric-families/b2-frame-buckets-actions.ts +3 -7
  24. package/src/analyst-metric-families/gate-cycles.ts +2 -8
  25. package/src/analyst-metric-families/leg-wall-clock.ts +4 -16
  26. package/src/analyst-metric-families/round-timeline.ts +1 -5
  27. package/src/analyst-model-groups.ts +1 -10
  28. package/src/analyst-page.ts +4 -16
  29. package/src/external-host-turn-loop.ts +36 -16
  30. package/src/headless-host/description.ts +3 -18
  31. package/src/headless-host/production-host.ts +3 -2
  32. package/src/headless-host/role-turn-host.ts +10 -68
  33. package/src/host-descriptions.ts +18 -0
  34. package/src/prepared-role-turn.ts +8 -0
  35. 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 Claude host stdout into the result envelope.
97
- * Production uses `--output-format stream-json` (#811 live records); last-result
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 single = JSON.parse(trimmed) as unknown;
107
- if (isHeadlessResultCandidate(single)) return single;
86
+ const value = JSON.parse(trimmed) as unknown;
87
+ return isHeadlessResultCandidate(value) ? value : undefined;
108
88
  } catch {
109
- // fall through
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
- const explicitHostSessionId = request.continuation.kind === "resume"
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 = withCleanupFailure(outcome, cleanupError);
766
+ outcome = withExternalHostCleanupFailure(outcome, cleanupError, "HeadlessDisposeFailure");
825
767
  }
826
768
  }
827
769
  return outcome;
@@ -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 binaryFromHome = host === "claude-code"
130
- ? HEADLESS_HOST_DESCRIPTIONS.claude!.binaryFromHome
131
- : HOST_DESCRIPTIONS.hermes!.binaryFromHome;
129
+ const description = host === "claude-code"
130
+ ? HEADLESS_HOST_DESCRIPTIONS.claude!
131
+ : HOST_DESCRIPTIONS.hermes!;
132
132
  try {
133
- accessSync(join(home, ...binaryFromHome), constants.X_OK);
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;