@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.
Files changed (42) hide show
  1. package/dist/acp-host/production-host.js +24606 -24532
  2. package/dist/engine-detour-tool.js +2 -2
  3. package/dist/engine-detour.js +23 -7
  4. package/dist/headless-host/production-host.js +4197 -4139
  5. package/dist/host-descriptions.js +5 -2
  6. package/dist/pi/role-turn-host.js +20 -92
  7. package/dist/public-cli/load-production-acp-host.js +1 -1
  8. package/dist/public-cli/load-production-external-host.js +10 -0
  9. package/dist/public-cli/load-production-headless-host.js +1 -1
  10. package/dist/public-cli/main.js +95 -102
  11. package/dist/public-role-summons.js +7 -49
  12. package/dist/reviewer-agent.js +1 -0
  13. package/dist/reviewer-child-executor.js +3 -2
  14. package/dist/role-activation-flags.js +50 -0
  15. package/dist/role-runtime.js +8 -3
  16. package/package.json +1 -1
  17. package/scripts/build-package.mjs +2 -2
  18. package/src/acp-host/production-host.ts +31 -94
  19. package/src/acp-host/role-turn-host.ts +178 -263
  20. package/src/diagnostic-tail.ts +18 -0
  21. package/src/engine-detour-tool.ts +5 -4
  22. package/src/engine-detour.ts +44 -9
  23. package/src/external-host-turn-loop.ts +172 -0
  24. package/src/headless-host/production-host.ts +10 -7
  25. package/src/headless-host/role-turn-host.ts +271 -263
  26. package/src/host-descriptions.ts +5 -2
  27. package/src/host-session-record.ts +41 -0
  28. package/src/pi/role-turn-host.ts +18 -93
  29. package/src/prepared-role-turn.ts +68 -0
  30. package/src/public-cli/cli.ts +11 -20
  31. package/src/public-cli/load-production-acp-host.ts +2 -1
  32. package/src/public-cli/load-production-external-host.ts +17 -0
  33. package/src/public-cli/load-production-headless-host.ts +2 -1
  34. package/src/public-role-summons.ts +12 -69
  35. package/src/reviewer-agent.ts +12 -1
  36. package/src/reviewer-child-executor.ts +10 -2
  37. package/src/role-activation-flags.ts +53 -0
  38. package/src/{acp-host/role-envelope.ts → role-envelope.ts} +28 -84
  39. package/src/role-runtime-dependencies.ts +91 -0
  40. package/src/role-runtime.ts +14 -4
  41. package/src/{acp-host/session-identity.ts → session-identity.ts} +6 -6
  42. /package/src/{acp-host/mcp-relay.mjs → mcp-relay.mjs} +0 -0
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Middle external-host role-turn loop (#820 / ADR 0082).
3
+ * One copy: serial, prior-native, abort merge/race, 8-round closeRound retry,
4
+ * host-aborted, round-limit. Last hop = ExternalHostTurnDriver (four verbs).
5
+ */
6
+ import type {
7
+ RoleTurnHost,
8
+ RoleTurnKnownFailure,
9
+ RoleTurnRequest,
10
+ RoleTurnResult,
11
+ } from "./host-contracts.ts";
12
+
13
+ export const EXTERNAL_ROLE_TURN_ROUND_LIMIT = 8 as const;
14
+
15
+ export type ExternalPreparedTurn = Readonly<{
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
+ }>;
31
+
32
+ export type ExternalHostRoundOutcome =
33
+ | { readonly status: "delivered"; readonly stderr?: string }
34
+ | { readonly status: "terminal"; readonly result: RoleTurnResult };
35
+
36
+ export type ExternalHostTurnDriver = Readonly<{
37
+ readonly roundLimitName: string;
38
+ currentSessionId(): string | undefined;
39
+ runRound(input: {
40
+ readonly prompt: string;
41
+ readonly abortSignal: AbortSignal | undefined;
42
+ readonly attempt: number;
43
+ }): Promise<ExternalHostRoundOutcome>;
44
+ afterAccepted?(): Promise<void>;
45
+ afterRetry?(): void;
46
+ }>;
47
+
48
+ export function mergeRoleTurnAbortSignals(
49
+ prepared: AbortSignal | undefined,
50
+ request: AbortSignal | undefined,
51
+ ): AbortSignal | undefined {
52
+ if (request === undefined) return prepared;
53
+ if (prepared === undefined) return request;
54
+ return AbortSignal.any([prepared, request]);
55
+ }
56
+
57
+ export function promptWithPriorNativePaths(basePrompt: string, request: RoleTurnRequest): string {
58
+ if (request.continuation.kind !== "resume") return basePrompt;
59
+ const paths = request.hostTransition?.priorNativePaths;
60
+ if (paths === undefined || paths.length === 0) return basePrompt;
61
+ return `${basePrompt}\n${paths.join("\n")}`;
62
+ }
63
+
64
+ export function isHostAbortedError(error: unknown): boolean {
65
+ return typeof error === "object" && error !== null && (error as { code?: unknown }).code === "host-aborted";
66
+ }
67
+
68
+ export function hostAbortedError(message = "host aborted"): Error & { readonly code: "host-aborted" } {
69
+ return Object.assign(new Error(message), { code: "host-aborted" as const });
70
+ }
71
+
72
+ /** Race host work against envelope/parent abort (#593). */
73
+ export function raceAgainstHostAbort<T>(
74
+ work: Promise<T>,
75
+ abortSignal: AbortSignal | undefined,
76
+ message = "host aborted",
77
+ ): Promise<T> {
78
+ if (abortSignal?.aborted) return Promise.reject(hostAbortedError(message));
79
+ if (abortSignal === undefined) return work;
80
+ return new Promise<T>((resolve, reject) => {
81
+ let settled = false;
82
+ const onAbort = (): void => {
83
+ if (settled) return;
84
+ settled = true;
85
+ work.catch(() => {});
86
+ reject(hostAbortedError(message));
87
+ };
88
+ abortSignal.addEventListener("abort", onAbort, { once: true });
89
+ work.then(
90
+ (value) => {
91
+ if (settled) return;
92
+ settled = true;
93
+ abortSignal.removeEventListener("abort", onAbort);
94
+ resolve(value);
95
+ },
96
+ (error) => {
97
+ if (settled) return;
98
+ settled = true;
99
+ abortSignal.removeEventListener("abort", onAbort);
100
+ reject(error);
101
+ },
102
+ );
103
+ });
104
+ }
105
+
106
+ function asFailure(knownFailure: RoleTurnKnownFailure, stderr = ""): RoleTurnResult {
107
+ return { code: null, stderr, timedOut: false, knownFailure };
108
+ }
109
+
110
+ async function settleHostAborted(
111
+ prepared: ExternalPreparedTurn,
112
+ sessionId: string | undefined,
113
+ ): Promise<RoleTurnResult> {
114
+ const closure = await prepared.closeRound();
115
+ if ("failure" in closure) return asFailure(closure.failure);
116
+ return asFailure({
117
+ cause: "session",
118
+ identity: { name: "HostAborted", code: "host-aborted" },
119
+ ...(sessionId === undefined ? {} : { details: { sessionId } }),
120
+ });
121
+ }
122
+
123
+ /** Shared external-host retry/resume loop. Caller owns session open + teardown. */
124
+ export async function driveExternalRoleTurnRounds(
125
+ prepared: ExternalPreparedTurn,
126
+ request: RoleTurnRequest,
127
+ driver: ExternalHostTurnDriver,
128
+ ): Promise<RoleTurnResult> {
129
+ let prompt = promptWithPriorNativePaths(prepared.prompt, request);
130
+ const abortSignal = mergeRoleTurnAbortSignals(prepared.abortSignal, request.signal);
131
+
132
+ for (let attempt = 0; attempt < EXTERNAL_ROLE_TURN_ROUND_LIMIT; attempt += 1) {
133
+ if (abortSignal?.aborted) return settleHostAborted(prepared, driver.currentSessionId());
134
+
135
+ let round: ExternalHostRoundOutcome;
136
+ try {
137
+ round = await driver.runRound({ prompt, abortSignal, attempt });
138
+ } catch (error) {
139
+ if (isHostAbortedError(error)) return settleHostAborted(prepared, driver.currentSessionId());
140
+ throw error;
141
+ }
142
+ if (round.status === "terminal") return round.result;
143
+
144
+ const closure = await prepared.closeRound();
145
+ if (closure.accepted) {
146
+ await driver.afterAccepted?.();
147
+ return { code: 0, stderr: "", timedOut: false };
148
+ }
149
+ if ("failure" in closure) return asFailure(closure.failure, round.stderr ?? "");
150
+ prompt = closure.retry.message;
151
+ driver.afterRetry?.();
152
+ }
153
+
154
+ return asFailure({
155
+ cause: "output",
156
+ identity: { name: driver.roundLimitName, code: "round-retry-limit" },
157
+ ...(driver.currentSessionId() === undefined ? {} : { details: { sessionId: driver.currentSessionId() } }),
158
+ });
159
+ }
160
+
161
+ export function createSerializedRoleTurnHost(
162
+ execute: (request: RoleTurnRequest) => Promise<RoleTurnResult>,
163
+ ): RoleTurnHost {
164
+ let serial = Promise.resolve();
165
+ return {
166
+ executeTurn(request) {
167
+ const execution = serial.then(() => execute(request));
168
+ serial = execution.then(() => undefined, () => undefined);
169
+ return execution;
170
+ },
171
+ };
172
+ }
@@ -12,9 +12,9 @@
12
12
  import { randomUUID } from "node:crypto";
13
13
 
14
14
  import type { DurablePrincipalAuthority, RoleTurnHost } from "../host-contracts.ts";
15
- import { createAcpRoleRuntimeDependencies } from "../acp-host/production-host.ts";
16
- import { prepareAcpRoleEnvelope } from "../acp-host/role-envelope.ts";
17
- import { createAcpSessionIdentityAuthority } from "../acp-host/session-identity.ts";
15
+ import { createRoleRuntimeDependencies } from "../role-runtime-dependencies.ts";
16
+ import { prepareRoleEnvelope } from "../role-envelope.ts";
17
+ import { createSessionIdentityAuthority } from "../session-identity.ts";
18
18
  import { resolveHeadlessBinary, type HeadlessHostDescription } from "./description.ts";
19
19
  import { createHeadlessRoleTurnHost } from "./role-turn-host.ts";
20
20
 
@@ -22,6 +22,8 @@ export type ProductionHeadlessHostOptions = Readonly<{
22
22
  packageRoot: string;
23
23
  principalAuthority: DurablePrincipalAuthority;
24
24
  description: HeadlessHostDescription;
25
+ /** Seat-table host key (e.g. claude). */
26
+ hostName: string;
25
27
  }>;
26
28
 
27
29
  /**
@@ -32,16 +34,17 @@ export type ProductionHeadlessHostOptions = Readonly<{
32
34
  export function createProductionHeadlessRoleTurnHost(
33
35
  options: ProductionHeadlessHostOptions,
34
36
  ): RoleTurnHost {
35
- const { packageRoot, principalAuthority, description } = options;
36
- const sessionIdentity = createAcpSessionIdentityAuthority(
37
+ const { packageRoot, principalAuthority, description, hostName } = options;
38
+ const sessionIdentity = createSessionIdentityAuthority(
37
39
  principalAuthority,
38
40
  description.sessionBindingFile,
39
41
  );
40
- const roleRuntimeDependencies = createAcpRoleRuntimeDependencies(packageRoot);
42
+ const roleRuntimeDependencies = createRoleRuntimeDependencies(packageRoot);
41
43
 
42
44
  const innerFor = (operatorHome: string): RoleTurnHost =>
43
45
  createHeadlessRoleTurnHost({
44
46
  description,
47
+ hostName,
45
48
  sessionIdentity,
46
49
  binary: resolveHeadlessBinary(description, operatorHome),
47
50
  env: {
@@ -49,7 +52,7 @@ export function createProductionHeadlessRoleTurnHost(
49
52
  AK_PACKAGE_ROOT: packageRoot,
50
53
  },
51
54
  prepare: (request) =>
52
- prepareAcpRoleEnvelope({
55
+ prepareRoleEnvelope({
53
56
  request,
54
57
  dependencies: roleRuntimeDependencies,
55
58
  sessionFile: sessionIdentity.resolveSessionFile(request.principal),