@gh-symphony/cli 0.0.20 → 0.0.22

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 (40) hide show
  1. package/README.md +66 -2
  2. package/dist/chunk-2TSM3INR.js +1085 -0
  3. package/dist/chunk-2UW7NQLX.js +684 -0
  4. package/dist/{chunk-MVRF7BES.js → chunk-36KYEDEO.js} +10 -1
  5. package/dist/{chunk-TILHWBP6.js → chunk-C67H3OUL.js} +239 -36
  6. package/dist/{chunk-C7G7RJ4G.js → chunk-DDL4BWSL.js} +1 -1
  7. package/dist/{chunk-XN5ABWZ6.js → chunk-DFLXHNYQ.js} +26 -30
  8. package/dist/{chunk-EKKT5USP.js → chunk-E7HYEEZD.js} +487 -133
  9. package/dist/chunk-EEQQWTXS.js +3257 -0
  10. package/dist/chunk-GDE6FYN4.js +26 -0
  11. package/dist/{chunk-Y6TYJMNT.js → chunk-GSX2FV3M.js} +10 -16
  12. package/dist/{chunk-RN2PACNV.js → chunk-HMLBBZNY.js} +731 -75
  13. package/dist/{chunk-5NV3LSAJ.js → chunk-IWFX2FMA.js} +5 -1
  14. package/dist/{chunk-HZVDTAPS.js → chunk-PUDXVBSN.js} +1549 -1458
  15. package/dist/{chunk-ROGRTUFI.js → chunk-QIRE2VXS.js} +14 -3
  16. package/dist/{chunk-3AWF54PI.js → chunk-ZHOKYUO3.js} +394 -42
  17. package/dist/{config-cmd-DNXNL26Z.js → config-cmd-Z3A7V6NC.js} +1 -1
  18. package/dist/{doctor-IYHCFXOZ.js → doctor-EJUMPBMW.js} +105 -40
  19. package/dist/index.js +112 -24
  20. package/dist/{init-KZT6YNOH.js → init-54HMKNYI.js} +8 -3
  21. package/dist/{logs-6JKKYDGJ.js → logs-GTZ4U5JE.js} +2 -2
  22. package/dist/project-RMYMZSFV.js +25 -0
  23. package/dist/{recover-5KQI7WH5.js → recover-LTLKMTRX.js} +7 -5
  24. package/dist/repo-WI7GF6XQ.js +749 -0
  25. package/dist/{run-ETC5UTRA.js → run-IHN3ZL35.js} +21 -7
  26. package/dist/{setup-VWB7RZUQ.js → setup-TZJSM3QV.js} +53 -14
  27. package/dist/start-RTAHQMR2.js +19 -0
  28. package/dist/status-F4D52OVK.js +12 -0
  29. package/dist/stop-MDKMJPVR.js +10 -0
  30. package/dist/{upgrade-3YNF3VKY.js → upgrade-O33S2SJK.js} +2 -2
  31. package/dist/{version-NUBTTOG7.js → version-CW54Q7BK.js} +1 -1
  32. package/dist/worker-entry.js +848 -693
  33. package/dist/{workflow-TBIFY5MO.js → workflow-L3KT6HB7.js} +177 -11
  34. package/package.json +4 -2
  35. package/dist/chunk-M3IFVLQS.js +0 -1155
  36. package/dist/project-UUVHS3ZR.js +0 -22
  37. package/dist/repo-HDDE7OUI.js +0 -321
  38. package/dist/start-ENFLZUI6.js +0 -16
  39. package/dist/status-QSCFVGRQ.js +0 -11
  40. package/dist/stop-7MFCBQVW.js +0 -9
@@ -1,448 +1,31 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ getCodexObservabilityEventName,
4
+ launchCodexAppServer,
5
+ loadLauncherEnvironment,
6
+ normalizeCodexRuntimeEvents,
7
+ prepareCodexRuntimePlan,
8
+ resolveLocalRuntimeLaunchConfig
9
+ } from "./chunk-2UW7NQLX.js";
10
+ import {
11
+ DEFAULT_AGENT_INPUT_REQUIRED_REASON,
3
12
  classifySessionExit,
13
+ createClaudePrintRuntimeAdapter,
14
+ extractEnvForClaude,
15
+ formatClaudePreflightText,
16
+ isClaudeRuntimeCommand,
4
17
  parseWorkflowMarkdown,
5
- readEnvFile
6
- } from "./chunk-M3IFVLQS.js";
18
+ resolveClaudeCommandBinary,
19
+ resolveWorkflowRuntimeCommand,
20
+ runClaudePreflight
21
+ } from "./chunk-EEQQWTXS.js";
7
22
 
8
- // ../worker/dist/index.js
23
+ // ../worker/src/index.ts
9
24
  import { spawn as spawn2 } from "child_process";
