@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.
- package/README.md +66 -2
- package/dist/chunk-2TSM3INR.js +1085 -0
- package/dist/chunk-2UW7NQLX.js +684 -0
- package/dist/{chunk-MVRF7BES.js → chunk-36KYEDEO.js} +10 -1
- package/dist/{chunk-TILHWBP6.js → chunk-C67H3OUL.js} +239 -36
- package/dist/{chunk-C7G7RJ4G.js → chunk-DDL4BWSL.js} +1 -1
- package/dist/{chunk-XN5ABWZ6.js → chunk-DFLXHNYQ.js} +26 -30
- package/dist/{chunk-EKKT5USP.js → chunk-E7HYEEZD.js} +487 -133
- package/dist/chunk-EEQQWTXS.js +3257 -0
- package/dist/chunk-GDE6FYN4.js +26 -0
- package/dist/{chunk-Y6TYJMNT.js → chunk-GSX2FV3M.js} +10 -16
- package/dist/{chunk-RN2PACNV.js → chunk-HMLBBZNY.js} +731 -75
- package/dist/{chunk-5NV3LSAJ.js → chunk-IWFX2FMA.js} +5 -1
- package/dist/{chunk-HZVDTAPS.js → chunk-PUDXVBSN.js} +1549 -1458
- package/dist/{chunk-ROGRTUFI.js → chunk-QIRE2VXS.js} +14 -3
- package/dist/{chunk-3AWF54PI.js → chunk-ZHOKYUO3.js} +394 -42
- package/dist/{config-cmd-DNXNL26Z.js → config-cmd-Z3A7V6NC.js} +1 -1
- package/dist/{doctor-IYHCFXOZ.js → doctor-EJUMPBMW.js} +105 -40
- package/dist/index.js +112 -24
- package/dist/{init-KZT6YNOH.js → init-54HMKNYI.js} +8 -3
- package/dist/{logs-6JKKYDGJ.js → logs-GTZ4U5JE.js} +2 -2
- package/dist/project-RMYMZSFV.js +25 -0
- package/dist/{recover-5KQI7WH5.js → recover-LTLKMTRX.js} +7 -5
- package/dist/repo-WI7GF6XQ.js +749 -0
- package/dist/{run-ETC5UTRA.js → run-IHN3ZL35.js} +21 -7
- package/dist/{setup-VWB7RZUQ.js → setup-TZJSM3QV.js} +53 -14
- package/dist/start-RTAHQMR2.js +19 -0
- package/dist/status-F4D52OVK.js +12 -0
- package/dist/stop-MDKMJPVR.js +10 -0
- package/dist/{upgrade-3YNF3VKY.js → upgrade-O33S2SJK.js} +2 -2
- package/dist/{version-NUBTTOG7.js → version-CW54Q7BK.js} +1 -1
- package/dist/worker-entry.js +848 -693
- package/dist/{workflow-TBIFY5MO.js → workflow-L3KT6HB7.js} +177 -11
- package/package.json +4 -2
- package/dist/chunk-M3IFVLQS.js +0 -1155
- package/dist/project-UUVHS3ZR.js +0 -22
- package/dist/repo-HDDE7OUI.js +0 -321
- package/dist/start-ENFLZUI6.js +0 -16
- package/dist/status-QSCFVGRQ.js +0 -11
- package/dist/stop-7MFCBQVW.js +0 -9
package/dist/worker-entry.js
CHANGED
|
@@ -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
|
-
|
|
6
|
-
|
|
18
|
+
resolveClaudeCommandBinary,
|
|
19
|
+
resolveWorkflowRuntimeCommand,
|
|
20
|
+
runClaudePreflight
|
|
21
|
+
} from "./chunk-EEQQWTXS.js";
|
|
7
22
|
|
|
8
|
-
// ../worker/
|
|
23
|
+
// ../worker/src/index.ts
|
|
9
24
|
import { spawn as spawn2 } from "child_process";
|
|
10
|
-
import { readFile
|
|
11
|
-
import { join as
|
|
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/
|
|
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/
|
|
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/
|
|
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(
|
|
494
|
-
|
|
495
|
-
|
|
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/
|
|
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/
|
|
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({
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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/
|
|
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/
|
|
627
|
-
import { mkdir
|
|
628
|
-
import { dirname
|
|
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
|
|
636
|
-
await
|
|
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(
|
|
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
|
|
408
|
+
return join(workspaceRuntimeDir, "token-usage.json");
|
|
649
409
|
}
|
|
650
410
|
|
|
651
|
-
// ../worker/
|
|
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(
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
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(
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
865
|
-
|
|
866
|
-
|
|
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(
|
|
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(
|
|
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 ||
|
|
725
|
+
const workflowPath = launcherEnv.SYMPHONY_WORKFLOW_PATH || join2(launcherEnv.WORKING_DIRECTORY, "WORKFLOW.md");
|
|
938
726
|
runtimeState.runPhase = "building_prompt";
|
|
939
|
-
const workflow = parseWorkflowMarkdown(
|
|
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(
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
1020
|
-
|
|
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
|
|
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 `${
|
|
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 `${
|
|
1091
|
+
return `${errorPrefix}: ${nestedMessage}`;
|
|
1054
1092
|
}
|
|
1055
1093
|
}
|
|
1056
1094
|
}
|
|
1057
1095
|
const serialized = JSON.stringify(params).slice(0, 300);
|
|
1058
|
-
return serialized && serialized !== "{}" ? `${
|
|
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((
|
|
1079
|
-
pendingRequests.set(id, { resolve
|
|
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((
|
|
1122
|
+
return new Promise((resolve, reject) => {
|
|
1085
1123
|
const timer = setTimeout(() => {
|
|
1086
1124
|
pendingRequests.delete(id);
|
|
1087
|
-
reject(
|
|
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(
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
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((
|
|
1100
|
-
turnCompletedResolve =
|
|
1144
|
+
return new Promise((resolve) => {
|
|
1145
|
+
turnCompletedResolve = resolve;
|
|
1101
1146
|
});
|
|
1102
1147
|
}
|
|
1103
1148
|
function waitForTurnWithTimeout() {
|
|
1104
|
-
return new Promise((
|
|
1149
|
+
return new Promise((resolve, reject) => {
|
|
1105
1150
|
const timer = setTimeout(() => {
|
|
1106
|
-
process.stderr.write(
|
|
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
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
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(
|
|
1176
|
+
const toolDef = plan.tools.find(
|
|
1177
|
+
(t) => t.name === toolName
|
|
1178
|
+
);
|
|
1127
1179
|
if (!toolDef) {
|
|
1128
|
-
process.stderr.write(
|
|
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(
|
|
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
|
|
1182
|
-
if (
|
|
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
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
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 (
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
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 (
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
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
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
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
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
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
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
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
|
-
|
|
1335
|
+
emitTurnCompletedEvent(activeTurnTelemetry);
|
|
1250
1336
|
activeTurnTelemetry = null;
|
|
1251
1337
|
}
|
|
1338
|
+
process.stderr.write("[worker] agent turn completed\n");
|
|
1252
1339
|
resolvePendingTurnCompletion();
|
|
1253
|
-
|
|
1254
|
-
|
|
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
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
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
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
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
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
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
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
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 (
|
|
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(
|
|
1311
|
-
process.stderr.write(
|
|
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(
|
|
1358
|
-
`)
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
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(
|
|
1370
|
-
`)
|
|
1477
|
+
process.stderr.write(
|
|
1478
|
+
`[worker] codex thread started (id=${String(threadId ?? "unknown")})
|
|
1479
|
+
`
|
|
1480
|
+
);
|
|
1371
1481
|
if (!threadId) {
|
|
1372
|
-
process.stderr.write(
|
|
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(
|
|
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(
|
|
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(
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
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(
|
|
1417
|
-
`)
|
|
1418
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|
|
1690
|
+
source: runtimeSource
|
|
1543
1691
|
};
|
|
1544
|
-
process.stderr.write(
|
|
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(
|
|
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((
|
|
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
|
-
|
|
1854
|
+
resolve(output || "{}");
|
|
1704
1855
|
} else {
|
|
1705
1856
|
const errOutput = Buffer.concat(stderr).toString("utf8").trim();
|
|
1706
|
-
reject(
|
|
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);
|