@gh-symphony/cli 0.0.20 → 0.0.21
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 +30 -2
- package/dist/chunk-A67CMOYE.js +684 -0
- package/dist/{chunk-TILHWBP6.js → chunk-C67H3OUL.js} +239 -36
- package/dist/{chunk-RN2PACNV.js → chunk-JN3TQVFV.js} +721 -74
- package/dist/{chunk-EKKT5USP.js → chunk-KY6WKH66.js} +437 -101
- package/dist/{chunk-HZVDTAPS.js → chunk-MYVJ6HK4.js} +943 -1182
- package/dist/{chunk-M3IFVLQS.js → chunk-QEONJ5DZ.js} +978 -72
- package/dist/{chunk-3AWF54PI.js → chunk-S6VIK4FF.js} +59 -31
- package/dist/chunk-SXGT7LOF.js +1060 -0
- package/dist/{doctor-IYHCFXOZ.js → doctor-4HBRICHP.js} +102 -37
- package/dist/index.js +38 -17
- package/dist/{init-KZT6YNOH.js → init-HZ3JEDGQ.js} +7 -2
- package/dist/{project-UUVHS3ZR.js → project-25NQ4J4Y.js} +8 -6
- package/dist/{recover-5KQI7WH5.js → recover-L3MJHHDA.js} +4 -2
- package/dist/{repo-HDDE7OUI.js → repo-TDCWQR6P.js} +72 -14
- package/dist/{run-ETC5UTRA.js → run-XJQ6BF7U.js} +4 -2
- package/dist/{setup-VWB7RZUQ.js → setup-B2SVLW2R.js} +46 -8
- package/dist/{start-ENFLZUI6.js → start-I2CC7BLW.js} +6 -4
- package/dist/{upgrade-3YNF3VKY.js → upgrade-OJXPZRYE.js} +2 -2
- package/dist/{version-NUBTTOG7.js → version-TBDCTKDO.js} +1 -1
- package/dist/worker-entry.js +489 -690
- package/dist/{workflow-TBIFY5MO.js → workflow-BLJH2HC3.js} +176 -10
- package/package.json +3 -1
package/dist/worker-entry.js
CHANGED
|
@@ -1,448 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
getCodexObservabilityEventName,
|
|
4
|
+
launchCodexAppServer,
|
|
5
|
+
loadLauncherEnvironment,
|
|
6
|
+
normalizeCodexRuntimeEvents,
|
|
7
|
+
prepareCodexRuntimePlan,
|
|
8
|
+
resolveLocalRuntimeLaunchConfig
|
|
9
|
+
} from "./chunk-A67CMOYE.js";
|
|
10
|
+
import {
|
|
11
|
+
DEFAULT_AGENT_INPUT_REQUIRED_REASON,
|
|
3
12
|
classifySessionExit,
|
|
13
|
+
formatClaudePreflightText,
|
|
14
|
+
isClaudeRuntimeCommand,
|
|
4
15
|
parseWorkflowMarkdown,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import { spawn as spawn2 } from "child_process";
|
|
10
|
-
import { readFile as readFile2 } from "fs/promises";
|
|
11
|
-
import { join as join3 } from "path";
|
|
16
|
+
resolveClaudeCommandBinary,
|
|
17
|
+
resolveWorkflowRuntimeCommand,
|
|
18
|
+
runClaudePreflight
|
|
19
|
+
} from "./chunk-QEONJ5DZ.js";
|
|
12
20
|
|
|
13
|
-
// ../
|
|
21
|
+
// ../worker/src/index.ts
|
|
14
22
|
import { spawn } from "child_process";
|
|
15
|
-
import {
|
|
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
|
-
}
|
|
23
|
+
import { readFile } from "fs/promises";
|
|
24
|
+
import { join as join2 } from "path";
|
|
444
25
|
|
|
445
|
-
// ../worker/
|
|
26
|
+
// ../worker/src/execution-phase.ts
|
|
446
27
|
function resolveInitialExecutionPhase(input) {
|
|
447
28
|
const { issueState, blockerCheckStates, activeStates } = input;
|
|
448
29
|
if (!issueState) {
|
|
@@ -472,7 +53,7 @@ function resolveFinalExecutionPhase(input) {
|
|
|
472
53
|
return resolvePausedExecutionPhase(input.currentPhase) ?? input.currentPhase;
|
|
473
54
|
}
|
|
474
55
|
|
|
475
|
-
// ../worker/
|
|
56
|
+
// ../worker/src/codex-policy.ts
|
|
476
57
|
function resolveCodexPolicySettings(env) {
|
|
477
58
|
return {
|
|
478
59
|
approvalPolicy: env.SYMPHONY_APPROVAL_POLICY || "never",
|
|
@@ -481,7 +62,7 @@ function resolveCodexPolicySettings(env) {
|
|
|
481
62
|
};
|
|
482
63
|
}
|
|
483
64
|
|
|
484
|
-
// ../worker/
|
|
65
|
+
// ../worker/src/convergence-detection.ts
|
|
485
66
|
import { spawnSync } from "child_process";
|
|
486
67
|
var DEFAULT_MAX_NONPRODUCTIVE_TURNS = 3;
|
|
487
68
|
function resolveMaxNonProductiveTurns(env) {
|
|
@@ -490,10 +71,14 @@ function resolveMaxNonProductiveTurns(env) {
|
|
|
490
71
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_MAX_NONPRODUCTIVE_TURNS;
|
|
491
72
|
}
|
|
492
73
|
function captureTurnWorkspaceSnapshot(cwd) {
|
|
493
|
-
const result = spawnSync(
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
74
|
+
const result = spawnSync(
|
|
75
|
+
"git",
|
|
76
|
+
["status", "--porcelain=v1", "--untracked-files=all"],
|
|
77
|
+
{
|
|
78
|
+
cwd,
|
|
79
|
+
encoding: "utf8"
|
|
80
|
+
}
|
|
81
|
+
);
|
|
497
82
|
if (result.status !== 0) {
|
|
498
83
|
return {
|
|
499
84
|
fingerprint: null,
|
|
@@ -539,7 +124,7 @@ function normalizeError(value) {
|
|
|
539
124
|
return normalized.length > 0 ? normalized : null;
|
|
540
125
|
}
|
|
541
126
|
|
|
542
|
-
// ../worker/
|
|
127
|
+
// ../worker/src/run-phase.ts
|
|
543
128
|
var TERMINAL_RUN_PHASES = /* @__PURE__ */ new Set([
|
|
544
129
|
"succeeded",
|
|
545
130
|
"failed",
|
|
@@ -554,7 +139,7 @@ function resolveExitRunPhase(currentRunPhase, exit) {
|
|
|
554
139
|
return exit.code === 0 && !exit.signal ? "succeeded" : "failed";
|
|
555
140
|
}
|
|
556
141
|
|
|
557
|
-
// ../worker/
|
|
142
|
+
// ../worker/src/thread-resume.ts
|
|
558
143
|
var DEFAULT_CONTINUATION_GUIDANCE = "Continue working on the issue. Review your progress and complete any remaining tasks.";
|
|
559
144
|
function parseNonNegativeInteger(value) {
|
|
560
145
|
const parsed = typeof value === "number" ? value : Number.parseInt(value ?? "", 10);
|
|
@@ -563,11 +148,17 @@ function parseNonNegativeInteger(value) {
|
|
|
563
148
|
}
|
|
564
149
|
return Math.floor(parsed);
|
|
565
150
|
}
|
|
566
|
-
function buildContinuationTurnInput({
|
|
151
|
+
function buildContinuationTurnInput({
|
|
152
|
+
continuationGuidance,
|
|
153
|
+
lastTurnSummary,
|
|
154
|
+
cumulativeTurnCount = 0
|
|
155
|
+
}) {
|
|
567
156
|
const template = continuationGuidance?.trim() || DEFAULT_CONTINUATION_GUIDANCE;
|
|
568
157
|
return renderContinuationGuidance(template, {
|
|
569
158
|
lastTurnSummary: normalizeContinuationVariable(lastTurnSummary) ?? "No previous turn summary was captured.",
|
|
570
|
-
cumulativeTurnCount: String(
|
|
159
|
+
cumulativeTurnCount: String(
|
|
160
|
+
Math.max(0, parseNonNegativeInteger(cumulativeTurnCount))
|
|
161
|
+
)
|
|
571
162
|
});
|
|
572
163
|
}
|
|
573
164
|
function normalizeContinuationVariable(value) {
|
|
@@ -576,7 +167,9 @@ function normalizeContinuationVariable(value) {
|
|
|
576
167
|
}
|
|
577
168
|
function renderContinuationGuidance(template, variables) {
|
|
578
169
|
if (template.includes("{%") || template.includes("%}")) {
|
|
579
|
-
throw new Error(
|
|
170
|
+
throw new Error(
|
|
171
|
+
"template_parse_error: continuation guidance does not support Liquid tags."
|
|
172
|
+
);
|
|
580
173
|
}
|
|
581
174
|
let rendered = "";
|
|
582
175
|
let lastIndex = 0;
|
|
@@ -587,7 +180,9 @@ function renderContinuationGuidance(template, variables) {
|
|
|
587
180
|
const index = match.index ?? 0;
|
|
588
181
|
rendered += template.slice(lastIndex, index);
|
|
589
182
|
if (!(expression in variables)) {
|
|
590
|
-
throw new Error(
|
|
183
|
+
throw new Error(
|
|
184
|
+
`template_render_error: unsupported continuation guidance variable '${expression}'.`
|
|
185
|
+
);
|
|
591
186
|
}
|
|
592
187
|
rendered += variables[expression] ?? "";
|
|
593
188
|
lastIndex = index + matchedText.length;
|
|
@@ -595,12 +190,14 @@ function renderContinuationGuidance(template, variables) {
|
|
|
595
190
|
rendered += template.slice(lastIndex);
|
|
596
191
|
const strayLiquidExpression = rendered.match(/\{\{[^}]*\}\}/);
|
|
597
192
|
if (strayLiquidExpression) {
|
|
598
|
-
throw new Error(
|
|
193
|
+
throw new Error(
|
|
194
|
+
`template_parse_error: invalid continuation guidance expression '${strayLiquidExpression[0]}'.`
|
|
195
|
+
);
|
|
599
196
|
}
|
|
600
197
|
return rendered;
|
|
601
198
|
}
|
|
602
199
|
|
|
603
|
-
// ../worker/
|
|
200
|
+
// ../worker/src/turn-limits.ts
|
|
604
201
|
var DEFAULT_SESSION_MAX_TURNS = 20;
|
|
605
202
|
function resolveMaxTurns(value) {
|
|
606
203
|
const parsed = typeof value === "number" ? value : Number(value);
|
|
@@ -623,21 +220,27 @@ function resolveMaxTurns(value) {
|
|
|
623
220
|
};
|
|
624
221
|
}
|
|
625
222
|
|
|
626
|
-
// ../worker/
|
|
627
|
-
import { mkdir
|
|
628
|
-
import { dirname
|
|
223
|
+
// ../worker/src/token-usage.ts
|
|
224
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
225
|
+
import { dirname, join } from "path";
|
|
629
226
|
async function persistTokenUsageArtifact(env, tokenUsage) {
|
|
630
227
|
const artifactPath = resolveTokenUsageArtifactPath(env);
|
|
631
228
|
if (!artifactPath) {
|
|
632
229
|
return;
|
|
633
230
|
}
|
|
634
231
|
try {
|
|
635
|
-
await
|
|
636
|
-
await
|
|
232
|
+
await mkdir(dirname(artifactPath), { recursive: true });
|
|
233
|
+
await writeFile(
|
|
234
|
+
artifactPath,
|
|
235
|
+
JSON.stringify(tokenUsage, null, 2) + "\n",
|
|
236
|
+
"utf8"
|
|
237
|
+
);
|
|
637
238
|
} catch (error) {
|
|
638
239
|
const message = error instanceof Error ? error.message : String(error);
|
|
639
|
-
process.stderr.write(
|
|
640
|
-
`
|
|
240
|
+
process.stderr.write(
|
|
241
|
+
`[worker] failed to persist token usage artifact: ${message}
|
|
242
|
+
`
|
|
243
|
+
);
|
|
641
244
|
}
|
|
642
245
|
}
|
|
643
246
|
function resolveTokenUsageArtifactPath(env) {
|
|
@@ -645,10 +248,10 @@ function resolveTokenUsageArtifactPath(env) {
|
|
|
645
248
|
if (!workspaceRuntimeDir) {
|
|
646
249
|
return null;
|
|
647
250
|
}
|
|
648
|
-
return
|
|
251
|
+
return join(workspaceRuntimeDir, "token-usage.json");
|
|
649
252
|
}
|
|
650
253
|
|
|
651
|
-
// ../worker/
|
|
254
|
+
// ../worker/src/index.ts
|
|
652
255
|
var launcherEnv = loadLauncherEnvironment(process.env);
|
|
653
256
|
var runtimeState = {
|
|
654
257
|
status: launcherEnv.SYMPHONY_RUN_ID ? "starting" : "idle",
|
|
@@ -684,10 +287,16 @@ var runtimeState = {
|
|
|
684
287
|
exitClassification: null
|
|
685
288
|
}
|
|
686
289
|
};
|
|
687
|
-
console.log(
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
290
|
+
console.log(
|
|
291
|
+
JSON.stringify(
|
|
292
|
+
{
|
|
293
|
+
package: "@gh-symphony/worker",
|
|
294
|
+
runtime: "self-hosted-sample"
|
|
295
|
+
},
|
|
296
|
+
null,
|
|
297
|
+
2
|
|
298
|
+
)
|
|
299
|
+
);
|
|
691
300
|
var childProcess = null;
|
|
692
301
|
var shutdownPromise = null;
|
|
693
302
|
var orchestratorChannelDrainPending = false;
|
|
@@ -722,7 +331,9 @@ function shutdown(signal) {
|
|
|
722
331
|
stopOrchestratorHeartbeatTimer();
|
|
723
332
|
emitOrchestratorHeartbeat();
|
|
724
333
|
await persistSessionTokenUsageArtifact(launcherEnv);
|
|
725
|
-
await waitForPendingOrchestratorChannelFlush(
|
|
334
|
+
await waitForPendingOrchestratorChannelFlush(
|
|
335
|
+
resolveTerminalOrchestratorChannelFlushTimeoutMs()
|
|
336
|
+
);
|
|
726
337
|
console.log(`Worker stopped on ${signal}`);
|
|
727
338
|
process.exit(0);
|
|
728
339
|
})();
|
|
@@ -755,13 +366,13 @@ function waitForPendingOrchestratorChannelFlush(timeoutMs = ORCHESTRATOR_CHANNEL
|
|
|
755
366
|
if (!orchestratorChannelDrainPending && pendingOrchestratorChannelPayloads.length === 0) {
|
|
756
367
|
return Promise.resolve();
|
|
757
368
|
}
|
|
758
|
-
return new Promise((
|
|
369
|
+
return new Promise((resolve) => {
|
|
759
370
|
let settled = false;
|
|
760
371
|
let timeout = setTimeout(() => {
|
|
761
372
|
settled = true;
|
|
762
373
|
process.stderr.removeListener("drain", handleDrain);
|
|
763
374
|
timeout = null;
|
|
764
|
-
|
|
375
|
+
resolve();
|
|
765
376
|
}, timeoutMs);
|
|
766
377
|
const handleDrain = () => {
|
|
767
378
|
if (orchestratorChannelDrainPending || pendingOrchestratorChannelPayloads.length > 0) {
|
|
@@ -776,14 +387,17 @@ function waitForPendingOrchestratorChannelFlush(timeoutMs = ORCHESTRATOR_CHANNEL
|
|
|
776
387
|
timeout = null;
|
|
777
388
|
}
|
|
778
389
|
process.stderr.removeListener("drain", handleDrain);
|
|
779
|
-
|
|
390
|
+
resolve();
|
|
780
391
|
};
|
|
781
392
|
process.stderr.on("drain", handleDrain);
|
|
782
393
|
});
|
|
783
394
|
}
|
|
784
395
|
function resolveTerminalOrchestratorChannelFlushTimeoutMs() {
|
|
785
396
|
const pendingPayloadCount = pendingOrchestratorChannelPayloads.length + (orchestratorChannelDrainPending ? 1 : 0);
|
|
786
|
-
return Math.max(
|
|
397
|
+
return Math.max(
|
|
398
|
+
ORCHESTRATOR_CHANNEL_FLUSH_TIMEOUT_MS,
|
|
399
|
+
pendingPayloadCount * ORCHESTRATOR_CHANNEL_FLUSH_TIMEOUT_MS
|
|
400
|
+
);
|
|
787
401
|
}
|
|
788
402
|
function writeOrQueueOrchestratorChannelPayload(serializedPayload) {
|
|
789
403
|
if (orchestratorChannelDrainPending) {
|
|
@@ -861,9 +475,18 @@ function cloneTokenUsageSnapshot() {
|
|
|
861
475
|
}
|
|
862
476
|
function resolveTurnTokenUsageDelta(baseline) {
|
|
863
477
|
return {
|
|
864
|
-
inputTokens: Math.max(
|
|
865
|
-
|
|
866
|
-
|
|
478
|
+
inputTokens: Math.max(
|
|
479
|
+
0,
|
|
480
|
+
runtimeState.tokenUsage.inputTokens - baseline.inputTokens
|
|
481
|
+
),
|
|
482
|
+
outputTokens: Math.max(
|
|
483
|
+
0,
|
|
484
|
+
runtimeState.tokenUsage.outputTokens - baseline.outputTokens
|
|
485
|
+
),
|
|
486
|
+
totalTokens: Math.max(
|
|
487
|
+
0,
|
|
488
|
+
runtimeState.tokenUsage.totalTokens - baseline.totalTokens
|
|
489
|
+
)
|
|
867
490
|
};
|
|
868
491
|
}
|
|
869
492
|
function resolveSessionTokenUsageDelta() {
|
|
@@ -900,7 +523,10 @@ function emitTurnCompletedEvent(turn) {
|
|
|
900
523
|
issueId,
|
|
901
524
|
startedAt: turn.startedAt,
|
|
902
525
|
completedAt,
|
|
903
|
-
durationMs: Math.max(
|
|
526
|
+
durationMs: Math.max(
|
|
527
|
+
0,
|
|
528
|
+
new Date(completedAt).getTime() - new Date(turn.startedAt).getTime()
|
|
529
|
+
),
|
|
904
530
|
threadId: turn.threadId,
|
|
905
531
|
turnId: turn.turnId,
|
|
906
532
|
turnCount: turn.turnCount,
|
|
@@ -921,7 +547,10 @@ function emitTurnFailedEvent(turn, error) {
|
|
|
921
547
|
issueId,
|
|
922
548
|
startedAt: turn.startedAt,
|
|
923
549
|
failedAt,
|
|
924
|
-
durationMs: Math.max(
|
|
550
|
+
durationMs: Math.max(
|
|
551
|
+
0,
|
|
552
|
+
new Date(failedAt).getTime() - new Date(turn.startedAt).getTime()
|
|
553
|
+
),
|
|
925
554
|
threadId: turn.threadId,
|
|
926
555
|
turnId: turn.turnId,
|
|
927
556
|
turnCount: turn.turnCount,
|
|
@@ -934,16 +563,42 @@ function emitTurnFailedEvent(turn, error) {
|
|
|
934
563
|
}
|
|
935
564
|
async function startAssignedRun() {
|
|
936
565
|
try {
|
|
937
|
-
const workflowPath = launcherEnv.SYMPHONY_WORKFLOW_PATH ||
|
|
566
|
+
const workflowPath = launcherEnv.SYMPHONY_WORKFLOW_PATH || join2(launcherEnv.WORKING_DIRECTORY, "WORKFLOW.md");
|
|
938
567
|
runtimeState.runPhase = "building_prompt";
|
|
939
|
-
const workflow = parseWorkflowMarkdown(
|
|
568
|
+
const workflow = parseWorkflowMarkdown(
|
|
569
|
+
await readFile(workflowPath, "utf8"),
|
|
570
|
+
launcherEnv
|
|
571
|
+
);
|
|
572
|
+
if (isClaudeRuntimeCommand(workflow.codex.command)) {
|
|
573
|
+
const hasGitHubGraphqlToken = typeof launcherEnv.GITHUB_GRAPHQL_TOKEN === "string" && launcherEnv.GITHUB_GRAPHQL_TOKEN.trim().length > 0;
|
|
574
|
+
const preflight = await runClaudePreflight({
|
|
575
|
+
cwd: launcherEnv.WORKING_DIRECTORY,
|
|
576
|
+
env: launcherEnv,
|
|
577
|
+
command: resolveClaudeCommandBinary(workflow.codex.command) ?? void 0,
|
|
578
|
+
includeGhAuth: !hasGitHubGraphqlToken
|
|
579
|
+
});
|
|
580
|
+
process.stderr.write(
|
|
581
|
+
`[worker] ${formatClaudePreflightText(preflight)}
|
|
582
|
+
`
|
|
583
|
+
);
|
|
584
|
+
if (!preflight.ok) {
|
|
585
|
+
await exitWorkerStartupFailure(
|
|
586
|
+
"Claude runtime preflight failed before agent launch."
|
|
587
|
+
);
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
await exitWorkerStartupFailure(
|
|
591
|
+
"Claude runtime worker launch is not yet implemented in this branch."
|
|
592
|
+
);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
940
595
|
runtimeState.executionPhase = resolveInitialExecutionPhase({
|
|
941
596
|
issueState: runtimeState.run?.state,
|
|
942
597
|
blockerCheckStates: workflow.lifecycle.blockerCheckStates,
|
|
943
598
|
activeStates: workflow.lifecycle.activeStates
|
|
944
599
|
});
|
|
945
600
|
const config = resolveLocalRuntimeLaunchConfig(launcherEnv);
|
|
946
|
-
config.agentCommand = workflow
|
|
601
|
+
config.agentCommand = resolveWorkflowRuntimeCommand(workflow);
|
|
947
602
|
runtimeState.runPhase = "launching_agent";
|
|
948
603
|
const plan = await prepareCodexRuntimePlan(config);
|
|
949
604
|
childProcess = launchCodexAppServer(plan);
|
|
@@ -955,24 +610,27 @@ async function startAssignedRun() {
|
|
|
955
610
|
void runCodexClientProtocol(childProcess, plan, launcherEnv, {
|
|
956
611
|
continuationGuidance: workflow.continuationGuidance
|
|
957
612
|
});
|
|
958
|
-
childProcess.once(
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
}
|
|
968
|
-
runtimeState.runPhase = nextRunPhase;
|
|
969
|
-
if (runtimeState.run) {
|
|
613
|
+
childProcess.once(
|
|
614
|
+
"exit",
|
|
615
|
+
(code, signal) => {
|
|
616
|
+
const currentRunPhase = runtimeState.runPhase;
|
|
617
|
+
const nextRunPhase = resolveExitRunPhase(currentRunPhase, {
|
|
618
|
+
code,
|
|
619
|
+
signal
|
|
620
|
+
});
|
|
621
|
+
const preservesTerminalPhase = currentRunPhase != null && nextRunPhase === currentRunPhase;
|
|
970
622
|
if (!preservesTerminalPhase) {
|
|
971
|
-
runtimeState.
|
|
623
|
+
runtimeState.status = code === 0 && !signal ? "completed" : "failed";
|
|
972
624
|
}
|
|
625
|
+
runtimeState.runPhase = nextRunPhase;
|
|
626
|
+
if (runtimeState.run) {
|
|
627
|
+
if (!preservesTerminalPhase) {
|
|
628
|
+
runtimeState.run.lastError = code === 0 && !signal ? null : `codex app-server exited with ${signal ?? code ?? "unknown"}`;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
void persistSessionTokenUsageArtifact(launcherEnv);
|
|
973
632
|
}
|
|
974
|
-
|
|
975
|
-
});
|
|
633
|
+
);
|
|
976
634
|
childProcess.once("error", (error) => {
|
|
977
635
|
runtimeState.status = "failed";
|
|
978
636
|
runtimeState.runPhase = "failed";
|
|
@@ -990,17 +648,39 @@ async function startAssignedRun() {
|
|
|
990
648
|
await persistSessionTokenUsageArtifact(launcherEnv);
|
|
991
649
|
}
|
|
992
650
|
}
|
|
651
|
+
async function exitWorkerStartupFailure(message) {
|
|
652
|
+
runtimeState.status = "failed";
|
|
653
|
+
runtimeState.runPhase = "failed";
|
|
654
|
+
if (runtimeState.run) {
|
|
655
|
+
runtimeState.run.lastError = message;
|
|
656
|
+
}
|
|
657
|
+
process.stderr.write(`[worker] ${message}
|
|
658
|
+
`);
|
|
659
|
+
stopOrchestratorHeartbeatTimer();
|
|
660
|
+
emitOrchestratorHeartbeat();
|
|
661
|
+
await persistSessionTokenUsageArtifact(launcherEnv);
|
|
662
|
+
await waitForPendingOrchestratorChannelFlush(
|
|
663
|
+
resolveTerminalOrchestratorChannelFlushTimeoutMs()
|
|
664
|
+
);
|
|
665
|
+
process.exit(1);
|
|
666
|
+
}
|
|
993
667
|
async function runCodexClientProtocol(child, plan, env, options) {
|
|
994
668
|
const renderedPrompt = env.SYMPHONY_RENDERED_PROMPT;
|
|
995
669
|
if (!renderedPrompt) {
|
|
996
|
-
process.stderr.write(
|
|
670
|
+
process.stderr.write(
|
|
671
|
+
"[worker] SYMPHONY_RENDERED_PROMPT not set; skipping codex client protocol\n"
|
|
672
|
+
);
|
|
997
673
|
return;
|
|
998
674
|
}
|
|
999
675
|
if (!child.stdin || !child.stdout) {
|
|
1000
|
-
process.stderr.write(
|
|
676
|
+
process.stderr.write(
|
|
677
|
+
"[worker] codex process has no stdio pipes; cannot run client protocol\n"
|
|
678
|
+
);
|
|
1001
679
|
return;
|
|
1002
680
|
}
|
|
1003
|
-
const { maxTurns, exhaustedBeforeStart } = resolveMaxTurns(
|
|
681
|
+
const { maxTurns, exhaustedBeforeStart } = resolveMaxTurns(
|
|
682
|
+
env.SYMPHONY_MAX_TURNS
|
|
683
|
+
);
|
|
1004
684
|
const readTimeoutMs = Number(env.SYMPHONY_READ_TIMEOUT_MS) || 5e3;
|
|
1005
685
|
const turnTimeoutMs = Number(env.SYMPHONY_TURN_TIMEOUT_MS) || 36e5;
|
|
1006
686
|
const maxNonProductiveTurns = resolveMaxNonProductiveTurns(env);
|
|
@@ -1015,10 +695,11 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1015
695
|
let lineBuffer = "";
|
|
1016
696
|
let deltaBuffer = null;
|
|
1017
697
|
function flushDeltaBuffer() {
|
|
1018
|
-
if (!deltaBuffer)
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
`
|
|
698
|
+
if (!deltaBuffer) return;
|
|
699
|
+
process.stderr.write(
|
|
700
|
+
`[worker] codex \u2192 agent_message [accumulated] ${JSON.stringify({ text: deltaBuffer.text }).slice(0, 500)}
|
|
701
|
+
`
|
|
702
|
+
);
|
|
1022
703
|
deltaBuffer = null;
|
|
1023
704
|
}
|
|
1024
705
|
const pendingRequests = /* @__PURE__ */ new Map();
|
|
@@ -1035,7 +716,8 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1035
716
|
}
|
|
1036
717
|
}
|
|
1037
718
|
function describeTurnTerminalEvent(event, params) {
|
|
1038
|
-
const
|
|
719
|
+
const errorPrefix = event === "agent.turnFailed" ? "turn_failed" : "turn_cancelled";
|
|
720
|
+
const fallback = event === "agent.turnFailed" ? "turn_failed: codex reported turn failure" : "turn_cancelled: codex reported turn cancellation";
|
|
1039
721
|
if (!params || typeof params !== "object") {
|
|
1040
722
|
return fallback;
|
|
1041
723
|
}
|
|
@@ -1044,18 +726,18 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1044
726
|
for (const key of directReasonKeys) {
|
|
1045
727
|
const value = record[key];
|
|
1046
728
|
if (typeof value === "string" && value.trim()) {
|
|
1047
|
-
return `${
|
|
729
|
+
return `${errorPrefix}: ${value.trim()}`;
|
|
1048
730
|
}
|
|
1049
731
|
if (value && typeof value === "object" && typeof value.message === "string") {
|
|
1050
732
|
const nested = value;
|
|
1051
733
|
const nestedMessage = String(nested.message).trim();
|
|
1052
734
|
if (nestedMessage) {
|
|
1053
|
-
return `${
|
|
735
|
+
return `${errorPrefix}: ${nestedMessage}`;
|
|
1054
736
|
}
|
|
1055
737
|
}
|
|
1056
738
|
}
|
|
1057
739
|
const serialized = JSON.stringify(params).slice(0, 300);
|
|
1058
|
-
return serialized && serialized !== "{}" ? `${
|
|
740
|
+
return serialized && serialized !== "{}" ? `${errorPrefix}: ${serialized}` : fallback;
|
|
1059
741
|
}
|
|
1060
742
|
function markTurnTerminalFailure(runPhase, lastError) {
|
|
1061
743
|
runtimeState.status = "failed";
|
|
@@ -1075,36 +757,45 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1075
757
|
child.stdin?.write(line);
|
|
1076
758
|
}
|
|
1077
759
|
function sendRequest(id, method, params) {
|
|
1078
|
-
return new Promise((
|
|
1079
|
-
pendingRequests.set(id, { resolve
|
|
760
|
+
return new Promise((resolve, reject) => {
|
|
761
|
+
pendingRequests.set(id, { resolve, reject });
|
|
1080
762
|
sendMessage({ jsonrpc: "2.0", id, method, params });
|
|
1081
763
|
});
|
|
1082
764
|
}
|
|
1083
765
|
function sendRequestWithTimeout(id, method, params) {
|
|
1084
|
-
return new Promise((
|
|
766
|
+
return new Promise((resolve, reject) => {
|
|
1085
767
|
const timer = setTimeout(() => {
|
|
1086
768
|
pendingRequests.delete(id);
|
|
1087
|
-
reject(
|
|
769
|
+
reject(
|
|
770
|
+
new Error(
|
|
771
|
+
`response_timeout: ${method} timed out after ${readTimeoutMs}ms`
|
|
772
|
+
)
|
|
773
|
+
);
|
|
1088
774
|
}, readTimeoutMs);
|
|
1089
|
-
sendRequest(id, method, params).then(
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
775
|
+
sendRequest(id, method, params).then(
|
|
776
|
+
(result) => {
|
|
777
|
+
clearTimeout(timer);
|
|
778
|
+
resolve(result);
|
|
779
|
+
},
|
|
780
|
+
(error) => {
|
|
781
|
+
clearTimeout(timer);
|
|
782
|
+
reject(error);
|
|
783
|
+
}
|
|
784
|
+
);
|
|
1096
785
|
});
|
|
1097
786
|
}
|
|
1098
787
|
function waitForTurnCompletion() {
|
|
1099
|
-
return new Promise((
|
|
1100
|
-
turnCompletedResolve =
|
|
788
|
+
return new Promise((resolve) => {
|
|
789
|
+
turnCompletedResolve = resolve;
|
|
1101
790
|
});
|
|
1102
791
|
}
|
|
1103
792
|
function waitForTurnWithTimeout() {
|
|
1104
|
-
return new Promise((
|
|
793
|
+
return new Promise((resolve, reject) => {
|
|
1105
794
|
const timer = setTimeout(() => {
|
|
1106
|
-
process.stderr.write(
|
|
1107
|
-
`
|
|
795
|
+
process.stderr.write(
|
|
796
|
+
`[worker] turn_timeout: turn exceeded ${turnTimeoutMs}ms \u2014 killing codex process
|
|
797
|
+
`
|
|
798
|
+
);
|
|
1108
799
|
if (child.pid) {
|
|
1109
800
|
try {
|
|
1110
801
|
process.kill(child.pid, "SIGTERM");
|
|
@@ -1113,20 +804,27 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1113
804
|
}
|
|
1114
805
|
reject(new Error("turn_timeout: turn exceeded time limit"));
|
|
1115
806
|
}, turnTimeoutMs);
|
|
1116
|
-
waitForTurnCompletion().then(
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
807
|
+
waitForTurnCompletion().then(
|
|
808
|
+
() => {
|
|
809
|
+
clearTimeout(timer);
|
|
810
|
+
resolve();
|
|
811
|
+
},
|
|
812
|
+
(error) => {
|
|
813
|
+
clearTimeout(timer);
|
|
814
|
+
reject(error);
|
|
815
|
+
}
|
|
816
|
+
);
|
|
1123
817
|
});
|
|
1124
818
|
}
|
|
1125
819
|
async function dispatchDynamicToolCall(callId, toolName, threadId, turnId, args) {
|
|
1126
|
-
const toolDef = plan.tools.find(
|
|
820
|
+
const toolDef = plan.tools.find(
|
|
821
|
+
(t) => t.name === toolName
|
|
822
|
+
);
|
|
1127
823
|
if (!toolDef) {
|
|
1128
|
-
process.stderr.write(
|
|
1129
|
-
`
|
|
824
|
+
process.stderr.write(
|
|
825
|
+
`[worker] unknown dynamic tool: ${toolName}; sending error response
|
|
826
|
+
`
|
|
827
|
+
);
|
|
1130
828
|
sendMessage({
|
|
1131
829
|
jsonrpc: "2.0",
|
|
1132
830
|
method: "dynamic_tool_call_response",
|
|
@@ -1146,8 +844,10 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1146
844
|
return;
|
|
1147
845
|
}
|
|
1148
846
|
const inputJson = JSON.stringify(args ?? {});
|
|
1149
|
-
process.stderr.write(
|
|
1150
|
-
`)
|
|
847
|
+
process.stderr.write(
|
|
848
|
+
`[worker] executing dynamic tool "${toolName}" (callId=${callId})
|
|
849
|
+
`
|
|
850
|
+
);
|
|
1151
851
|
try {
|
|
1152
852
|
const output = await runToolProcess(toolDef, inputJson);
|
|
1153
853
|
sendMessage({
|
|
@@ -1178,138 +878,185 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1178
878
|
});
|
|
1179
879
|
}
|
|
1180
880
|
}
|
|
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
|
-
}
|
|
881
|
+
function emitObservedAgentEvent(event) {
|
|
882
|
+
if (event.payload.suppressUpdate) {
|
|
1193
883
|
return;
|
|
1194
884
|
}
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
885
|
+
emitOrchestratorChannelEvent(getCodexObservabilityEventName(event));
|
|
886
|
+
}
|
|
887
|
+
function handleInputRequired(reason, event) {
|
|
888
|
+
process.stderr.write(
|
|
889
|
+
"[worker] user_input_required detected \u2014 terminating agent process\n"
|
|
890
|
+
);
|
|
891
|
+
userInputRequired = true;
|
|
892
|
+
runtimeState.status = "failed";
|
|
893
|
+
if (runtimeState.run) {
|
|
894
|
+
runtimeState.run.lastError = reason;
|
|
1202
895
|
}
|
|
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;
|
|
896
|
+
if (child.pid) {
|
|
897
|
+
try {
|
|
898
|
+
process.kill(child.pid, "SIGTERM");
|
|
899
|
+
} catch {
|
|
1219
900
|
}
|
|
1220
|
-
resolvePendingTurnCompletion();
|
|
1221
|
-
emitOrchestratorChannelEvent(orchestratorEventName);
|
|
1222
|
-
return;
|
|
1223
901
|
}
|
|
1224
|
-
if (
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
902
|
+
if (activeTurnTelemetry) {
|
|
903
|
+
emitTurnFailedEvent(
|
|
904
|
+
activeTurnTelemetry,
|
|
905
|
+
runtimeState.run?.lastError ?? null
|
|
906
|
+
);
|
|
907
|
+
activeTurnTelemetry = null;
|
|
908
|
+
}
|
|
909
|
+
resolvePendingTurnCompletion();
|
|
910
|
+
emitObservedAgentEvent(event);
|
|
911
|
+
}
|
|
912
|
+
function handleAgentEvent(event) {
|
|
913
|
+
switch (event.name) {
|
|
914
|
+
case "agent.turnStarted":
|
|
915
|
+
emitObservedAgentEvent(event);
|
|
916
|
+
return true;
|
|
917
|
+
case "agent.toolCallRequested":
|
|
918
|
+
if (!event.payload.threadId || !event.payload.turnId) {
|
|
919
|
+
process.stderr.write(
|
|
920
|
+
`[worker] dynamic tool call ${event.payload.callId} is missing threadId/turnId; cannot send response
|
|
921
|
+
`
|
|
922
|
+
);
|
|
923
|
+
emitObservedAgentEvent(event);
|
|
924
|
+
return true;
|
|
925
|
+
}
|
|
926
|
+
void dispatchDynamicToolCall(
|
|
927
|
+
event.payload.callId,
|
|
928
|
+
event.payload.toolName,
|
|
929
|
+
event.payload.threadId,
|
|
930
|
+
event.payload.turnId,
|
|
931
|
+
event.payload.arguments
|
|
932
|
+
);
|
|
933
|
+
emitObservedAgentEvent(event);
|
|
934
|
+
return true;
|
|
935
|
+
case "agent.inputRequired":
|
|
936
|
+
handleInputRequired(event.payload.reason, event);
|
|
937
|
+
return true;
|
|
938
|
+
case "agent.tokenUsageUpdated": {
|
|
939
|
+
const tokenUsage = extractAbsoluteTokenUsage(event.payload.params);
|
|
940
|
+
if (tokenUsage) {
|
|
941
|
+
applyTokenUsageUpdate(
|
|
942
|
+
getCodexObservabilityEventName(event) ?? event.name,
|
|
943
|
+
tokenUsage
|
|
944
|
+
);
|
|
945
|
+
}
|
|
946
|
+
emitObservedAgentEvent(event);
|
|
947
|
+
return true;
|
|
1230
948
|
}
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
949
|
+
case "agent.rateLimit": {
|
|
950
|
+
const rateLimits = extractRateLimitPayload(event.payload.params);
|
|
951
|
+
if (rateLimits) {
|
|
952
|
+
applyRateLimitUpdate(
|
|
953
|
+
getCodexObservabilityEventName(event) ?? event.name,
|
|
954
|
+
rateLimits
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
emitObservedAgentEvent(event);
|
|
958
|
+
return true;
|
|
1234
959
|
}
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
960
|
+
case "agent.messageDelta": {
|
|
961
|
+
const { delta, itemId } = event.payload;
|
|
962
|
+
if (deltaBuffer?.itemId !== itemId) {
|
|
963
|
+
flushDeltaBuffer();
|
|
964
|
+
deltaBuffer = { itemId, text: delta };
|
|
965
|
+
} else {
|
|
966
|
+
deltaBuffer.text += delta;
|
|
1241
967
|
}
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
968
|
+
emitObservedAgentEvent(event);
|
|
969
|
+
return true;
|
|
970
|
+
}
|
|
971
|
+
case "agent.turnCompleted":
|
|
972
|
+
flushDeltaBuffer();
|
|
973
|
+
if (event.payload.inputRequired) {
|
|
974
|
+
handleInputRequired(DEFAULT_AGENT_INPUT_REQUIRED_REASON, event);
|
|
975
|
+
return true;
|
|
1247
976
|
}
|
|
977
|
+
emitObservedAgentEvent(event);
|
|
1248
978
|
if (activeTurnTelemetry) {
|
|
1249
|
-
|
|
979
|
+
emitTurnCompletedEvent(activeTurnTelemetry);
|
|
1250
980
|
activeTurnTelemetry = null;
|
|
1251
981
|
}
|
|
982
|
+
process.stderr.write("[worker] agent turn completed\n");
|
|
1252
983
|
resolvePendingTurnCompletion();
|
|
1253
|
-
|
|
1254
|
-
|
|
984
|
+
return true;
|
|
985
|
+
case "agent.turnFailed": {
|
|
986
|
+
flushDeltaBuffer();
|
|
987
|
+
const lastError = describeTurnTerminalEvent(
|
|
988
|
+
"agent.turnFailed",
|
|
989
|
+
event.payload.params
|
|
990
|
+
);
|
|
991
|
+
process.stderr.write(
|
|
992
|
+
`[worker] agent turn failed ${JSON.stringify(event.payload.params).slice(0, 300)}
|
|
993
|
+
`
|
|
994
|
+
);
|
|
995
|
+
markTurnTerminalFailure("failed", lastError);
|
|
996
|
+
emitObservedAgentEvent(event);
|
|
997
|
+
return true;
|
|
1255
998
|
}
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
999
|
+
case "agent.turnCancelled": {
|
|
1000
|
+
flushDeltaBuffer();
|
|
1001
|
+
const lastError = describeTurnTerminalEvent(
|
|
1002
|
+
"agent.turnCancelled",
|
|
1003
|
+
event.payload.params
|
|
1004
|
+
);
|
|
1005
|
+
process.stderr.write(
|
|
1006
|
+
`[worker] agent turn cancelled ${JSON.stringify(event.payload.params).slice(0, 300)}
|
|
1007
|
+
`
|
|
1008
|
+
);
|
|
1009
|
+
markTurnTerminalFailure("canceled_by_reconciliation", lastError);
|
|
1010
|
+
emitObservedAgentEvent(event);
|
|
1011
|
+
return true;
|
|
1260
1012
|
}
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1013
|
+
case "agent.error":
|
|
1014
|
+
flushDeltaBuffer();
|
|
1015
|
+
process.stderr.write(
|
|
1016
|
+
`[worker] runtime error ${JSON.stringify(event.payload.params).slice(0, 300)}
|
|
1017
|
+
`
|
|
1018
|
+
);
|
|
1019
|
+
markTurnTerminalFailure("failed", event.payload.error);
|
|
1020
|
+
emitObservedAgentEvent(event);
|
|
1021
|
+
return true;
|
|
1022
|
+
default:
|
|
1023
|
+
return false;
|
|
1264
1024
|
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1025
|
+
}
|
|
1026
|
+
function handleServerMessage(msg) {
|
|
1027
|
+
if ("id" in msg && msg.id != null && ("result" in msg || "error" in msg)) {
|
|
1028
|
+
const id = String(msg.id);
|
|
1029
|
+
const pending = pendingRequests.get(id);
|
|
1030
|
+
if (pending) {
|
|
1031
|
+
pendingRequests.delete(id);
|
|
1032
|
+
if ("error" in msg) {
|
|
1033
|
+
pending.reject(new Error(JSON.stringify(msg.error)));
|
|
1034
|
+
} else {
|
|
1035
|
+
pending.resolve(msg.result);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1272
1038
|
return;
|
|
1273
1039
|
}
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
markTurnTerminalFailure("canceled_by_reconciliation", lastError);
|
|
1280
|
-
emitOrchestratorChannelEvent(orchestratorEventName);
|
|
1281
|
-
return;
|
|
1040
|
+
runtimeState.lastEventAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1041
|
+
const agentEvents = normalizeCodexRuntimeEvents(msg);
|
|
1042
|
+
let handledAgentEvent = false;
|
|
1043
|
+
for (const event of agentEvents) {
|
|
1044
|
+
handledAgentEvent = handleAgentEvent(event) || handledAgentEvent;
|
|
1282
1045
|
}
|
|
1283
|
-
if (
|
|
1284
|
-
const tokenUsage = extractAbsoluteTokenUsage(msg.params);
|
|
1285
|
-
if (tokenUsage) {
|
|
1286
|
-
applyTokenUsageUpdate(msg.method, tokenUsage);
|
|
1287
|
-
}
|
|
1288
|
-
emitOrchestratorChannelEvent(orchestratorEventName);
|
|
1046
|
+
if (handledAgentEvent) {
|
|
1289
1047
|
return;
|
|
1290
1048
|
}
|
|
1291
1049
|
const rateLimits = extractRateLimitPayload(msg.params);
|
|
1292
1050
|
if (rateLimits && typeof msg.method === "string") {
|
|
1293
1051
|
applyRateLimitUpdate(msg.method, rateLimits);
|
|
1294
1052
|
}
|
|
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
1053
|
if (typeof msg.method === "string") {
|
|
1309
1054
|
flushDeltaBuffer();
|
|
1310
|
-
emitOrchestratorChannelEvent(
|
|
1311
|
-
process.stderr.write(
|
|
1312
|
-
`)
|
|
1055
|
+
emitOrchestratorChannelEvent(msg.method);
|
|
1056
|
+
process.stderr.write(
|
|
1057
|
+
`[worker] codex \u2192 ${msg.method} ${JSON.stringify(msg.params ?? {}).slice(0, 300)}
|
|
1058
|
+
`
|
|
1059
|
+
);
|
|
1313
1060
|
}
|
|
1314
1061
|
}
|
|
1315
1062
|
child.stdout.on("data", (chunk) => {
|
|
@@ -1318,8 +1065,7 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1318
1065
|
lineBuffer = lines.pop() ?? "";
|
|
1319
1066
|
for (const line of lines) {
|
|
1320
1067
|
const trimmed = line.trim();
|
|
1321
|
-
if (!trimmed)
|
|
1322
|
-
continue;
|
|
1068
|
+
if (!trimmed) continue;
|
|
1323
1069
|
try {
|
|
1324
1070
|
const msg = JSON.parse(trimmed);
|
|
1325
1071
|
handleServerMessage(msg);
|
|
@@ -1354,30 +1100,42 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1354
1100
|
mcp_servers: mcpServers
|
|
1355
1101
|
}
|
|
1356
1102
|
};
|
|
1357
|
-
process.stderr.write(
|
|
1358
|
-
`)
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1103
|
+
process.stderr.write(
|
|
1104
|
+
`[worker] starting codex thread (mcp_servers: ${Object.keys(mcpServers).join(", ")})
|
|
1105
|
+
`
|
|
1106
|
+
);
|
|
1107
|
+
const threadResult = await sendRequestWithTimeout(
|
|
1108
|
+
"thread-1",
|
|
1109
|
+
"thread/start",
|
|
1110
|
+
{
|
|
1111
|
+
...baseThreadParams,
|
|
1112
|
+
ephemeral: false
|
|
1113
|
+
}
|
|
1114
|
+
);
|
|
1363
1115
|
const threadId = threadResult.thread_id ?? threadResult.thread?.id;
|
|
1364
1116
|
runtimeState.sessionInfo.threadId = threadId ?? null;
|
|
1365
1117
|
runtimeState.sessionInfo.turnId = null;
|
|
1366
1118
|
runtimeState.sessionInfo.sessionId = null;
|
|
1367
1119
|
runtimeState.sessionInfo.exitClassification = null;
|
|
1368
1120
|
runtimeState.sessionId = null;
|
|
1369
|
-
process.stderr.write(
|
|
1370
|
-
`)
|
|
1121
|
+
process.stderr.write(
|
|
1122
|
+
`[worker] codex thread started (id=${String(threadId ?? "unknown")})
|
|
1123
|
+
`
|
|
1124
|
+
);
|
|
1371
1125
|
if (!threadId) {
|
|
1372
|
-
process.stderr.write(
|
|
1126
|
+
process.stderr.write(
|
|
1127
|
+
"[worker] warning: no threadId returned; cannot start turn\n"
|
|
1128
|
+
);
|
|
1373
1129
|
return;
|
|
1374
1130
|
}
|
|
1375
1131
|
let turnCount = 0;
|
|
1376
1132
|
let requestIdCounter = 0;
|
|
1377
1133
|
let maxTurnsReached = exhaustedBeforeStart;
|
|
1378
1134
|
if (exhaustedBeforeStart) {
|
|
1379
|
-
process.stderr.write(
|
|
1380
|
-
`)
|
|
1135
|
+
process.stderr.write(
|
|
1136
|
+
`[worker] max_turns (${String(env.SYMPHONY_MAX_TURNS ?? maxTurns)}) does not allow any turns for this worker session \u2014 exiting
|
|
1137
|
+
`
|
|
1138
|
+
);
|
|
1381
1139
|
}
|
|
1382
1140
|
for (let turn = 0; turn < maxTurns; turn++) {
|
|
1383
1141
|
turnCount = turn + 1;
|
|
@@ -1388,18 +1146,24 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1388
1146
|
continuationGuidance,
|
|
1389
1147
|
cumulativeTurnCount: turn
|
|
1390
1148
|
});
|
|
1391
|
-
process.stderr.write(
|
|
1392
|
-
`)
|
|
1149
|
+
process.stderr.write(
|
|
1150
|
+
`[worker] starting codex turn ${turnCount}/${maxTurns}${isFirstTurn ? " (initial)" : " (continuation)"}
|
|
1151
|
+
`
|
|
1152
|
+
);
|
|
1393
1153
|
requestIdCounter += 1;
|
|
1394
1154
|
const turnRequestId = `turn-${requestIdCounter}`;
|
|
1395
|
-
const turnResult = await sendRequestWithTimeout(
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1155
|
+
const turnResult = await sendRequestWithTimeout(
|
|
1156
|
+
turnRequestId,
|
|
1157
|
+
"turn/start",
|
|
1158
|
+
{
|
|
1159
|
+
threadId,
|
|
1160
|
+
input: [{ type: "text", text: turnInput }],
|
|
1161
|
+
cwd: plan.cwd,
|
|
1162
|
+
title: composeTurnTitle(issueIdentifier, env.SYMPHONY_ISSUE_TITLE),
|
|
1163
|
+
approvalPolicy,
|
|
1164
|
+
sandboxPolicy: turnSandboxPolicy
|
|
1165
|
+
}
|
|
1166
|
+
);
|
|
1403
1167
|
const turnId = turnResult.turn_id ?? turnResult.turn?.id;
|
|
1404
1168
|
const sessionId = threadId && turnId ? `${threadId}-${turnId}` : null;
|
|
1405
1169
|
runtimeState.sessionInfo.turnId = turnId ?? null;
|
|
@@ -1413,10 +1177,14 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1413
1177
|
sessionId,
|
|
1414
1178
|
tokenUsageBaseline: cloneTokenUsageSnapshot()
|
|
1415
1179
|
};
|
|
1416
|
-
process.stderr.write(
|
|
1417
|
-
`)
|
|
1418
|
-
|
|
1419
|
-
|
|
1180
|
+
process.stderr.write(
|
|
1181
|
+
`[worker] codex turn started (id=${String(turnId ?? "unknown")})
|
|
1182
|
+
`
|
|
1183
|
+
);
|
|
1184
|
+
process.stderr.write(
|
|
1185
|
+
`[worker] session_id=${String(sessionId ?? "unknown")}
|
|
1186
|
+
`
|
|
1187
|
+
);
|
|
1420
1188
|
emitTurnStartedEvent(activeTurnTelemetry);
|
|
1421
1189
|
await waitForTurnWithTimeout();
|
|
1422
1190
|
if (userInputRequired) {
|
|
@@ -1424,14 +1192,18 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1424
1192
|
break;
|
|
1425
1193
|
}
|
|
1426
1194
|
if (turnTerminalFailurePhase) {
|
|
1427
|
-
process.stderr.write(
|
|
1428
|
-
`
|
|
1195
|
+
process.stderr.write(
|
|
1196
|
+
`[worker] exiting due to ${turnTerminalFailurePhase}
|
|
1197
|
+
`
|
|
1198
|
+
);
|
|
1429
1199
|
break;
|
|
1430
1200
|
}
|
|
1431
1201
|
if (turn + 1 >= maxTurns) {
|
|
1432
1202
|
maxTurnsReached = true;
|
|
1433
|
-
process.stderr.write(
|
|
1434
|
-
`)
|
|
1203
|
+
process.stderr.write(
|
|
1204
|
+
`[worker] max_turns (${maxTurns}) reached for this worker session \u2014 exiting
|
|
1205
|
+
`
|
|
1206
|
+
);
|
|
1435
1207
|
break;
|
|
1436
1208
|
}
|
|
1437
1209
|
const trackerState = await refreshTrackerState(env);
|
|
@@ -1444,19 +1216,26 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1444
1216
|
trackerState,
|
|
1445
1217
|
userInputRequired: false
|
|
1446
1218
|
});
|
|
1447
|
-
process.stderr.write(
|
|
1219
|
+
process.stderr.write(
|
|
1220
|
+
"[worker] issue no longer actionable \u2014 exiting multi-turn loop\n"
|
|
1221
|
+
);
|
|
1448
1222
|
break;
|
|
1449
1223
|
}
|
|
1450
1224
|
const currentTurnProgressSnapshot = {
|
|
1451
1225
|
...captureTurnWorkspaceSnapshot(plan.cwd),
|
|
1452
1226
|
lastError: runtimeState.run?.lastError ?? null
|
|
1453
1227
|
};
|
|
1454
|
-
const turnProgress = evaluateTurnProgress(
|
|
1228
|
+
const turnProgress = evaluateTurnProgress(
|
|
1229
|
+
previousTurnProgressSnapshot,
|
|
1230
|
+
currentTurnProgressSnapshot
|
|
1231
|
+
);
|
|
1455
1232
|
previousTurnProgressSnapshot = currentTurnProgressSnapshot;
|
|
1456
1233
|
if (turnProgress.nonProductive) {
|
|
1457
1234
|
consecutiveNonProductiveTurns += 1;
|
|
1458
|
-
process.stderr.write(
|
|
1459
|
-
`)
|
|
1235
|
+
process.stderr.write(
|
|
1236
|
+
`[worker] non-productive turn detected (${consecutiveNonProductiveTurns}/${maxNonProductiveTurns})${turnProgress.reason ? `: ${turnProgress.reason}` : ""}
|
|
1237
|
+
`
|
|
1238
|
+
);
|
|
1460
1239
|
} else {
|
|
1461
1240
|
consecutiveNonProductiveTurns = 0;
|
|
1462
1241
|
}
|
|
@@ -1465,13 +1244,17 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1465
1244
|
if (runtimeState.run) {
|
|
1466
1245
|
runtimeState.run.lastError = turnProgress.reason ? `convergence_detected: ${turnProgress.reason}` : "convergence_detected: repeated non-productive turn results";
|
|
1467
1246
|
}
|
|
1468
|
-
process.stderr.write(
|
|
1469
|
-
`
|
|
1247
|
+
process.stderr.write(
|
|
1248
|
+
`[worker] convergence detected after ${consecutiveNonProductiveTurns} non-productive turns \u2014 exiting
|
|
1249
|
+
`
|
|
1250
|
+
);
|
|
1470
1251
|
break;
|
|
1471
1252
|
}
|
|
1472
1253
|
}
|
|
1473
|
-
process.stderr.write(
|
|
1474
|
-
`)
|
|
1254
|
+
process.stderr.write(
|
|
1255
|
+
`[worker] multi-turn loop complete after ${turnCount} turn(s) \u2014 exiting worker
|
|
1256
|
+
`
|
|
1257
|
+
);
|
|
1475
1258
|
runtimeState.runPhase = "finishing";
|
|
1476
1259
|
runtimeState.status = userInputRequired || turnTerminalFailurePhase ? "failed" : "completed";
|
|
1477
1260
|
runtimeState.runPhase = convergenceDetected ? "failed" : userInputRequired ? "failed" : turnTerminalFailurePhase ?? "succeeded";
|
|
@@ -1485,7 +1268,9 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1485
1268
|
stopOrchestratorHeartbeatTimer();
|
|
1486
1269
|
emitOrchestratorHeartbeat();
|
|
1487
1270
|
await persistSessionTokenUsageArtifact(env);
|
|
1488
|
-
await waitForPendingOrchestratorChannelFlush(
|
|
1271
|
+
await waitForPendingOrchestratorChannelFlush(
|
|
1272
|
+
resolveTerminalOrchestratorChannelFlushTimeoutMs()
|
|
1273
|
+
);
|
|
1489
1274
|
setTimeout(() => {
|
|
1490
1275
|
process.exit(userInputRequired || turnTerminalFailurePhase ? 1 : 0);
|
|
1491
1276
|
}, 1500);
|
|
@@ -1517,13 +1302,18 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1517
1302
|
maxTurnsReached: false
|
|
1518
1303
|
});
|
|
1519
1304
|
if (activeTurnTelemetry) {
|
|
1520
|
-
emitTurnFailedEvent(
|
|
1305
|
+
emitTurnFailedEvent(
|
|
1306
|
+
activeTurnTelemetry,
|
|
1307
|
+
runtimeState.run?.lastError ?? errMsg
|
|
1308
|
+
);
|
|
1521
1309
|
activeTurnTelemetry = null;
|
|
1522
1310
|
}
|
|
1523
1311
|
stopOrchestratorHeartbeatTimer();
|
|
1524
1312
|
emitOrchestratorHeartbeat();
|
|
1525
1313
|
await persistSessionTokenUsageArtifact(env);
|
|
1526
|
-
await waitForPendingOrchestratorChannelFlush(
|
|
1314
|
+
await waitForPendingOrchestratorChannelFlush(
|
|
1315
|
+
resolveTerminalOrchestratorChannelFlushTimeoutMs()
|
|
1316
|
+
);
|
|
1527
1317
|
setTimeout(() => {
|
|
1528
1318
|
process.exit(1);
|
|
1529
1319
|
}, 1500);
|
|
@@ -1533,16 +1323,20 @@ function applyTokenUsageUpdate(source, tokenUsage) {
|
|
|
1533
1323
|
runtimeState.tokenUsage.inputTokens = tokenUsage.inputTokens;
|
|
1534
1324
|
runtimeState.tokenUsage.outputTokens = tokenUsage.outputTokens;
|
|
1535
1325
|
runtimeState.tokenUsage.totalTokens = tokenUsage.totalTokens;
|
|
1536
|
-
process.stderr.write(
|
|
1537
|
-
`
|
|
1326
|
+
process.stderr.write(
|
|
1327
|
+
`[worker] token_usage source=${source} input=${tokenUsage.inputTokens} output=${tokenUsage.outputTokens} total=${tokenUsage.totalTokens}
|
|
1328
|
+
`
|
|
1329
|
+
);
|
|
1538
1330
|
}
|
|
1539
1331
|
function applyRateLimitUpdate(source, rateLimits) {
|
|
1540
1332
|
runtimeState.rateLimits = {
|
|
1541
1333
|
...rateLimits,
|
|
1542
1334
|
source: "codex"
|
|
1543
1335
|
};
|
|
1544
|
-
process.stderr.write(
|
|
1545
|
-
`)
|
|
1336
|
+
process.stderr.write(
|
|
1337
|
+
`[worker] rate_limits source=${source} payload=${JSON.stringify(runtimeState.rateLimits).slice(0, 300)}
|
|
1338
|
+
`
|
|
1339
|
+
);
|
|
1546
1340
|
}
|
|
1547
1341
|
function extractRateLimitPayload(value) {
|
|
1548
1342
|
if (!value || typeof value !== "object") {
|
|
@@ -1673,22 +1467,23 @@ async function refreshTrackerState(env) {
|
|
|
1673
1467
|
}
|
|
1674
1468
|
try {
|
|
1675
1469
|
const response = await fetch(`${orchestratorUrl}/api/v1/state`);
|
|
1676
|
-
if (!response.ok)
|
|
1677
|
-
return "unknown";
|
|
1470
|
+
if (!response.ok) return "unknown";
|
|
1678
1471
|
const status = await response.json();
|
|
1679
|
-
const isActive = status.activeRuns?.some(
|
|
1472
|
+
const isActive = status.activeRuns?.some(
|
|
1473
|
+
(run) => run.issueIdentifier === issueIdentifier
|
|
1474
|
+
);
|
|
1680
1475
|
return isActive ? "active" : "non-actionable";
|
|
1681
1476
|
} catch {
|
|
1682
1477
|
return "unknown";
|
|
1683
1478
|
}
|
|
1684
1479
|
}
|
|
1685
1480
|
function runToolProcess(toolDef, inputJson) {
|
|
1686
|
-
return new Promise((
|
|
1481
|
+
return new Promise((resolve, reject) => {
|
|
1687
1482
|
const toolEnv = {
|
|
1688
1483
|
...process.env,
|
|
1689
1484
|
...toolDef.env
|
|
1690
1485
|
};
|
|
1691
|
-
const toolProc =
|
|
1486
|
+
const toolProc = spawn(toolDef.command, toolDef.args, {
|
|
1692
1487
|
env: toolEnv,
|
|
1693
1488
|
stdio: "pipe"
|
|
1694
1489
|
});
|
|
@@ -1700,10 +1495,14 @@ function runToolProcess(toolDef, inputJson) {
|
|
|
1700
1495
|
toolProc.once("exit", (code) => {
|
|
1701
1496
|
const output = Buffer.concat(stdout).toString("utf8").trim();
|
|
1702
1497
|
if (code === 0) {
|
|
1703
|
-
|
|
1498
|
+
resolve(output || "{}");
|
|
1704
1499
|
} else {
|
|
1705
1500
|
const errOutput = Buffer.concat(stderr).toString("utf8").trim();
|
|
1706
|
-
reject(
|
|
1501
|
+
reject(
|
|
1502
|
+
new Error(
|
|
1503
|
+
`Tool exited with code ${code ?? "unknown"}: ${errOutput || output}`
|
|
1504
|
+
)
|
|
1505
|
+
);
|
|
1707
1506
|
}
|
|
1708
1507
|
});
|
|
1709
1508
|
toolProc.stdin?.write(inputJson);
|