10
- import { readFile as readFile2 } from "fs/promises";
11
- import { join as join3 } from "path";
12
-
13
- // ../runtime-codex/dist/runtime.js
14
- import { spawn } from "child_process";
15
- import { copyFile, mkdir, writeFile } from "fs/promises";
16
- import { join } from "path";
17
- import { homedir } from "os";
18
- import { fileURLToPath } from "url";
19
- var DEFAULT_GITHUB_GRAPHQL_API_URL = "https://api.github.com/graphql";
20
- var DEFAULT_GITHUB_GIT_HOST = "github.com";
21
- var DEFAULT_GITHUB_GIT_USERNAME = "x-access-token";
22
- var AgentRuntimeResolutionError = class extends Error {
23
- };
24
- function createGitHubGraphQLToolDefinition(config) {
25
- return {
26
- name: "github_graphql",
27
- description: "Execute GitHub GraphQL queries for the active workspace so the agent can mutate project and issue state directly.",
28
- command: "node",
29
- args: [fileURLToPath(new URL("./github-graphql-mcp-server.js", import.meta.url))],
30
- env: {
31
- GITHUB_GRAPHQL_API_URL: config.githubGraphqlApiUrl ?? DEFAULT_GITHUB_GRAPHQL_API_URL,
32
- ...config.githubToken ? {
33
- GITHUB_GRAPHQL_TOKEN: config.githubToken
34
- } : {},
35
- ...config.githubTokenBrokerUrl ? {
36
- GITHUB_TOKEN_BROKER_URL: config.githubTokenBrokerUrl
37
- } : {},
38
- ...config.githubTokenBrokerSecret ? {
39
- GITHUB_TOKEN_BROKER_SECRET: config.githubTokenBrokerSecret
40
- } : {},
41
- ...config.githubTokenCachePath ? {
42
- GITHUB_TOKEN_CACHE_PATH: config.githubTokenCachePath
43
- } : {},
44
- ...config.githubProjectId ? {
45
- GITHUB_PROJECT_ID: config.githubProjectId
46
- } : {}
47
- },
48
- inputSchema: {
49
- type: "object",
50
- properties: {
51
- query: {
52
- type: "string",
53
- description: "GraphQL query or mutation document."
54
- },
55
- variables: {
56
- type: "object",
57
- description: "Variables for the GraphQL document."
58
- },
59
- operationName: {
60
- type: "string",
61
- description: "Optional GraphQL operation name."
62
- }
63
- },
64
- required: ["query"],
65
- additionalProperties: false
66
- }
67
- };
68
- }
69
- function buildCodexRuntimePlan(config) {
70
- const tool = createGitHubGraphQLToolDefinition(config);
71
- const gitCredentialHelper = createGitCredentialHelperEnvironment(config);
72
- const shellCmd = (() => {
73
- const cmd = config.agentCommand ?? "codex app-server";
74
- return cmd.startsWith("bash -lc ") ? cmd.slice("bash -lc ".length) : cmd;
75
- })();
76
- return {
77
- cwd: config.workingDirectory,
78
- command: "bash",
79
- args: ["-lc", shellCmd],
80
- env: {
81
- ...process.env,
82
- ...config.extraEnv,
83
- ...config.agentEnv,
84
- CODEX_PROJECT_ID: config.projectId,
85
- GITHUB_PROJECT_ID: config.githubProjectId ?? "",
86
- GITHUB_GRAPHQL_TOOL_NAME: tool.name,
87
- GITHUB_GRAPHQL_TOOL_COMMAND: [tool.command, ...tool.args].join(" "),
88
- // Point codex to an isolated config dir so personal MCPs (playwright,
89
- // chrome-devtools, context7, etc.) from the operator's ~/.codex/config.toml
90
- // are not loaded and do not confuse the implementation agent.
91
- CODEX_HOME: join(config.workingDirectory, ".codex-agent"),
92
- ...gitCredentialHelper,
93
- ...tool.env
94
- },
95
- tools: [tool]
96
- };
97
- }
98
- function launchCodexAppServer(plan, spawnImpl = spawn) {
99
- return spawnImpl(plan.command, plan.args, {
100
- cwd: plan.cwd,
101
- env: plan.env,
102
- stdio: "pipe"
103
- });
104
- }
105
- async function prepareCodexRuntimePlan(config, dependencies = {}) {
106
- const agentEnv = await resolveAgentRuntimeEnvironment(config, dependencies);
107
- const codexHomeDir = join(config.workingDirectory, ".codex-agent");
108
- const mkdirImpl = dependencies.mkdirImpl ?? mkdir;
109
- await mkdirImpl(codexHomeDir, { recursive: true });
110
- const writeFileImpl = dependencies.writeFileImpl ?? writeFile;
111
- await writeFileImpl(join(codexHomeDir, "config.toml"), "# Isolated agent config \u2014 no personal MCP servers\n", "utf8");
112
- const realCodexHome = process.env.CODEX_HOME ?? join(homedir(), ".codex");
113
- const copyFileImpl = dependencies.copyFileImpl ?? copyFile;
114
- try {
115
- await copyFileImpl(join(realCodexHome, "auth.json"), join(codexHomeDir, "auth.json"));
116
- } catch {
117
- }
118
- return buildCodexRuntimePlan({
119
- ...config,
120
- agentEnv
121
- });
122
- }
123
- function createGitCredentialHelperEnvironment(config) {
124
- return {
125
- GITHUB_GIT_HOST: DEFAULT_GITHUB_GIT_HOST,
126
- GITHUB_GIT_USERNAME: DEFAULT_GITHUB_GIT_USERNAME,
127
- GIT_TERMINAL_PROMPT: "0",
128
- GIT_CONFIG_COUNT: "1",
129
- GIT_CONFIG_KEY_0: "credential.helper",
130
- GIT_CONFIG_VALUE_0: `!node ${fileURLToPath(new URL("./git-credential-helper.js", import.meta.url))}`,
131
- ...config.githubToken ? {
132
- GITHUB_GRAPHQL_TOKEN: config.githubToken
133
- } : {},
134
- ...config.githubTokenBrokerUrl ? {
135
- GITHUB_TOKEN_BROKER_URL: config.githubTokenBrokerUrl
136
- } : {},
137
- ...config.githubTokenBrokerSecret ? {
138
- GITHUB_TOKEN_BROKER_SECRET: config.githubTokenBrokerSecret
139
- } : {},
140
- ...config.githubTokenCachePath ? {
141
- GITHUB_TOKEN_CACHE_PATH: config.githubTokenCachePath
142
- } : {}
143
- };
144
- }
145
- async function resolveAgentRuntimeEnvironment(config, dependencies = {}) {
146
- if (config.agentEnv) {
147
- return config.agentEnv;
148
- }
149
- if (!config.agentCredentialBrokerUrl || !config.agentCredentialBrokerSecret) {
150
- return {};
151
- }
152
- const fetchImpl = dependencies.fetchImpl ?? fetch;
153
- const response = await fetchImpl(config.agentCredentialBrokerUrl, {
154
- method: "POST",
155
- headers: {
156
- accept: "application/json",
157
- authorization: `Bearer ${config.agentCredentialBrokerSecret}`
158
- }
159
- });
160
- const payload = await response.json();
161
- if (!response.ok || !payload.env || Object.keys(payload.env).length === 0) {
162
- throw new AgentRuntimeResolutionError(payload.error ?? `Agent credential broker request failed with status ${response.status}.`);
163
- }
164
- if (config.agentCredentialCachePath) {
165
- const writeFileImpl = dependencies.writeFileImpl ?? writeFile;
166
- await writeFileImpl(config.agentCredentialCachePath, JSON.stringify(payload), "utf8");
167
- }
168
- return payload.env;
169
- }
170
-
171
- // ../runtime-codex/dist/launcher.js
172
- import { dirname, resolve } from "path";
173
- import { fileURLToPath as fileURLToPath2 } from "url";
174
- var LocalRuntimeLauncherError = class extends Error {
175
- };
176
- function resolveLocalRuntimeLaunchConfig(env = process.env) {
177
- const projectId = env.PROJECT_ID ?? env.CODEX_PROJECT_ID;
178
- const workingDirectory = env.WORKING_DIRECTORY;
179
- if (!projectId) {
180
- throw new LocalRuntimeLauncherError("PROJECT_ID or CODEX_PROJECT_ID is required.");
181
- }
182
- if (!workingDirectory) {
183
- throw new LocalRuntimeLauncherError("WORKING_DIRECTORY is required.");
184
- }
185
- return {
186
- projectId,
187
- workingDirectory,
188
- githubToken: env.GITHUB_GRAPHQL_TOKEN,
189
- githubTokenBrokerUrl: env.GITHUB_TOKEN_BROKER_URL,
190
- githubTokenBrokerSecret: env.GITHUB_TOKEN_BROKER_SECRET,
191
- githubTokenCachePath: env.GITHUB_TOKEN_CACHE_PATH,
192
- agentEnv: readDirectAgentEnvironment(env),
193
- agentCredentialBrokerUrl: env.AGENT_CREDENTIAL_BROKER_URL,
194
- agentCredentialBrokerSecret: env.AGENT_CREDENTIAL_BROKER_SECRET,
195
- agentCredentialCachePath: env.AGENT_CREDENTIAL_CACHE_PATH,
196
- githubProjectId: env.GITHUB_PROJECT_ID,
197
- githubGraphqlApiUrl: env.GITHUB_GRAPHQL_API_URL,
198
- agentCommand: env.SYMPHONY_AGENT_COMMAND
199
- };
200
- }
201
- async function runLocalRuntimeLauncher(env = process.env) {
202
- const launcherEnv2 = loadLauncherEnvironment(env);
203
- const config = resolveLocalRuntimeLaunchConfig(launcherEnv2);
204
- const plan = await prepareCodexRuntimePlan(config);
205
- emitLaunchSummary(config);
206
- const child = launchCodexAppServer(plan);
207
- process.stdout.write(`[worker] codex app-server started (pid: ${child.pid ?? "unknown"})
208
- `);
209
- child.stdout?.pipe(process.stdout);
210
- child.stderr?.pipe(process.stderr);
211
- return await waitForChildProcess(child);
212
- }
213
- function loadLauncherEnvironment(env = process.env, cwd = process.cwd()) {
214
- const mergedEnv = {
215
- ...readEnvFile(resolve(dirname(fileURLToPath2(import.meta.url)), "..", ".env")),
216
- ...readEnvFile(resolve(cwd, ".env")),
217
- ...env
218
- };
219
- return mergedEnv;
220
- }
221
- function readDirectAgentEnvironment(env) {
222
- const agentEnv = {};
223
- for (const key of [
224
- "OPENAI_API_KEY",
225
- "OPENAI_BASE_URL",
226
- "OPENAI_ORG_ID",
227
- "OPENAI_PROJECT"
228
- ]) {
229
- const value = env[key];
230
- if (value) {
231
- agentEnv[key] = value;
232
- }
233
- }
234
- return Object.keys(agentEnv).length ? agentEnv : void 0;
235
- }
236
- function waitForChildProcess(child) {
237
- return new Promise((resolve2, reject) => {
238
- child.once("error", reject);
239
- child.once("exit", (code, signal) => {
240
- if (signal) {
241
- reject(new LocalRuntimeLauncherError(`codex app-server exited on ${signal}.`));
242
- return;
243
- }
244
- resolve2(code ?? 0);
245
- });
246
- });
247
- }
248
- async function main() {
249
- const exitCode = await runLocalRuntimeLauncher(process.env);
250
- process.exitCode = exitCode;
251
- }
252
- if (import.meta.url === new URL(process.argv[1] ?? "", "file:").href) {
253
- main().catch((error) => {
254
- const message = error instanceof Error ? error.message : "Unknown error";
255
- process.stderr.write(`${message}
256
- `);
257
- process.exitCode = 1;
258
- });
259
- }
260
- function emitLaunchSummary(config) {
261
- const githubAuthMode = config.githubToken ? "direct token" : config.githubTokenBrokerUrl && config.githubTokenBrokerSecret ? "broker" : "missing";
262
- const agentAuthMode = config.agentEnv?.OPENAI_API_KEY ? "direct env" : config.agentCredentialBrokerUrl && config.agentCredentialBrokerSecret ? "broker" : "local codex auth or inherited environment";
263
- process.stdout.write([
264
- "[worker] starting local codex runtime",
265
- `[worker] project: ${config.projectId}`,
266
- `[worker] cwd: ${config.workingDirectory}`,
267
- `[worker] github project: ${config.githubProjectId ?? "(unset)"}`,
268
- `[worker] github auth: ${githubAuthMode}`,
269
- `[worker] agent auth: ${agentAuthMode}`,
270
- "[worker] note: codex app-server does not proactively read GitHub issues.",
271
- "[worker] note: it waits for a client request or tool invocation."
272
- ].join("\n") + "\n");
273
- }
274
-
275
- // ../runtime-codex/dist/github-graphql-tool.js
276
- import { readFile, writeFile as writeFile2 } from "fs/promises";
277
- var DEFAULT_GITHUB_GRAPHQL_API_URL2 = "https://api.github.com/graphql";
278
- var TOKEN_REUSE_WINDOW_MS = 60 * 1e3;
279
- async function executeGitHubGraphQL(invocation, config, fetchImpl = fetch) {
280
- const token = await resolveGitHubGraphQLToken(config, {
281
- fetchImpl
282
- });
283
- const response = await fetchImpl(config.apiUrl ?? DEFAULT_GITHUB_GRAPHQL_API_URL2, {
284
- method: "POST",
285
- headers: {
286
- "content-type": "application/json",
287
- authorization: `Bearer ${token}`
288
- },
289
- body: JSON.stringify(invocation)
290
- });
291
- const payload = await response.json();
292
- if (!response.ok) {
293
- throw new Error(`GitHub GraphQL request failed with status ${response.status}: ${JSON.stringify(payload)}`);
294
- }
295
- if (payload.errors?.length) {
296
- throw new Error(payload.errors.map((error) => error.message).join("; "));
297
- }
298
- return payload;
299
- }
300
- async function resolveGitHubGraphQLToken(config, dependencies = {}) {
301
- if (config.token) {
302
- return config.token;
303
- }
304
- if (!config.tokenBrokerUrl || !config.tokenBrokerSecret) {
305
- throw new Error("Either GITHUB_GRAPHQL_TOKEN or the runtime token broker configuration is required.");
306
- }
307
- const now = dependencies.now ?? /* @__PURE__ */ new Date();
308
- const readFileImpl = dependencies.readFileImpl ?? readFile;
309
- const writeFileImpl = dependencies.writeFileImpl ?? writeFile2;
310
- const cachedToken = config.tokenCachePath ? await readCachedToken(config.tokenCachePath, readFileImpl) : null;
311
- if (cachedToken && cachedToken.expiresAt.getTime() - now.getTime() > TOKEN_REUSE_WINDOW_MS) {
312
- return cachedToken.token;
313
- }
314
- const fetchImpl = dependencies.fetchImpl ?? fetch;
315
- const response = await fetchImpl(config.tokenBrokerUrl, {
316
- method: "POST",
317
- headers: {
318
- accept: "application/json",
319
- authorization: `Bearer ${config.tokenBrokerSecret}`
320
- }
321
- });
322
- const payload = await response.json();
323
- if (!response.ok || !payload.token || !payload.expiresAt) {
324
- throw new Error(payload.error ?? `Runtime token broker request failed with status ${response.status}.`);
325
- }
326
- if (config.tokenCachePath) {
327
- await writeFileImpl(config.tokenCachePath, JSON.stringify(payload), "utf8");
328
- }
329
- return payload.token;
330
- }
331
- async function readStdin() {
332
- const chunks = [];
333
- for await (const chunk of process.stdin) {
334
- chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
335
- }
336
- return Buffer.concat(chunks).toString("utf8");
337
- }
338
- async function main2() {
339
- const rawInput = await readStdin();
340
- const invocation = JSON.parse(rawInput);
341
- const result = await executeGitHubGraphQL(invocation, {
342
- token: process.env.GITHUB_GRAPHQL_TOKEN,
343
- apiUrl: process.env.GITHUB_GRAPHQL_API_URL,
344
- tokenBrokerUrl: process.env.GITHUB_TOKEN_BROKER_URL,
345
- tokenBrokerSecret: process.env.GITHUB_TOKEN_BROKER_SECRET,
346
- tokenCachePath: process.env.GITHUB_TOKEN_CACHE_PATH
347
- });
348
- process.stdout.write(`${JSON.stringify(result)}
349
- `);
350
- }
351
- if (import.meta.url === new URL(process.argv[1] ?? "", "file:").href) {
352
- main2().catch((error) => {
353
- const message = error instanceof Error ? error.message : "Unknown error";
354
- process.stderr.write(`${message}
355
- `);
356
- process.exitCode = 1;
357
- });
358
- }
359
- async function readCachedToken(path, readFileImpl) {
360
- try {
361
- const payload = JSON.parse(await readFileImpl(path, "utf8"));
362
- if (!payload.token || !payload.expiresAt) {
363
- return null;
364
- }
365
- return {
366
- token: payload.token,
367
- expiresAt: new Date(payload.expiresAt)
368
- };
369
- } catch {
370
- return null;
371
- }
372
- }
373
-
374
- // ../runtime-codex/dist/git-credential-helper.js
375
- var DEFAULT_GITHUB_GIT_HOST2 = "github.com";
376
- var DEFAULT_GITHUB_GIT_USERNAME2 = "x-access-token";
377
- async function resolveGitCredential(request, config, fetchImpl = fetch) {
378
- const requestHost = request.host?.trim();
379
- const requestProtocol = request.protocol?.trim();
380
- if (!requestHost || requestProtocol && requestProtocol !== "https") {
381
- return "";
382
- }
383
- const expectedHost = normalizeGitHost(config.gitHost ?? DEFAULT_GITHUB_GIT_HOST2);
384
- if (normalizeGitHost(requestHost) !== expectedHost) {
385
- return "";
386
- }
387
- const token = await resolveGitHubGraphQLToken(config, {
388
- fetchImpl
389
- });
390
- return formatGitCredentialResponse({
391
- protocol: requestProtocol || "https",
392
- host: requestHost,
393
- username: config.gitUsername ?? DEFAULT_GITHUB_GIT_USERNAME2,
394
- password: token
395
- });
396
- }
397
- function parseGitCredentialRequest(rawInput) {
398
- return rawInput.split("\n").map((line) => line.trim()).filter(Boolean).reduce((request, line) => {
399
- const separatorIndex = line.indexOf("=");
400
- if (separatorIndex === -1) {
401
- return request;
402
- }
403
- const key = line.slice(0, separatorIndex);
404
- const value = line.slice(separatorIndex + 1);
405
- request[key] = value;
406
- return request;
407
- }, {});
408
- }
409
- function formatGitCredentialResponse(value) {
410
- return `${Object.entries(value).map(([key, entry]) => `${key}=${entry}`).join("\n")}
411
-
412
- `;
413
- }
414
- async function readStdin2() {
415
- const chunks = [];
416
- for await (const chunk of process.stdin) {
417
- chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
418
- }
419
- return Buffer.concat(chunks).toString("utf8");
420
- }
421
- async function main3() {
422
- const request = parseGitCredentialRequest(await readStdin2());
423
- const response = await resolveGitCredential(request, {
424
- token: process.env.GITHUB_GRAPHQL_TOKEN,
425
- tokenBrokerUrl: process.env.GITHUB_TOKEN_BROKER_URL,
426
- tokenBrokerSecret: process.env.GITHUB_TOKEN_BROKER_SECRET,
427
- tokenCachePath: process.env.GITHUB_TOKEN_CACHE_PATH,
428
- gitHost: process.env.GITHUB_GIT_HOST,
429
- gitUsername: process.env.GITHUB_GIT_USERNAME
430
- });
431
- process.stdout.write(response);
432
- }
433
- if (import.meta.url === new URL(process.argv[1] ?? "", "file:").href) {
434
- main3().catch((error) => {
435
- const message = error instanceof Error ? error.message : "Unknown error";
436
- process.stderr.write(`${message}
437
- `);
438
- process.exitCode = 1;
439
- });
440
- }
441
- function normalizeGitHost(host) {
442
- return host.trim().toLowerCase();
443
- }
25
+ import { readFile } from "fs/promises";
26
+ import { join as join2 } from "path";
444
27
 
445
- // ../worker/dist/execution-phase.js
28
+ // ../worker/src/execution-phase.ts
446
29
  function resolveInitialExecutionPhase(input) {
447
30
  const { issueState, blockerCheckStates, activeStates } = input;
448
31
  if (!issueState) {
@@ -472,7 +55,7 @@ function resolveFinalExecutionPhase(input) {
472
55
  return resolvePausedExecutionPhase(input.currentPhase) ?? input.currentPhase;
473
56
  }
474
57
 
475
- // ../worker/dist/codex-policy.js
58
+ // ../worker/src/codex-policy.ts
476
59
  function resolveCodexPolicySettings(env) {
477
60
  return {
478
61
  approvalPolicy: env.SYMPHONY_APPROVAL_POLICY || "never",
@@ -481,7 +64,7 @@ function resolveCodexPolicySettings(env) {
481
64
  };
482
65
  }
483
66
 
484
- // ../worker/dist/convergence-detection.js
67
+ // ../worker/src/convergence-detection.ts
485
68
  import { spawnSync } from "child_process";
486
69
  var DEFAULT_MAX_NONPRODUCTIVE_TURNS = 3;
487
70
  function resolveMaxNonProductiveTurns(env) {
@@ -490,10 +73,14 @@ function resolveMaxNonProductiveTurns(env) {
490
73
  return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_MAX_NONPRODUCTIVE_TURNS;
491
74
  }
492
75
  function captureTurnWorkspaceSnapshot(cwd) {
493
- const result = spawnSync("git", ["status", "--porcelain=v1", "--untracked-files=all"], {
494
- cwd,
495
- encoding: "utf8"
496
- });
76
+ const result = spawnSync(
77
+ "git",
78
+ ["status", "--porcelain=v1", "--untracked-files=all"],
79
+ {
80
+ cwd,
81
+ encoding: "utf8"
82
+ }
83
+ );
497
84
  if (result.status !== 0) {
498
85
  return {
499
86
  fingerprint: null,
@@ -539,7 +126,153 @@ function normalizeError(value) {
539
126
  return normalized.length > 0 ? normalized : null;
540
127
  }
541
128
 
542
- // ../worker/dist/run-phase.js
129
+ // ../worker/src/non-codex-runtime.ts
130
+ import {
131
+ spawn
132
+ } from "child_process";
133
+ import { finished } from "stream/promises";
134
+ var CustomCommandWorkerRuntimeAdapter = class {
135
+ constructor(config, dependencies = {}) {
136
+ this.config = config;
137
+ this.dependencies = dependencies;
138
+ }
139
+ activeChild = null;
140
+ prepare() {
141
+ }
142
+ async spawnTurn(input) {
143
+ const command = this.config.command;
144
+ const args = [...this.config.args];
145
+ const cwd = input.cwd ?? this.config.workingDirectory;
146
+ const child = (this.dependencies.spawnImpl ?? spawn)(command, args, {
147
+ cwd,
148
+ env: {
149
+ ...this.config.env,
150
+ ...input.env,
151
+ SYMPHONY_RENDERED_PROMPT: input.prompt
152
+ },
153
+ stdio: "pipe"
154
+ });
155
+ this.activeChild = child;
156
+ this.config.onSpawned?.(child);
157
+ let stdout = "";
158
+ let stderr = "";
159
+ let spawnError;
160
+ child.stdout?.setEncoding("utf8");
161
+ child.stderr?.setEncoding("utf8");
162
+ child.stdout?.on("data", (chunk) => {
163
+ stdout += chunk;
164
+ });
165
+ child.stderr?.on("data", (chunk) => {
166
+ stderr += chunk;
167
+ });
168
+ child.once("error", (error) => {
169
+ spawnError = error.message;
170
+ });
171
+ if (child.stdin && !child.stdin.destroyed) {
172
+ child.stdin.end(input.prompt);
173
+ }
174
+ const { exitCode, signal } = await waitForChildExit(child);
175
+ await Promise.all([
176
+ child.stdout ? finished(child.stdout).catch(() => void 0) : void 0,
177
+ child.stderr ? finished(child.stderr).catch(() => void 0) : void 0
178
+ ]);
179
+ this.activeChild = null;
180
+ const result = exitCode === 0 && signal === null && !spawnError ? "success" : "process-error";
181
+ return {
182
+ command,
183
+ args,
184
+ cwd,
185
+ stdout,
186
+ stderr,
187
+ exitCode,
188
+ signal,
189
+ result,
190
+ errorMessage: spawnError
191
+ };
192
+ }
193
+ onEvent(_handler) {
194
+ return () => {
195
+ };
196
+ }
197
+ resolveCredentials(brokerResponse) {
198
+ if (!this.config.authEnvKey) {
199
+ return {};
200
+ }
201
+ return extractEnvForClaude(brokerResponse.env, this.config.authEnvKey);
202
+ }
203
+ shutdown() {
204
+ this.stopActiveChild();
205
+ }
206
+ cancel() {
207
+ this.stopActiveChild();
208
+ }
209
+ stopActiveChild() {
210
+ if (!this.activeChild || this.activeChild.killed) {
211
+ this.activeChild = null;
212
+ return;
213
+ }
214
+ this.activeChild.kill("SIGTERM");
215
+ this.activeChild = null;
216
+ }
217
+ };
218
+ function createWorkerNonCodexRuntimeAdapter(workflow, context) {
219
+ const runtime = workflow.runtime;
220
+ if (!runtime || runtime.kind === "codex-app-server") {
221
+ throw new Error(
222
+ "Worker non-Codex runtime adapter requested for a Codex runtime."
223
+ );
224
+ }
225
+ switch (runtime.kind) {
226
+ case "claude-print":
227
+ return createClaudePrintRuntimeAdapter(
228
+ {
229
+ workingDirectory: context.workingDirectory,
230
+ runtimeRoot: context.runtimeRoot,
231
+ runtimeDirectory: context.runtimeDirectory,
232
+ command: runtime.command,
233
+ args: runtime.args,
234
+ env: context.env,
235
+ authEnvKey: runtime.auth.env ?? void 0,
236
+ isolation: {
237
+ bare: runtime.isolation.bare,
238
+ strictMcpConfig: runtime.isolation.strictMcpConfig
239
+ }
240
+ },
241
+ {
242
+ ...context.claudeDependencies,
243
+ onSpawned: context.onSpawned
244
+ }
245
+ );
246
+ case "custom":
247
+ return new CustomCommandWorkerRuntimeAdapter(
248
+ {
249
+ workingDirectory: context.workingDirectory,
250
+ command: runtime.command,
251
+ args: runtime.args,
252
+ env: context.env,
253
+ authEnvKey: runtime.auth.env ?? void 0,
254
+ onSpawned: context.onSpawned
255
+ },
256
+ context.customDependencies
257
+ );
258
+ }
259
+ }
260
+ function waitForChildExit(child) {
261
+ return new Promise((resolve) => {
262
+ let settled = false;
263
+ const settle = (exitCode, signal) => {
264
+ if (settled) {
265
+ return;
266
+ }
267
+ settled = true;
268
+ resolve({ exitCode, signal });
269
+ };
270
+ child.once("exit", settle);
271
+ child.once("error", () => settle(1, null));
272
+ });
273
+ }
274
+
275
+ // ../worker/src/run-phase.ts
543
276
  var TERMINAL_RUN_PHASES = /* @__PURE__ */ new Set([
544
277
  "succeeded",
545
278
  "failed",
@@ -554,7 +287,16 @@ function resolveExitRunPhase(currentRunPhase, exit) {
554
287
  return exit.code === 0 && !exit.signal ? "succeeded" : "failed";
555
288
  }
556
289
 
557
- // ../worker/dist/thread-resume.js
290
+ // ../worker/src/runtime-routing.ts
291
+ function resolveWorkerRuntimeRoute(workflow) {
292
+ const kind = workflow.runtime?.kind;
293
+ if (!kind || kind === "codex-app-server") {
294
+ return "codex-app-server";
295
+ }
296
+ return "runtime-adapter";
297
+ }
298
+
299
+ // ../worker/src/thread-resume.ts
558
300
  var DEFAULT_CONTINUATION_GUIDANCE = "Continue working on the issue. Review your progress and complete any remaining tasks.";
559
301
  function parseNonNegativeInteger(value) {
560
302
  const parsed = typeof value === "number" ? value : Number.parseInt(value ?? "", 10);
@@ -563,11 +305,17 @@ function parseNonNegativeInteger(value) {
563
305
  }
564
306
  return Math.floor(parsed);
565
307
  }
566
- function buildContinuationTurnInput({ continuationGuidance, lastTurnSummary, cumulativeTurnCount = 0 }) {
308
+ function buildContinuationTurnInput({
309
+ continuationGuidance,
310
+ lastTurnSummary,
311
+ cumulativeTurnCount = 0
312
+ }) {
567
313
  const template = continuationGuidance?.trim() || DEFAULT_CONTINUATION_GUIDANCE;
568
314
  return renderContinuationGuidance(template, {
569
315
  lastTurnSummary: normalizeContinuationVariable(lastTurnSummary) ?? "No previous turn summary was captured.",
570
- cumulativeTurnCount: String(Math.max(0, parseNonNegativeInteger(cumulativeTurnCount)))
316
+ cumulativeTurnCount: String(
317
+ Math.max(0, parseNonNegativeInteger(cumulativeTurnCount))
318
+ )
571
319
  });
572
320
  }
573
321
  function normalizeContinuationVariable(value) {
@@ -576,7 +324,9 @@ function normalizeContinuationVariable(value) {
576
324
  }
577
325
  function renderContinuationGuidance(template, variables) {
578
326
  if (template.includes("{%") || template.includes("%}")) {
579
- throw new Error("template_parse_error: continuation guidance does not support Liquid tags.");
327
+ throw new Error(
328
+ "template_parse_error: continuation guidance does not support Liquid tags."
329
+ );
580
330
  }
581
331
  let rendered = "";
582
332
  let lastIndex = 0;
@@ -587,7 +337,9 @@ function renderContinuationGuidance(template, variables) {
587
337
  const index = match.index ?? 0;
588
338
  rendered += template.slice(lastIndex, index);
589
339
  if (!(expression in variables)) {
590
- throw new Error(`template_render_error: unsupported continuation guidance variable '${expression}'.`);
340
+ throw new Error(
341
+ `template_render_error: unsupported continuation guidance variable '${expression}'.`
342
+ );
591
343
  }
592
344
  rendered += variables[expression] ?? "";
593
345
  lastIndex = index + matchedText.length;
@@ -595,12 +347,14 @@ function renderContinuationGuidance(template, variables) {
595
347
  rendered += template.slice(lastIndex);
596
348
  const strayLiquidExpression = rendered.match(/\{\{[^}]*\}\}/);
597
349
  if (strayLiquidExpression) {
598
- throw new Error(`template_parse_error: invalid continuation guidance expression '${strayLiquidExpression[0]}'.`);
350
+ throw new Error(
351
+ `template_parse_error: invalid continuation guidance expression '${strayLiquidExpression[0]}'.`
352
+ );
599
353
  }
600
354
  return rendered;
601
355
  }
602
356
 
603
- // ../worker/dist/turn-limits.js
357
+ // ../worker/src/turn-limits.ts
604
358
  var DEFAULT_SESSION_MAX_TURNS = 20;
605
359
  function resolveMaxTurns(value) {
606
360
  const parsed = typeof value === "number" ? value : Number(value);
@@ -623,21 +377,27 @@ function resolveMaxTurns(value) {
623
377
  };
624
378
  }
625
379
 
626
- // ../worker/dist/token-usage.js
627
- import { mkdir as mkdir2, writeFile as writeFile3 } from "fs/promises";
628
- import { dirname as dirname2, join as join2 } from "path";
380
+ // ../worker/src/token-usage.ts
381
+ import { mkdir, writeFile } from "fs/promises";
382
+ import { dirname, join } from "path";
629
383
  async function persistTokenUsageArtifact(env, tokenUsage) {
630
384
  const artifactPath = resolveTokenUsageArtifactPath(env);
631
385
  if (!artifactPath) {
632
386
  return;
633
387
  }
634
388
  try {
635
- await mkdir2(dirname2(artifactPath), { recursive: true });
636
- await writeFile3(artifactPath, JSON.stringify(tokenUsage, null, 2) + "\n", "utf8");
389
+ await mkdir(dirname(artifactPath), { recursive: true });
390
+ await writeFile(
391
+ artifactPath,
392
+ JSON.stringify(tokenUsage, null, 2) + "\n",
393
+ "utf8"
394
+ );
637
395
  } catch (error) {
638
396
  const message = error instanceof Error ? error.message : String(error);
639
- process.stderr.write(`[worker] failed to persist token usage artifact: ${message}
640
- `);
397
+ process.stderr.write(
398
+ `[worker] failed to persist token usage artifact: ${message}
399
+ `
400
+ );
641
401
  }
642
402
  }
643
403
  function resolveTokenUsageArtifactPath(env) {
@@ -645,10 +405,10 @@ function resolveTokenUsageArtifactPath(env) {
645
405
  if (!workspaceRuntimeDir) {
646
406
  return null;
647
407
  }
648
- return join2(workspaceRuntimeDir, "token-usage.json");
408
+ return join(workspaceRuntimeDir, "token-usage.json");
649
409
  }
650
410
 
651
- // ../worker/dist/index.js
411
+ // ../worker/src/index.ts
652
412
  var launcherEnv = loadLauncherEnvironment(process.env);
653
413
  var runtimeState = {
654
414
  status: launcherEnv.SYMPHONY_RUN_ID ? "starting" : "idle",
@@ -684,11 +444,18 @@ var runtimeState = {
684
444
  exitClassification: null
685
445
  }
686
446
  };
687
- console.log(JSON.stringify({
688
- package: "@gh-symphony/worker",
689
- runtime: "self-hosted-sample"
690
- }, null, 2));
447
+ console.log(
448
+ JSON.stringify(
449
+ {
450
+ package: "@gh-symphony/worker",
451
+ runtime: "self-hosted-sample"
452
+ },
453
+ null,
454
+ 2
455
+ )
456
+ );
691
457
  var childProcess = null;
458
+ var runtimeAdapter = null;
692
459
  var shutdownPromise = null;
693
460
  var orchestratorChannelDrainPending = false;
694
461
  var pendingOrchestratorChannelPayloads = [];
@@ -719,10 +486,13 @@ function shutdown(signal) {
719
486
  } catch {
720
487
  }
721
488
  }
489
+ await runtimeAdapter?.cancel(`worker received ${signal}`);
722
490
  stopOrchestratorHeartbeatTimer();
723
491
  emitOrchestratorHeartbeat();
724
492
  await persistSessionTokenUsageArtifact(launcherEnv);
725
- await waitForPendingOrchestratorChannelFlush(resolveTerminalOrchestratorChannelFlushTimeoutMs());
493
+ await waitForPendingOrchestratorChannelFlush(
494
+ resolveTerminalOrchestratorChannelFlushTimeoutMs()
495
+ );
726
496
  console.log(`Worker stopped on ${signal}`);
727
497
  process.exit(0);
728
498
  })();
@@ -755,13 +525,13 @@ function waitForPendingOrchestratorChannelFlush(timeoutMs = ORCHESTRATOR_CHANNEL
755
525
  if (!orchestratorChannelDrainPending && pendingOrchestratorChannelPayloads.length === 0) {
756
526
  return Promise.resolve();
757
527
  }
758
- return new Promise((resolve2) => {
528
+ return new Promise((resolve) => {
759
529
  let settled = false;
760
530
  let timeout = setTimeout(() => {
761
531
  settled = true;
762
532
  process.stderr.removeListener("drain", handleDrain);
763
533
  timeout = null;
764
- resolve2();
534
+ resolve();
765
535
  }, timeoutMs);
766
536
  const handleDrain = () => {
767
537
  if (orchestratorChannelDrainPending || pendingOrchestratorChannelPayloads.length > 0) {
@@ -776,14 +546,17 @@ function waitForPendingOrchestratorChannelFlush(timeoutMs = ORCHESTRATOR_CHANNEL
776
546
  timeout = null;
777
547
  }
778
548
  process.stderr.removeListener("drain", handleDrain);
779
- resolve2();
549
+ resolve();
780
550
  };
781
551
  process.stderr.on("drain", handleDrain);
782
552
  });
783
553
  }
784
554
  function resolveTerminalOrchestratorChannelFlushTimeoutMs() {
785
555
  const pendingPayloadCount = pendingOrchestratorChannelPayloads.length + (orchestratorChannelDrainPending ? 1 : 0);
786
- return Math.max(ORCHESTRATOR_CHANNEL_FLUSH_TIMEOUT_MS, pendingPayloadCount * ORCHESTRATOR_CHANNEL_FLUSH_TIMEOUT_MS);
556
+ return Math.max(
557
+ ORCHESTRATOR_CHANNEL_FLUSH_TIMEOUT_MS,
558
+ pendingPayloadCount * ORCHESTRATOR_CHANNEL_FLUSH_TIMEOUT_MS
559
+ );
787
560
  }
788
561
  function writeOrQueueOrchestratorChannelPayload(serializedPayload) {
789
562
  if (orchestratorChannelDrainPending) {
@@ -861,9 +634,18 @@ function cloneTokenUsageSnapshot() {
861
634
  }
862
635
  function resolveTurnTokenUsageDelta(baseline) {
863
636
  return {
864
- inputTokens: Math.max(0, runtimeState.tokenUsage.inputTokens - baseline.inputTokens),
865
- outputTokens: Math.max(0, runtimeState.tokenUsage.outputTokens - baseline.outputTokens),
866
- totalTokens: Math.max(0, runtimeState.tokenUsage.totalTokens - baseline.totalTokens)
637
+ inputTokens: Math.max(
638
+ 0,
639
+ runtimeState.tokenUsage.inputTokens - baseline.inputTokens
640
+ ),
641
+ outputTokens: Math.max(
642
+ 0,
643
+ runtimeState.tokenUsage.outputTokens - baseline.outputTokens
644
+ ),
645
+ totalTokens: Math.max(
646
+ 0,
647
+ runtimeState.tokenUsage.totalTokens - baseline.totalTokens
648
+ )
867
649
  };
868
650
  }
869
651
  function resolveSessionTokenUsageDelta() {
@@ -900,7 +682,10 @@ function emitTurnCompletedEvent(turn) {
900
682
  issueId,
901
683
  startedAt: turn.startedAt,
902
684
  completedAt,
903
- durationMs: Math.max(0, new Date(completedAt).getTime() - new Date(turn.startedAt).getTime()),
685
+ durationMs: Math.max(
686
+ 0,
687
+ new Date(completedAt).getTime() - new Date(turn.startedAt).getTime()
688
+ ),
904
689
  threadId: turn.threadId,
905
690
  turnId: turn.turnId,
906
691
  turnCount: turn.turnCount,
@@ -921,7 +706,10 @@ function emitTurnFailedEvent(turn, error) {
921
706
  issueId,
922
707
  startedAt: turn.startedAt,
923
708
  failedAt,
924
- durationMs: Math.max(0, new Date(failedAt).getTime() - new Date(turn.startedAt).getTime()),
709
+ durationMs: Math.max(
710
+ 0,
711
+ new Date(failedAt).getTime() - new Date(turn.startedAt).getTime()
712
+ ),
925
713
  threadId: turn.threadId,
926
714
  turnId: turn.turnId,
927
715
  turnCount: turn.turnCount,
@@ -934,17 +722,44 @@ function emitTurnFailedEvent(turn, error) {
934
722
  }
935
723
  async function startAssignedRun() {
936
724
  try {
937
- const workflowPath = launcherEnv.SYMPHONY_WORKFLOW_PATH || join3(launcherEnv.WORKING_DIRECTORY, "WORKFLOW.md");
725
+ const workflowPath = launcherEnv.SYMPHONY_WORKFLOW_PATH || join2(launcherEnv.WORKING_DIRECTORY, "WORKFLOW.md");
938
726
  runtimeState.runPhase = "building_prompt";
939
- const workflow = parseWorkflowMarkdown(await readFile2(workflowPath, "utf8"), launcherEnv);
727
+ const workflow = parseWorkflowMarkdown(
728
+ await readFile(workflowPath, "utf8"),
729
+ launcherEnv
730
+ );
731
+ const route = resolveWorkerRuntimeRoute(workflow);
732
+ if (route === "runtime-adapter" && workflow.runtime?.kind === "claude-print" && isClaudeRuntimeCommand(resolveWorkflowRuntimeCommand(workflow))) {
733
+ const hasGitHubGraphqlToken = typeof launcherEnv.GITHUB_GRAPHQL_TOKEN === "string" && launcherEnv.GITHUB_GRAPHQL_TOKEN.trim().length > 0;
734
+ const preflight = await runClaudePreflight({
735
+ cwd: launcherEnv.WORKING_DIRECTORY,
736
+ env: launcherEnv,
737
+ command: resolveClaudeCommandBinary(workflow.codex.command) ?? void 0,
738
+ includeGhAuth: !hasGitHubGraphqlToken
739
+ });
740
+ process.stderr.write(
741
+ `[worker] ${formatClaudePreflightText(preflight)}
742
+ `
743
+ );
744
+ if (!preflight.ok) {
745
+ await exitWorkerStartupFailure(
746
+ "Claude runtime preflight failed before agent launch."
747
+ );
748
+ return;
749
+ }
750
+ }
940
751
  runtimeState.executionPhase = resolveInitialExecutionPhase({
941
752
  issueState: runtimeState.run?.state,
942
753
  blockerCheckStates: workflow.lifecycle.blockerCheckStates,
943
754
  activeStates: workflow.lifecycle.activeStates
944
755
  });
945
- const config = resolveLocalRuntimeLaunchConfig(launcherEnv);
946
- config.agentCommand = workflow.codex.command;
947
756
  runtimeState.runPhase = "launching_agent";
757
+ if (route === "runtime-adapter") {
758
+ await runNonCodexRuntimeAdapterLifecycle(workflow, launcherEnv);
759
+ return;
760
+ }
761
+ const config = resolveLocalRuntimeLaunchConfig(launcherEnv);
762
+ config.agentCommand = resolveWorkflowRuntimeCommand(workflow);
948
763
  const plan = await prepareCodexRuntimePlan(config);
949
764
  childProcess = launchCodexAppServer(plan);
950
765
  runtimeState.status = "running";
@@ -955,24 +770,27 @@ async function startAssignedRun() {
955
770
  void runCodexClientProtocol(childProcess, plan, launcherEnv, {
956
771
  continuationGuidance: workflow.continuationGuidance
957
772
  });
958
- childProcess.once("exit", (code, signal) => {
959
- const currentRunPhase = runtimeState.runPhase;
960
- const nextRunPhase = resolveExitRunPhase(currentRunPhase, {
961
- code,
962
- signal
963
- });
964
- const preservesTerminalPhase = currentRunPhase != null && nextRunPhase === currentRunPhase;
965
- if (!preservesTerminalPhase) {
966
- runtimeState.status = code === 0 && !signal ? "completed" : "failed";
967
- }
968
- runtimeState.runPhase = nextRunPhase;
969
- if (runtimeState.run) {
773
+ childProcess.once(
774
+ "exit",
775
+ (code, signal) => {
776
+ const currentRunPhase = runtimeState.runPhase;
777
+ const nextRunPhase = resolveExitRunPhase(currentRunPhase, {
778
+ code,
779
+ signal
780
+ });
781
+ const preservesTerminalPhase = currentRunPhase != null && nextRunPhase === currentRunPhase;
970
782
  if (!preservesTerminalPhase) {
971
- runtimeState.run.lastError = code === 0 && !signal ? null : `codex app-server exited with ${signal ?? code ?? "unknown"}`;
783
+ runtimeState.status = code === 0 && !signal ? "completed" : "failed";
784
+ }
785
+ runtimeState.runPhase = nextRunPhase;
786
+ if (runtimeState.run) {
787
+ if (!preservesTerminalPhase) {
788
+ runtimeState.run.lastError = code === 0 && !signal ? null : `codex app-server exited with ${signal ?? code ?? "unknown"}`;
789
+ }
972
790
  }
791
+ void persistSessionTokenUsageArtifact(launcherEnv);
973
792
  }
974
- void persistSessionTokenUsageArtifact(launcherEnv);
975
- });
793
+ );
976
794
  childProcess.once("error", (error) => {
977
795
  runtimeState.status = "failed";
978
796
  runtimeState.runPhase = "failed";
@@ -982,25 +800,243 @@ async function startAssignedRun() {
982
800
  void persistSessionTokenUsageArtifact(launcherEnv);
983
801
  });
984
802
  } catch (error) {
803
+ const message = error instanceof Error ? error.message : "Unknown worker startup error";
985
804
  runtimeState.status = "failed";
986
805
  runtimeState.runPhase = "failed";
987
806
  if (runtimeState.run) {
988
- runtimeState.run.lastError = error instanceof Error ? error.message : "Unknown worker startup error";
807
+ runtimeState.run.lastError = message;
989
808
  }
809
+ process.stderr.write(`[worker] startup failed: ${message}
810
+ `);
990
811
  await persistSessionTokenUsageArtifact(launcherEnv);
991
812
  }
992
813
  }
814
+ async function runNonCodexRuntimeAdapterLifecycle(workflow, env) {
815
+ const renderedPrompt = env.SYMPHONY_RENDERED_PROMPT;
816
+ if (!renderedPrompt) {
817
+ await exitWorkerStartupFailure(
818
+ "SYMPHONY_RENDERED_PROMPT not set; cannot run runtime adapter lifecycle."
819
+ );
820
+ return;
821
+ }
822
+ const runId = env.SYMPHONY_RUN_ID;
823
+ if (!runId) {
824
+ await exitWorkerStartupFailure(
825
+ "SYMPHONY_RUN_ID not set; cannot prepare runtime adapter lifecycle."
826
+ );
827
+ return;
828
+ }
829
+ const runtimeKind = workflow.runtime?.kind;
830
+ if (runtimeKind !== "claude-print" && runtimeKind !== "custom") {
831
+ await exitWorkerStartupFailure(
832
+ "Runtime adapter lifecycle requested for an unsupported runtime kind."
833
+ );
834
+ return;
835
+ }
836
+ const adapter = createWorkerNonCodexRuntimeAdapter(workflow, {
837
+ workingDirectory: env.WORKING_DIRECTORY,
838
+ env,
839
+ runtimeRoot: env.WORKSPACE_RUNTIME_DIR,
840
+ runtimeDirectory: env.WORKSPACE_RUNTIME_DIR,
841
+ onSpawned: (child) => {
842
+ childProcess = child;
843
+ if (runtimeState.run) {
844
+ runtimeState.run.processId = child.pid ?? null;
845
+ }
846
+ }
847
+ });
848
+ runtimeAdapter = adapter;
849
+ let terminalFailure = null;
850
+ const unsubscribe = adapter.onEvent((event) => {
851
+ const agentEvent = event;
852
+ handleNonCodexRuntimeEvent(agentEvent);
853
+ if (agentEvent.name === "agent.inputRequired") {
854
+ terminalFailure = agentEvent.payload.reason;
855
+ }
856
+ if (agentEvent.name === "agent.turnFailed" || agentEvent.name === "agent.error") {
857
+ terminalFailure = agentEvent.name === "agent.error" ? agentEvent.payload.error : JSON.stringify(agentEvent.payload.params);
858
+ }
859
+ });
860
+ const turnTelemetry = {
861
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
862
+ threadId: null,
863
+ turnId: `${runId}-turn-1`,
864
+ turnCount: 1,
865
+ sessionId: runId,
866
+ tokenUsageBaseline: cloneTokenUsageSnapshot()
867
+ };
868
+ try {
869
+ await adapter.prepare({
870
+ runId
871
+ });
872
+ runtimeState.status = "running";
873
+ runtimeState.runPhase = "streaming_turn";
874
+ runtimeState.sessionInfo = {
875
+ threadId: turnTelemetry.threadId,
876
+ turnId: turnTelemetry.turnId,
877
+ turnCount: turnTelemetry.turnCount,
878
+ sessionId: turnTelemetry.sessionId,
879
+ exitClassification: null
880
+ };
881
+ runtimeState.sessionId = turnTelemetry.sessionId;
882
+ emitTurnStartedEvent(turnTelemetry);
883
+ const result = await spawnNonCodexRuntimeTurn(
884
+ adapter,
885
+ runtimeKind,
886
+ renderedPrompt,
887
+ env
888
+ );
889
+ if (isNonCodexTurnFailure(result)) {
890
+ terminalFailure = describeNonCodexTurnFailure(result);
891
+ }
892
+ runtimeState.status = terminalFailure ? "failed" : "completed";
893
+ runtimeState.runPhase = terminalFailure ? "failed" : "succeeded";
894
+ if (runtimeState.run) {
895
+ runtimeState.run.lastError = terminalFailure;
896
+ }
897
+ runtimeState.sessionInfo.exitClassification = classifySessionExit({
898
+ runPhase: runtimeState.runPhase,
899
+ userInputRequired: terminalFailure === DEFAULT_AGENT_INPUT_REQUIRED_REASON || terminalFailure?.startsWith("turn_input_required:") === true,
900
+ budgetExceeded: false,
901
+ convergenceDetected: false,
902
+ maxTurnsReached: false
903
+ });
904
+ if (terminalFailure) {
905
+ emitTurnFailedEvent(turnTelemetry, terminalFailure);
906
+ } else {
907
+ emitTurnCompletedEvent(turnTelemetry);
908
+ }
909
+ } catch (error) {
910
+ const message = error instanceof Error ? error.message : "Unknown runtime adapter lifecycle error";
911
+ runtimeState.status = "failed";
912
+ runtimeState.runPhase = "failed";
913
+ if (runtimeState.run) {
914
+ runtimeState.run.lastError = message;
915
+ }
916
+ runtimeState.sessionInfo.exitClassification = classifySessionExit({
917
+ runPhase: runtimeState.runPhase,
918
+ userInputRequired: false,
919
+ budgetExceeded: false,
920
+ convergenceDetected: false,
921
+ maxTurnsReached: false
922
+ });
923
+ emitTurnFailedEvent(turnTelemetry, message);
924
+ } finally {
925
+ unsubscribe();
926
+ await adapter.shutdown();
927
+ runtimeAdapter = null;
928
+ childProcess = null;
929
+ runtimeState.runPhase = runtimeState.runPhase ?? "failed";
930
+ stopOrchestratorHeartbeatTimer();
931
+ emitOrchestratorHeartbeat();
932
+ await persistSessionTokenUsageArtifact(env);
933
+ await waitForPendingOrchestratorChannelFlush(
934
+ resolveTerminalOrchestratorChannelFlushTimeoutMs()
935
+ );
936
+ setTimeout(() => {
937
+ process.exit(runtimeState.status === "completed" ? 0 : 1);
938
+ }, 1500);
939
+ }
940
+ }
941
+ async function spawnNonCodexRuntimeTurn(adapter, runtimeKind, renderedPrompt, env) {
942
+ if (runtimeKind === "claude-print") {
943
+ return await adapter.spawnTurn({
944
+ messages: [{ type: "user", text: renderedPrompt }],
945
+ cwd: env.WORKING_DIRECTORY,
946
+ env
947
+ });
948
+ }
949
+ return await adapter.spawnTurn({
950
+ prompt: renderedPrompt,
951
+ cwd: env.WORKING_DIRECTORY,
952
+ env
953
+ });
954
+ }
955
+ function handleNonCodexRuntimeEvent(event) {
956
+ if (event.payload.suppressUpdate) {
957
+ return;
958
+ }
959
+ runtimeState.lastEventAt = (/* @__PURE__ */ new Date()).toISOString();
960
+ switch (event.name) {
961
+ case "agent.tokenUsageUpdated": {
962
+ const tokenUsage = extractAbsoluteTokenUsage(event.payload.params);
963
+ if (tokenUsage) {
964
+ applyTokenUsageUpdate(
965
+ event.payload.observabilityEvent ?? event.name,
966
+ tokenUsage
967
+ );
968
+ }
969
+ break;
970
+ }
971
+ case "agent.rateLimit": {
972
+ const rateLimits = extractRateLimitPayload(event.payload.params);
973
+ if (rateLimits) {
974
+ applyRateLimitUpdate(
975
+ event.payload.observabilityEvent ?? event.name,
976
+ rateLimits,
977
+ "runtime-adapter"
978
+ );
979
+ }
980
+ break;
981
+ }
982
+ case "agent.inputRequired":
983
+ runtimeState.status = "failed";
984
+ if (runtimeState.run) {
985
+ runtimeState.run.lastError = event.payload.reason;
986
+ }
987
+ break;
988
+ case "agent.turnFailed":
989
+ case "agent.turnCancelled":
990
+ case "agent.error":
991
+ runtimeState.status = "failed";
992
+ if (runtimeState.run) {
993
+ runtimeState.run.lastError = event.name === "agent.error" ? event.payload.error : JSON.stringify(event.payload.params);
994
+ }
995
+ break;
996
+ default:
997
+ break;
998
+ }
999
+ emitOrchestratorChannelEvent(event.payload.observabilityEvent ?? event.name);
1000
+ }
1001
+ function isNonCodexTurnFailure(result) {
1002
+ return result.result !== "success";
1003
+ }
1004
+ function describeNonCodexTurnFailure(result) {
1005
+ return result.errorMessage ?? `${result.command} exited with ${result.signal ?? result.exitCode ?? "unknown"}`;
1006
+ }
1007
+ async function exitWorkerStartupFailure(message) {
1008
+ runtimeState.status = "failed";
1009
+ runtimeState.runPhase = "failed";
1010
+ if (runtimeState.run) {
1011
+ runtimeState.run.lastError = message;
1012
+ }
1013
+ process.stderr.write(`[worker] ${message}
1014
+ `);
1015
+ stopOrchestratorHeartbeatTimer();
1016
+ emitOrchestratorHeartbeat();
1017
+ await persistSessionTokenUsageArtifact(launcherEnv);
1018
+ await waitForPendingOrchestratorChannelFlush(
1019
+ resolveTerminalOrchestratorChannelFlushTimeoutMs()
1020
+ );
1021
+ process.exit(1);
1022
+ }
993
1023
  async function runCodexClientProtocol(child, plan, env, options) {
994
1024
  const renderedPrompt = env.SYMPHONY_RENDERED_PROMPT;
995
1025
  if (!renderedPrompt) {
996
- process.stderr.write("[worker] SYMPHONY_RENDERED_PROMPT not set; skipping codex client protocol\n");
1026
+ process.stderr.write(
1027
+ "[worker] SYMPHONY_RENDERED_PROMPT not set; skipping codex client protocol\n"
1028
+ );
997
1029
  return;
998
1030
  }
999
1031
  if (!child.stdin || !child.stdout) {
1000
- process.stderr.write("[worker] codex process has no stdio pipes; cannot run client protocol\n");
1032
+ process.stderr.write(
1033
+ "[worker] codex process has no stdio pipes; cannot run client protocol\n"
1034
+ );
1001
1035
  return;
1002
1036
  }
1003
- const { maxTurns, exhaustedBeforeStart } = resolveMaxTurns(env.SYMPHONY_MAX_TURNS);
1037
+ const { maxTurns, exhaustedBeforeStart } = resolveMaxTurns(
1038
+ env.SYMPHONY_MAX_TURNS
1039
+ );
1004
1040
  const readTimeoutMs = Number(env.SYMPHONY_READ_TIMEOUT_MS) || 5e3;
1005
1041
  const turnTimeoutMs = Number(env.SYMPHONY_TURN_TIMEOUT_MS) || 36e5;
1006
1042
  const maxNonProductiveTurns = resolveMaxNonProductiveTurns(env);
@@ -1015,10 +1051,11 @@ async function runCodexClientProtocol(child, plan, env, options) {
1015
1051
  let lineBuffer = "";
1016
1052
  let deltaBuffer = null;
1017
1053
  function flushDeltaBuffer() {
1018
- if (!deltaBuffer)
1019
- return;
1020
- process.stderr.write(`[worker] codex \u2192 agent_message [accumulated] ${JSON.stringify({ text: deltaBuffer.text }).slice(0, 500)}
1021
- `);
1054
+ if (!deltaBuffer) return;
1055
+ process.stderr.write(
1056
+ `[worker] codex \u2192 agent_message [accumulated] ${JSON.stringify({ text: deltaBuffer.text }).slice(0, 500)}
1057
+ `
1058
+ );
1022
1059
  deltaBuffer = null;
1023
1060
  }
1024
1061
  const pendingRequests = /* @__PURE__ */ new Map();
@@ -1035,7 +1072,8 @@ async function runCodexClientProtocol(child, plan, env, options) {
1035
1072
  }
1036
1073
  }
1037
1074
  function describeTurnTerminalEvent(event, params) {
1038
- const fallback = event === "turn/failed" ? "turn_failed: codex reported turn failure" : "turn_cancelled: codex reported turn cancellation";
1075
+ const errorPrefix = event === "agent.turnFailed" ? "turn_failed" : "turn_cancelled";
1076
+ const fallback = event === "agent.turnFailed" ? "turn_failed: codex reported turn failure" : "turn_cancelled: codex reported turn cancellation";
1039
1077
  if (!params || typeof params !== "object") {
1040
1078
  return fallback;
1041
1079
  }
@@ -1044,18 +1082,18 @@ async function runCodexClientProtocol(child, plan, env, options) {
1044
1082
  for (const key of directReasonKeys) {
1045
1083
  const value = record[key];
1046
1084
  if (typeof value === "string" && value.trim()) {
1047
- return `${event.replace("/", "_")}: ${value.trim()}`;
1085
+ return `${errorPrefix}: ${value.trim()}`;
1048
1086
  }
1049
1087
  if (value && typeof value === "object" && typeof value.message === "string") {
1050
1088
  const nested = value;
1051
1089
  const nestedMessage = String(nested.message).trim();
1052
1090
  if (nestedMessage) {
1053
- return `${event.replace("/", "_")}: ${nestedMessage}`;
1091
+ return `${errorPrefix}: ${nestedMessage}`;
1054
1092
  }
1055
1093
  }
1056
1094
  }
1057
1095
  const serialized = JSON.stringify(params).slice(0, 300);
1058
- return serialized && serialized !== "{}" ? `${event.replace("/", "_")}: ${serialized}` : fallback;
1096
+ return serialized && serialized !== "{}" ? `${errorPrefix}: ${serialized}` : fallback;
1059
1097
  }
1060
1098
  function markTurnTerminalFailure(runPhase, lastError) {
1061
1099
  runtimeState.status = "failed";
@@ -1075,36 +1113,45 @@ async function runCodexClientProtocol(child, plan, env, options) {
1075
1113
  child.stdin?.write(line);
1076
1114
  }
1077
1115
  function sendRequest(id, method, params) {
1078
- return new Promise((resolve2, reject) => {
1079
- pendingRequests.set(id, { resolve: resolve2, reject });
1116
+ return new Promise((resolve, reject) => {
1117
+ pendingRequests.set(id, { resolve, reject });
1080
1118
  sendMessage({ jsonrpc: "2.0", id, method, params });
1081
1119
  });
1082
1120
  }
1083
1121
  function sendRequestWithTimeout(id, method, params) {
1084
- return new Promise((resolve2, reject) => {
1122
+ return new Promise((resolve, reject) => {
1085
1123
  const timer = setTimeout(() => {
1086
1124
  pendingRequests.delete(id);
1087
- reject(new Error(`response_timeout: ${method} timed out after ${readTimeoutMs}ms`));
1125
+ reject(
1126
+ new Error(
1127
+ `response_timeout: ${method} timed out after ${readTimeoutMs}ms`
1128
+ )
1129
+ );
1088
1130
  }, readTimeoutMs);
1089
- sendRequest(id, method, params).then((result) => {
1090
- clearTimeout(timer);
1091
- resolve2(result);
1092
- }, (error) => {
1093
- clearTimeout(timer);
1094
- reject(error);
1095
- });
1131
+ sendRequest(id, method, params).then(
1132
+ (result) => {
1133
+ clearTimeout(timer);
1134
+ resolve(result);
1135
+ },
1136
+ (error) => {
1137
+ clearTimeout(timer);
1138
+ reject(error);
1139
+ }
1140
+ );
1096
1141
  });
1097
1142
  }
1098
1143
  function waitForTurnCompletion() {
1099
- return new Promise((resolve2) => {
1100
- turnCompletedResolve = resolve2;
1144
+ return new Promise((resolve) => {
1145
+ turnCompletedResolve = resolve;
1101
1146
  });
1102
1147
  }
1103
1148
  function waitForTurnWithTimeout() {
1104
- return new Promise((resolve2, reject) => {
1149
+ return new Promise((resolve, reject) => {
1105
1150
  const timer = setTimeout(() => {
1106
- process.stderr.write(`[worker] turn_timeout: turn exceeded ${turnTimeoutMs}ms \u2014 killing codex process
1107
- `);
1151
+ process.stderr.write(
1152
+ `[worker] turn_timeout: turn exceeded ${turnTimeoutMs}ms \u2014 killing codex process
1153
+ `
1154
+ );
1108
1155
  if (child.pid) {
1109
1156
  try {
1110
1157
  process.kill(child.pid, "SIGTERM");
@@ -1113,20 +1160,27 @@ async function runCodexClientProtocol(child, plan, env, options) {
1113
1160
  }
1114
1161
  reject(new Error("turn_timeout: turn exceeded time limit"));
1115
1162
  }, turnTimeoutMs);
1116
- waitForTurnCompletion().then(() => {
1117
- clearTimeout(timer);
1118
- resolve2();
1119
- }, (error) => {
1120
- clearTimeout(timer);
1121
- reject(error);
1122
- });
1163
+ waitForTurnCompletion().then(
1164
+ () => {
1165
+ clearTimeout(timer);
1166
+ resolve();
1167
+ },
1168
+ (error) => {
1169
+ clearTimeout(timer);
1170
+ reject(error);
1171
+ }
1172
+ );
1123
1173
  });
1124
1174
  }
1125
1175
  async function dispatchDynamicToolCall(callId, toolName, threadId, turnId, args) {
1126
- const toolDef = plan.tools.find((t) => t.name === toolName);
1176
+ const toolDef = plan.tools.find(
1177
+ (t) => t.name === toolName
1178
+ );
1127
1179
  if (!toolDef) {
1128
- process.stderr.write(`[worker] unknown dynamic tool: ${toolName}; sending error response
1129
- `);
1180
+ process.stderr.write(
1181
+ `[worker] unknown dynamic tool: ${toolName}; sending error response
1182
+ `
1183
+ );
1130
1184
  sendMessage({
1131
1185
  jsonrpc: "2.0",
1132
1186
  method: "dynamic_tool_call_response",
@@ -1146,8 +1200,10 @@ async function runCodexClientProtocol(child, plan, env, options) {
1146
1200
  return;
1147
1201
  }
1148
1202
  const inputJson = JSON.stringify(args ?? {});
1149
- process.stderr.write(`[worker] executing dynamic tool "${toolName}" (callId=${callId})
1150
- `);
1203
+ process.stderr.write(
1204
+ `[worker] executing dynamic tool "${toolName}" (callId=${callId})
1205
+ `
1206
+ );
1151
1207
  try {
1152
1208
  const output = await runToolProcess(toolDef, inputJson);
1153
1209
  sendMessage({
@@ -1178,138 +1234,185 @@ async function runCodexClientProtocol(child, plan, env, options) {
1178
1234
  });
1179
1235
  }
1180
1236
  }
1181
- function handleServerMessage(msg) {
1182
- if ("id" in msg && msg.id != null && ("result" in msg || "error" in msg)) {
1183
- const id = String(msg.id);
1184
- const pending = pendingRequests.get(id);
1185
- if (pending) {
1186
- pendingRequests.delete(id);
1187
- if ("error" in msg) {
1188
- pending.reject(new Error(JSON.stringify(msg.error)));
1189
- } else {
1190
- pending.resolve(msg.result);
1191
- }
1192
- }
1237
+ function emitObservedAgentEvent(event) {
1238
+ if (event.payload.suppressUpdate) {
1193
1239
  return;
1194
1240
  }
1195
- runtimeState.lastEventAt = (/* @__PURE__ */ new Date()).toISOString();
1196
- const orchestratorEventName = typeof msg.method === "string" ? msg.method : void 0;
1197
- if (msg.method === "dynamic_tool_call_request" && msg.params != null) {
1198
- const params = msg.params;
1199
- void dispatchDynamicToolCall(params.callId, params.tool, params.threadId, params.turnId, params.arguments);
1200
- emitOrchestratorChannelEvent(orchestratorEventName);
1201
- return;
1241
+ emitOrchestratorChannelEvent(getCodexObservabilityEventName(event));
1242
+ }
1243
+ function handleInputRequired(reason, event) {
1244
+ process.stderr.write(
1245
+ "[worker] user_input_required detected \u2014 terminating agent process\n"
1246
+ );
1247
+ userInputRequired = true;
1248
+ runtimeState.status = "failed";
1249
+ if (runtimeState.run) {
1250
+ runtimeState.run.lastError = reason;
1202
1251
  }
1203
- if (msg.method === "item/tool/requestUserInput") {
1204
- process.stderr.write("[worker] user_input_required detected \u2014 terminating codex process\n");
1205
- userInputRequired = true;
1206
- runtimeState.status = "failed";
1207
- if (runtimeState.run) {
1208
- runtimeState.run.lastError = "turn_input_required: agent requires user input";
1209
- }
1210
- if (child.pid) {
1211
- try {
1212
- process.kill(child.pid, "SIGTERM");
1213
- } catch {
1214
- }
1215
- }
1216
- if (activeTurnTelemetry) {
1217
- emitTurnFailedEvent(activeTurnTelemetry, runtimeState.run?.lastError ?? null);
1218
- activeTurnTelemetry = null;
1252
+ if (child.pid) {
1253
+ try {
1254
+ process.kill(child.pid, "SIGTERM");
1255
+ } catch {
1219
1256
  }
1220
- resolvePendingTurnCompletion();
1221
- emitOrchestratorChannelEvent(orchestratorEventName);
1222
- return;
1223
1257
  }
1224
- if (msg.method === "turn/completed") {
1225
- flushDeltaBuffer();
1226
- const turnParams = msg.params ?? {};
1227
- const turnUsage = extractAbsoluteTokenUsage(turnParams.usage);
1228
- if (turnUsage) {
1229
- applyTokenUsageUpdate("turn/completed", turnUsage);
1258
+ if (activeTurnTelemetry) {
1259
+ emitTurnFailedEvent(
1260
+ activeTurnTelemetry,
1261
+ runtimeState.run?.lastError ?? null
1262
+ );
1263
+ activeTurnTelemetry = null;
1264
+ }
1265
+ resolvePendingTurnCompletion();
1266
+ emitObservedAgentEvent(event);
1267
+ }
1268
+ function handleAgentEvent(event) {
1269
+ switch (event.name) {
1270
+ case "agent.turnStarted":
1271
+ emitObservedAgentEvent(event);
1272
+ return true;
1273
+ case "agent.toolCallRequested":
1274
+ if (!event.payload.threadId || !event.payload.turnId) {
1275
+ process.stderr.write(
1276
+ `[worker] dynamic tool call ${event.payload.callId} is missing threadId/turnId; cannot send response
1277
+ `
1278
+ );
1279
+ emitObservedAgentEvent(event);
1280
+ return true;
1281
+ }
1282
+ void dispatchDynamicToolCall(
1283
+ event.payload.callId,
1284
+ event.payload.toolName,
1285
+ event.payload.threadId,
1286
+ event.payload.turnId,
1287
+ event.payload.arguments
1288
+ );
1289
+ emitObservedAgentEvent(event);
1290
+ return true;
1291
+ case "agent.inputRequired":
1292
+ handleInputRequired(event.payload.reason, event);
1293
+ return true;
1294
+ case "agent.tokenUsageUpdated": {
1295
+ const tokenUsage = extractAbsoluteTokenUsage(event.payload.params);
1296
+ if (tokenUsage) {
1297
+ applyTokenUsageUpdate(
1298
+ getCodexObservabilityEventName(event) ?? event.name,
1299
+ tokenUsage
1300
+ );
1301
+ }
1302
+ emitObservedAgentEvent(event);
1303
+ return true;
1230
1304
  }
1231
- const rateLimits2 = extractRateLimitPayload(turnParams);
1232
- if (rateLimits2) {
1233
- applyRateLimitUpdate("turn/completed", rateLimits2);
1305
+ case "agent.rateLimit": {
1306
+ const rateLimits = extractRateLimitPayload(event.payload.params);
1307
+ if (rateLimits) {
1308
+ applyRateLimitUpdate(
1309
+ getCodexObservabilityEventName(event) ?? event.name,
1310
+ rateLimits
1311
+ );
1312
+ }
1313
+ emitObservedAgentEvent(event);
1314
+ return true;
1234
1315
  }
1235
- if (turnParams.inputRequired === true) {
1236
- process.stderr.write("[worker] user_input_required detected \u2014 terminating codex process\n");
1237
- userInputRequired = true;
1238
- runtimeState.status = "failed";
1239
- if (runtimeState.run) {
1240
- runtimeState.run.lastError = "turn_input_required: agent requires user input";
1316
+ case "agent.messageDelta": {
1317
+ const { delta, itemId } = event.payload;
1318
+ if (deltaBuffer?.itemId !== itemId) {
1319
+ flushDeltaBuffer();
1320
+ deltaBuffer = { itemId, text: delta };
1321
+ } else {
1322
+ deltaBuffer.text += delta;
1241
1323
  }
1242
- if (child.pid) {
1243
- try {
1244
- process.kill(child.pid, "SIGTERM");
1245
- } catch {
1246
- }
1324
+ emitObservedAgentEvent(event);
1325
+ return true;
1326
+ }
1327
+ case "agent.turnCompleted":
1328
+ flushDeltaBuffer();
1329
+ if (event.payload.inputRequired) {
1330
+ handleInputRequired(DEFAULT_AGENT_INPUT_REQUIRED_REASON, event);
1331
+ return true;
1247
1332
  }
1333
+ emitObservedAgentEvent(event);
1248
1334
  if (activeTurnTelemetry) {
1249
- emitTurnFailedEvent(activeTurnTelemetry, runtimeState.run?.lastError ?? null);
1335
+ emitTurnCompletedEvent(activeTurnTelemetry);
1250
1336
  activeTurnTelemetry = null;
1251
1337
  }
1338
+ process.stderr.write("[worker] agent turn completed\n");
1252
1339
  resolvePendingTurnCompletion();
1253
- emitOrchestratorChannelEvent(orchestratorEventName);
1254
- return;
1340
+ return true;
1341
+ case "agent.turnFailed": {
1342
+ flushDeltaBuffer();
1343
+ const lastError = describeTurnTerminalEvent(
1344
+ "agent.turnFailed",
1345
+ event.payload.params
1346
+ );
1347
+ process.stderr.write(
1348
+ `[worker] agent turn failed ${JSON.stringify(event.payload.params).slice(0, 300)}
1349
+ `
1350
+ );
1351
+ markTurnTerminalFailure("failed", lastError);
1352
+ emitObservedAgentEvent(event);
1353
+ return true;
1255
1354
  }
1256
- emitOrchestratorChannelEvent(orchestratorEventName);
1257
- if (activeTurnTelemetry) {
1258
- emitTurnCompletedEvent(activeTurnTelemetry);
1259
- activeTurnTelemetry = null;
1355
+ case "agent.turnCancelled": {
1356
+ flushDeltaBuffer();
1357
+ const lastError = describeTurnTerminalEvent(
1358
+ "agent.turnCancelled",
1359
+ event.payload.params
1360
+ );
1361
+ process.stderr.write(
1362
+ `[worker] agent turn cancelled ${JSON.stringify(event.payload.params).slice(0, 300)}
1363
+ `
1364
+ );
1365
+ markTurnTerminalFailure("canceled_by_reconciliation", lastError);
1366
+ emitObservedAgentEvent(event);
1367
+ return true;
1260
1368
  }
1261
- process.stderr.write("[worker] codex turn/completed\n");
1262
- resolvePendingTurnCompletion();
1263
- return;
1369
+ case "agent.error":
1370
+ flushDeltaBuffer();
1371
+ process.stderr.write(
1372
+ `[worker] runtime error ${JSON.stringify(event.payload.params).slice(0, 300)}
1373
+ `
1374
+ );
1375
+ markTurnTerminalFailure("failed", event.payload.error);
1376
+ emitObservedAgentEvent(event);
1377
+ return true;
1378
+ default:
1379
+ return false;
1264
1380
  }
1265
- if (msg.method === "turn/failed") {
1266
- flushDeltaBuffer();
1267
- const lastError = describeTurnTerminalEvent("turn/failed", msg.params ?? null);
1268
- process.stderr.write(`[worker] codex turn/failed ${JSON.stringify(msg.params ?? {}).slice(0, 300)}
1269
- `);
1270
- markTurnTerminalFailure("failed", lastError);
1271
- emitOrchestratorChannelEvent(orchestratorEventName);
1381
+ }
1382
+ function handleServerMessage(msg) {
1383
+ if ("id" in msg && msg.id != null && ("result" in msg || "error" in msg)) {
1384
+ const id = String(msg.id);
1385
+ const pending = pendingRequests.get(id);
1386
+ if (pending) {
1387
+ pendingRequests.delete(id);
1388
+ if ("error" in msg) {
1389
+ pending.reject(new Error(JSON.stringify(msg.error)));
1390
+ } else {
1391
+ pending.resolve(msg.result);
1392
+ }
1393
+ }
1272
1394
  return;
1273
1395
  }
1274
- if (msg.method === "turn/cancelled") {
1275
- flushDeltaBuffer();
1276
- const lastError = describeTurnTerminalEvent("turn/cancelled", msg.params ?? null);
1277
- process.stderr.write(`[worker] codex turn/cancelled ${JSON.stringify(msg.params ?? {}).slice(0, 300)}
1278
- `);
1279
- markTurnTerminalFailure("canceled_by_reconciliation", lastError);
1280
- emitOrchestratorChannelEvent(orchestratorEventName);
1281
- return;
1396
+ runtimeState.lastEventAt = (/* @__PURE__ */ new Date()).toISOString();
1397
+ const agentEvents = normalizeCodexRuntimeEvents(msg);
1398
+ let handledAgentEvent = false;
1399
+ for (const event of agentEvents) {
1400
+ handledAgentEvent = handleAgentEvent(event) || handledAgentEvent;
1282
1401
  }
1283
- if (msg.method === "thread/tokenUsage/updated" || msg.method === "total_token_usage" || msg.method === "codex/event/token_count") {
1284
- const tokenUsage = extractAbsoluteTokenUsage(msg.params);
1285
- if (tokenUsage) {
1286
- applyTokenUsageUpdate(msg.method, tokenUsage);
1287
- }
1288
- emitOrchestratorChannelEvent(orchestratorEventName);
1402
+ if (handledAgentEvent) {
1289
1403
  return;
1290
1404
  }
1291
1405
  const rateLimits = extractRateLimitPayload(msg.params);
1292
1406
  if (rateLimits && typeof msg.method === "string") {
1293
1407
  applyRateLimitUpdate(msg.method, rateLimits);
1294
1408
  }
1295
- if (typeof msg.method === "string" && (msg.method === "codex/event/agent_message_content_delta" || msg.method === "codex/event/agent_message_delta" || msg.method === "item/agentMessage/delta")) {
1296
- const params = msg.params ?? {};
1297
- const delta = typeof params.delta === "string" ? params.delta : "";
1298
- const itemId = typeof params.item_id === "string" ? params.item_id : "";
1299
- if (deltaBuffer?.itemId !== itemId) {
1300
- flushDeltaBuffer();
1301
- deltaBuffer = { itemId, text: delta };
1302
- } else {
1303
- deltaBuffer.text += delta;
1304
- }
1305
- emitOrchestratorChannelEvent(orchestratorEventName);
1306
- return;
1307
- }
1308
1409
  if (typeof msg.method === "string") {
1309
1410
  flushDeltaBuffer();
1310
- emitOrchestratorChannelEvent(orchestratorEventName);
1311
- process.stderr.write(`[worker] codex \u2192 ${msg.method} ${JSON.stringify(msg.params ?? {}).slice(0, 300)}
1312
- `);
1411
+ emitOrchestratorChannelEvent(msg.method);
1412
+ process.stderr.write(
1413
+ `[worker] codex \u2192 ${msg.method} ${JSON.stringify(msg.params ?? {}).slice(0, 300)}
1414
+ `
1415
+ );
1313
1416
  }
1314
1417
  }
1315
1418
  child.stdout.on("data", (chunk) => {
@@ -1318,8 +1421,7 @@ async function runCodexClientProtocol(child, plan, env, options) {
1318
1421
  lineBuffer = lines.pop() ?? "";
1319
1422
  for (const line of lines) {
1320
1423
  const trimmed = line.trim();
1321
- if (!trimmed)
1322
- continue;
1424
+ if (!trimmed) continue;
1323
1425
  try {
1324
1426
  const msg = JSON.parse(trimmed);
1325
1427
  handleServerMessage(msg);
@@ -1354,30 +1456,42 @@ async function runCodexClientProtocol(child, plan, env, options) {
1354
1456
  mcp_servers: mcpServers
1355
1457
  }
1356
1458
  };
1357
- process.stderr.write(`[worker] starting codex thread (mcp_servers: ${Object.keys(mcpServers).join(", ")})
1358
- `);
1359
- const threadResult = await sendRequestWithTimeout("thread-1", "thread/start", {
1360
- ...baseThreadParams,
1361
- ephemeral: false
1362
- });
1459
+ process.stderr.write(
1460
+ `[worker] starting codex thread (mcp_servers: ${Object.keys(mcpServers).join(", ")})
1461
+ `
1462
+ );
1463
+ const threadResult = await sendRequestWithTimeout(
1464
+ "thread-1",
1465
+ "thread/start",
1466
+ {
1467
+ ...baseThreadParams,
1468
+ ephemeral: false
1469
+ }
1470
+ );
1363
1471
  const threadId = threadResult.thread_id ?? threadResult.thread?.id;
1364
1472
  runtimeState.sessionInfo.threadId = threadId ?? null;
1365
1473
  runtimeState.sessionInfo.turnId = null;
1366
1474
  runtimeState.sessionInfo.sessionId = null;
1367
1475
  runtimeState.sessionInfo.exitClassification = null;
1368
1476
  runtimeState.sessionId = null;
1369
- process.stderr.write(`[worker] codex thread started (id=${String(threadId ?? "unknown")})
1370
- `);
1477
+ process.stderr.write(
1478
+ `[worker] codex thread started (id=${String(threadId ?? "unknown")})
1479
+ `
1480
+ );
1371
1481
  if (!threadId) {
1372
- process.stderr.write("[worker] warning: no threadId returned; cannot start turn\n");
1482
+ process.stderr.write(
1483
+ "[worker] warning: no threadId returned; cannot start turn\n"
1484
+ );
1373
1485
  return;
1374
1486
  }
1375
1487
  let turnCount = 0;
1376
1488
  let requestIdCounter = 0;
1377
1489
  let maxTurnsReached = exhaustedBeforeStart;
1378
1490
  if (exhaustedBeforeStart) {
1379
- process.stderr.write(`[worker] max_turns (${String(env.SYMPHONY_MAX_TURNS ?? maxTurns)}) does not allow any turns for this worker session \u2014 exiting
1380
- `);
1491
+ process.stderr.write(
1492
+ `[worker] max_turns (${String(env.SYMPHONY_MAX_TURNS ?? maxTurns)}) does not allow any turns for this worker session \u2014 exiting
1493
+ `
1494
+ );
1381
1495
  }
1382
1496
  for (let turn = 0; turn < maxTurns; turn++) {
1383
1497
  turnCount = turn + 1;
@@ -1388,18 +1502,24 @@ async function runCodexClientProtocol(child, plan, env, options) {
1388
1502
  continuationGuidance,
1389
1503
  cumulativeTurnCount: turn
1390
1504
  });
1391
- process.stderr.write(`[worker] starting codex turn ${turnCount}/${maxTurns}${isFirstTurn ? " (initial)" : " (continuation)"}
1392
- `);
1505
+ process.stderr.write(
1506
+ `[worker] starting codex turn ${turnCount}/${maxTurns}${isFirstTurn ? " (initial)" : " (continuation)"}
1507
+ `
1508
+ );
1393
1509
  requestIdCounter += 1;
1394
1510
  const turnRequestId = `turn-${requestIdCounter}`;
1395
- const turnResult = await sendRequestWithTimeout(turnRequestId, "turn/start", {
1396
- threadId,
1397
- input: [{ type: "text", text: turnInput }],
1398
- cwd: plan.cwd,
1399
- title: composeTurnTitle(issueIdentifier, env.SYMPHONY_ISSUE_TITLE),
1400
- approvalPolicy,
1401
- sandboxPolicy: turnSandboxPolicy
1402
- });
1511
+ const turnResult = await sendRequestWithTimeout(
1512
+ turnRequestId,
1513
+ "turn/start",
1514
+ {
1515
+ threadId,
1516
+ input: [{ type: "text", text: turnInput }],
1517
+ cwd: plan.cwd,
1518
+ title: composeTurnTitle(issueIdentifier, env.SYMPHONY_ISSUE_TITLE),
1519
+ approvalPolicy,
1520
+ sandboxPolicy: turnSandboxPolicy
1521
+ }
1522
+ );
1403
1523
  const turnId = turnResult.turn_id ?? turnResult.turn?.id;
1404
1524
  const sessionId = threadId && turnId ? `${threadId}-${turnId}` : null;
1405
1525
  runtimeState.sessionInfo.turnId = turnId ?? null;
@@ -1413,10 +1533,14 @@ async function runCodexClientProtocol(child, plan, env, options) {
1413
1533
  sessionId,
1414
1534
  tokenUsageBaseline: cloneTokenUsageSnapshot()
1415
1535
  };
1416
- process.stderr.write(`[worker] codex turn started (id=${String(turnId ?? "unknown")})
1417
- `);
1418
- process.stderr.write(`[worker] session_id=${String(sessionId ?? "unknown")}
1419
- `);
1536
+ process.stderr.write(
1537
+ `[worker] codex turn started (id=${String(turnId ?? "unknown")})
1538
+ `
1539
+ );
1540
+ process.stderr.write(
1541
+ `[worker] session_id=${String(sessionId ?? "unknown")}
1542
+ `
1543
+ );
1420
1544
  emitTurnStartedEvent(activeTurnTelemetry);
1421
1545
  await waitForTurnWithTimeout();
1422
1546
  if (userInputRequired) {
@@ -1424,14 +1548,18 @@ async function runCodexClientProtocol(child, plan, env, options) {
1424
1548
  break;
1425
1549
  }
1426
1550
  if (turnTerminalFailurePhase) {
1427
- process.stderr.write(`[worker] exiting due to ${turnTerminalFailurePhase}
1428
- `);
1551
+ process.stderr.write(
1552
+ `[worker] exiting due to ${turnTerminalFailurePhase}
1553
+ `
1554
+ );
1429
1555
  break;
1430
1556
  }
1431
1557
  if (turn + 1 >= maxTurns) {
1432
1558
  maxTurnsReached = true;
1433
- process.stderr.write(`[worker] max_turns (${maxTurns}) reached for this worker session \u2014 exiting
1434
- `);
1559
+ process.stderr.write(
1560
+ `[worker] max_turns (${maxTurns}) reached for this worker session \u2014 exiting
1561
+ `
1562
+ );
1435
1563
  break;
1436
1564
  }
1437
1565
  const trackerState = await refreshTrackerState(env);
@@ -1444,19 +1572,26 @@ async function runCodexClientProtocol(child, plan, env, options) {
1444
1572
  trackerState,
1445
1573
  userInputRequired: false
1446
1574
  });
1447
- process.stderr.write("[worker] issue no longer actionable \u2014 exiting multi-turn loop\n");
1575
+ process.stderr.write(
1576
+ "[worker] issue no longer actionable \u2014 exiting multi-turn loop\n"
1577
+ );
1448
1578
  break;
1449
1579
  }
1450
1580
  const currentTurnProgressSnapshot = {
1451
1581
  ...captureTurnWorkspaceSnapshot(plan.cwd),
1452
1582
  lastError: runtimeState.run?.lastError ?? null
1453
1583
  };
1454
- const turnProgress = evaluateTurnProgress(previousTurnProgressSnapshot, currentTurnProgressSnapshot);
1584
+ const turnProgress = evaluateTurnProgress(
1585
+ previousTurnProgressSnapshot,
1586
+ currentTurnProgressSnapshot
1587
+ );
1455
1588
  previousTurnProgressSnapshot = currentTurnProgressSnapshot;
1456
1589
  if (turnProgress.nonProductive) {
1457
1590
  consecutiveNonProductiveTurns += 1;
1458
- process.stderr.write(`[worker] non-productive turn detected (${consecutiveNonProductiveTurns}/${maxNonProductiveTurns})${turnProgress.reason ? `: ${turnProgress.reason}` : ""}
1459
- `);
1591
+ process.stderr.write(
1592
+ `[worker] non-productive turn detected (${consecutiveNonProductiveTurns}/${maxNonProductiveTurns})${turnProgress.reason ? `: ${turnProgress.reason}` : ""}
1593
+ `
1594
+ );
1460
1595
  } else {
1461
1596
  consecutiveNonProductiveTurns = 0;
1462
1597
  }
@@ -1465,13 +1600,17 @@ async function runCodexClientProtocol(child, plan, env, options) {
1465
1600
  if (runtimeState.run) {
1466
1601
  runtimeState.run.lastError = turnProgress.reason ? `convergence_detected: ${turnProgress.reason}` : "convergence_detected: repeated non-productive turn results";
1467
1602
  }
1468
- process.stderr.write(`[worker] convergence detected after ${consecutiveNonProductiveTurns} non-productive turns \u2014 exiting
1469
- `);
1603
+ process.stderr.write(
1604
+ `[worker] convergence detected after ${consecutiveNonProductiveTurns} non-productive turns \u2014 exiting
1605
+ `
1606
+ );
1470
1607
  break;
1471
1608
  }
1472
1609
  }
1473
- process.stderr.write(`[worker] multi-turn loop complete after ${turnCount} turn(s) \u2014 exiting worker
1474
- `);
1610
+ process.stderr.write(
1611
+ `[worker] multi-turn loop complete after ${turnCount} turn(s) \u2014 exiting worker
1612
+ `
1613
+ );
1475
1614
  runtimeState.runPhase = "finishing";
1476
1615
  runtimeState.status = userInputRequired || turnTerminalFailurePhase ? "failed" : "completed";
1477
1616
  runtimeState.runPhase = convergenceDetected ? "failed" : userInputRequired ? "failed" : turnTerminalFailurePhase ?? "succeeded";
@@ -1485,7 +1624,9 @@ async function runCodexClientProtocol(child, plan, env, options) {
1485
1624
  stopOrchestratorHeartbeatTimer();
1486
1625
  emitOrchestratorHeartbeat();
1487
1626
  await persistSessionTokenUsageArtifact(env);
1488
- await waitForPendingOrchestratorChannelFlush(resolveTerminalOrchestratorChannelFlushTimeoutMs());
1627
+ await waitForPendingOrchestratorChannelFlush(
1628
+ resolveTerminalOrchestratorChannelFlushTimeoutMs()
1629
+ );
1489
1630
  setTimeout(() => {
1490
1631
  process.exit(userInputRequired || turnTerminalFailurePhase ? 1 : 0);
1491
1632
  }, 1500);
@@ -1517,13 +1658,18 @@ async function runCodexClientProtocol(child, plan, env, options) {
1517
1658
  maxTurnsReached: false
1518
1659
  });
1519
1660
  if (activeTurnTelemetry) {
1520
- emitTurnFailedEvent(activeTurnTelemetry, runtimeState.run?.lastError ?? errMsg);
1661
+ emitTurnFailedEvent(
1662
+ activeTurnTelemetry,
1663
+ runtimeState.run?.lastError ?? errMsg
1664
+ );
1521
1665
  activeTurnTelemetry = null;
1522
1666
  }
1523
1667
  stopOrchestratorHeartbeatTimer();
1524
1668
  emitOrchestratorHeartbeat();
1525
1669
  await persistSessionTokenUsageArtifact(env);
1526
- await waitForPendingOrchestratorChannelFlush(resolveTerminalOrchestratorChannelFlushTimeoutMs());
1670
+ await waitForPendingOrchestratorChannelFlush(
1671
+ resolveTerminalOrchestratorChannelFlushTimeoutMs()
1672
+ );
1527
1673
  setTimeout(() => {
1528
1674
  process.exit(1);
1529
1675
  }, 1500);
@@ -1533,16 +1679,20 @@ function applyTokenUsageUpdate(source, tokenUsage) {
1533
1679
  runtimeState.tokenUsage.inputTokens = tokenUsage.inputTokens;
1534
1680
  runtimeState.tokenUsage.outputTokens = tokenUsage.outputTokens;
1535
1681
  runtimeState.tokenUsage.totalTokens = tokenUsage.totalTokens;
1536
- process.stderr.write(`[worker] token_usage source=${source} input=${tokenUsage.inputTokens} output=${tokenUsage.outputTokens} total=${tokenUsage.totalTokens}
1537
- `);
1682
+ process.stderr.write(
1683
+ `[worker] token_usage source=${source} input=${tokenUsage.inputTokens} output=${tokenUsage.outputTokens} total=${tokenUsage.totalTokens}
1684
+ `
1685
+ );
1538
1686
  }
1539
- function applyRateLimitUpdate(source, rateLimits) {
1687
+ function applyRateLimitUpdate(source, rateLimits, runtimeSource = "codex") {
1540
1688
  runtimeState.rateLimits = {
1541
1689
  ...rateLimits,
1542
- source: "codex"
1690
+ source: runtimeSource
1543
1691
  };
1544
- process.stderr.write(`[worker] rate_limits source=${source} payload=${JSON.stringify(runtimeState.rateLimits).slice(0, 300)}
1545
- `);
1692
+ process.stderr.write(
1693
+ `[worker] rate_limits source=${source} payload=${JSON.stringify(runtimeState.rateLimits).slice(0, 300)}
1694
+ `
1695
+ );
1546
1696
  }
1547
1697
  function extractRateLimitPayload(value) {
1548
1698
  if (!value || typeof value !== "object") {
@@ -1673,17 +1823,18 @@ async function refreshTrackerState(env) {
1673
1823
  }
1674
1824
  try {
1675
1825
  const response = await fetch(`${orchestratorUrl}/api/v1/state`);
1676
- if (!response.ok)
1677
- return "unknown";
1826
+ if (!response.ok) return "unknown";
1678
1827
  const status = await response.json();
1679
- const isActive = status.activeRuns?.some((run) => run.issueIdentifier === issueIdentifier);
1828
+ const isActive = status.activeRuns?.some(
1829
+ (run) => run.issueIdentifier === issueIdentifier
1830
+ );
1680
1831
  return isActive ? "active" : "non-actionable";
1681
1832
  } catch {
1682
1833
  return "unknown";
1683
1834
  }
1684
1835
  }
1685
1836
  function runToolProcess(toolDef, inputJson) {
1686
- return new Promise((resolve2, reject) => {
1837
+ return new Promise((resolve, reject) => {
1687
1838
  const toolEnv = {
1688
1839
  ...process.env,
1689
1840
  ...toolDef.env
@@ -1700,10 +1851,14 @@ function runToolProcess(toolDef, inputJson) {
1700
1851
  toolProc.once("exit", (code) => {
1701
1852
  const output = Buffer.concat(stdout).toString("utf8").trim();
1702
1853
  if (code === 0) {
1703
- resolve2(output || "{}");
1854
+ resolve(output || "{}");
1704
1855
  } else {
1705
1856
  const errOutput = Buffer.concat(stderr).toString("utf8").trim();
1706
- reject(new Error(`Tool exited with code ${code ?? "unknown"}: ${errOutput || output}`));
1857
+ reject(
1858
+ new Error(
1859
+ `Tool exited with code ${code ?? "unknown"}: ${errOutput || output}`
1860
+ )
1861
+ );
1707
1862
  }
1708
1863
  });
1709
1864
  toolProc.stdin?.write(inputJson);