@gh-symphony/cli 0.0.19 → 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-GKENCODJ.js → chunk-KY6WKH66.js} +437 -101
- package/dist/{chunk-6CI3UUMH.js → chunk-MYVJ6HK4.js} +950 -1240
- package/dist/{chunk-M3IFVLQS.js → chunk-QEONJ5DZ.js} +978 -72
- package/dist/{chunk-H2YXSYOZ.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-DNALEWO3.js → project-25NQ4J4Y.js} +8 -6
- package/dist/{recover-C3V2QAUB.js → recover-L3MJHHDA.js} +4 -2
- package/dist/{repo-HDDE7OUI.js → repo-TDCWQR6P.js} +72 -14
- package/dist/{run-XI2S5Y4V.js → run-XJQ6BF7U.js} +4 -2
- package/dist/{setup-K4CYYJBF.js → setup-B2SVLW2R.js} +46 -8
- package/dist/{start-M6IQGRFO.js → start-I2CC7BLW.js} +6 -4
- package/dist/{upgrade-F4VE4XBS.js → upgrade-OJXPZRYE.js} +2 -2
- package/dist/{version-Y5RYNWMF.js → version-TBDCTKDO.js} +1 -1
- package/dist/worker-entry.js +522 -867
- package/dist/{workflow-TBIFY5MO.js → workflow-BLJH2HC3.js} +176 -10
- package/package.json +5 -3
package/dist/worker-entry.js
CHANGED
|
@@ -1,450 +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
|
-
resumeThreadId: config.resumeThreadId?.trim() || null
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
function launchCodexAppServer(plan, spawnImpl = spawn) {
|
|
100
|
-
return spawnImpl(plan.command, plan.args, {
|
|
101
|
-
cwd: plan.cwd,
|
|
102
|
-
env: plan.env,
|
|
103
|
-
stdio: "pipe"
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
async function prepareCodexRuntimePlan(config, dependencies = {}) {
|
|
107
|
-
const agentEnv = await resolveAgentRuntimeEnvironment(config, dependencies);
|
|
108
|
-
const codexHomeDir = join(config.workingDirectory, ".codex-agent");
|
|
109
|
-
const mkdirImpl = dependencies.mkdirImpl ?? mkdir;
|
|
110
|
-
await mkdirImpl(codexHomeDir, { recursive: true });
|
|
111
|
-
const writeFileImpl = dependencies.writeFileImpl ?? writeFile;
|
|
112
|
-
await writeFileImpl(join(codexHomeDir, "config.toml"), "# Isolated agent config \u2014 no personal MCP servers\n", "utf8");
|
|
113
|
-
const realCodexHome = process.env.CODEX_HOME ?? join(homedir(), ".codex");
|
|
114
|
-
const copyFileImpl = dependencies.copyFileImpl ?? copyFile;
|
|
115
|
-
try {
|
|
116
|
-
await copyFileImpl(join(realCodexHome, "auth.json"), join(codexHomeDir, "auth.json"));
|
|
117
|
-
} catch {
|
|
118
|
-
}
|
|
119
|
-
return buildCodexRuntimePlan({
|
|
120
|
-
...config,
|
|
121
|
-
agentEnv
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
function createGitCredentialHelperEnvironment(config) {
|
|
125
|
-
return {
|
|
126
|
-
GITHUB_GIT_HOST: DEFAULT_GITHUB_GIT_HOST,
|
|
127
|
-
GITHUB_GIT_USERNAME: DEFAULT_GITHUB_GIT_USERNAME,
|
|
128
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
129
|
-
GIT_CONFIG_COUNT: "1",
|
|
130
|
-
GIT_CONFIG_KEY_0: "credential.helper",
|
|
131
|
-
GIT_CONFIG_VALUE_0: `!node ${fileURLToPath(new URL("./git-credential-helper.js", import.meta.url))}`,
|
|
132
|
-
...config.githubToken ? {
|
|
133
|
-
GITHUB_GRAPHQL_TOKEN: config.githubToken
|
|
134
|
-
} : {},
|
|
135
|
-
...config.githubTokenBrokerUrl ? {
|
|
136
|
-
GITHUB_TOKEN_BROKER_URL: config.githubTokenBrokerUrl
|
|
137
|
-
} : {},
|
|
138
|
-
...config.githubTokenBrokerSecret ? {
|
|
139
|
-
GITHUB_TOKEN_BROKER_SECRET: config.githubTokenBrokerSecret
|
|
140
|
-
} : {},
|
|
141
|
-
...config.githubTokenCachePath ? {
|
|
142
|
-
GITHUB_TOKEN_CACHE_PATH: config.githubTokenCachePath
|
|
143
|
-
} : {}
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
async function resolveAgentRuntimeEnvironment(config, dependencies = {}) {
|
|
147
|
-
if (config.agentEnv) {
|
|
148
|
-
return config.agentEnv;
|
|
149
|
-
}
|
|
150
|
-
if (!config.agentCredentialBrokerUrl || !config.agentCredentialBrokerSecret) {
|
|
151
|
-
return {};
|
|
152
|
-
}
|
|
153
|
-
const fetchImpl = dependencies.fetchImpl ?? fetch;
|
|
154
|
-
const response = await fetchImpl(config.agentCredentialBrokerUrl, {
|
|
155
|
-
method: "POST",
|
|
156
|
-
headers: {
|
|
157
|
-
accept: "application/json",
|
|
158
|
-
authorization: `Bearer ${config.agentCredentialBrokerSecret}`
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
const payload = await response.json();
|
|
162
|
-
if (!response.ok || !payload.env || Object.keys(payload.env).length === 0) {
|
|
163
|
-
throw new AgentRuntimeResolutionError(payload.error ?? `Agent credential broker request failed with status ${response.status}.`);
|
|
164
|
-
}
|
|
165
|
-
if (config.agentCredentialCachePath) {
|
|
166
|
-
const writeFileImpl = dependencies.writeFileImpl ?? writeFile;
|
|
167
|
-
await writeFileImpl(config.agentCredentialCachePath, JSON.stringify(payload), "utf8");
|
|
168
|
-
}
|
|
169
|
-
return payload.env;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// ../runtime-codex/dist/launcher.js
|
|
173
|
-
import { dirname, resolve } from "path";
|
|
174
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
175
|
-
var LocalRuntimeLauncherError = class extends Error {
|
|
176
|
-
};
|
|
177
|
-
function resolveLocalRuntimeLaunchConfig(env = process.env) {
|
|
178
|
-
const projectId = env.PROJECT_ID ?? env.CODEX_PROJECT_ID;
|
|
179
|
-
const workingDirectory = env.WORKING_DIRECTORY;
|
|
180
|
-
if (!projectId) {
|
|
181
|
-
throw new LocalRuntimeLauncherError("PROJECT_ID or CODEX_PROJECT_ID is required.");
|
|
182
|
-
}
|
|
183
|
-
if (!workingDirectory) {
|
|
184
|
-
throw new LocalRuntimeLauncherError("WORKING_DIRECTORY is required.");
|
|
185
|
-
}
|
|
186
|
-
return {
|
|
187
|
-
projectId,
|
|
188
|
-
workingDirectory,
|
|
189
|
-
githubToken: env.GITHUB_GRAPHQL_TOKEN,
|
|
190
|
-
githubTokenBrokerUrl: env.GITHUB_TOKEN_BROKER_URL,
|
|
191
|
-
githubTokenBrokerSecret: env.GITHUB_TOKEN_BROKER_SECRET,
|
|
192
|
-
githubTokenCachePath: env.GITHUB_TOKEN_CACHE_PATH,
|
|
193
|
-
agentEnv: readDirectAgentEnvironment(env),
|
|
194
|
-
agentCredentialBrokerUrl: env.AGENT_CREDENTIAL_BROKER_URL,
|
|
195
|
-
agentCredentialBrokerSecret: env.AGENT_CREDENTIAL_BROKER_SECRET,
|
|
196
|
-
agentCredentialCachePath: env.AGENT_CREDENTIAL_CACHE_PATH,
|
|
197
|
-
githubProjectId: env.GITHUB_PROJECT_ID,
|
|
198
|
-
githubGraphqlApiUrl: env.GITHUB_GRAPHQL_API_URL,
|
|
199
|
-
agentCommand: env.SYMPHONY_AGENT_COMMAND,
|
|
200
|
-
resumeThreadId: env.SYMPHONY_RESUME_THREAD_ID
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
async function runLocalRuntimeLauncher(env = process.env) {
|
|
204
|
-
const launcherEnv2 = loadLauncherEnvironment(env);
|
|
205
|
-
const config = resolveLocalRuntimeLaunchConfig(launcherEnv2);
|
|
206
|
-
const plan = await prepareCodexRuntimePlan(config);
|
|
207
|
-
emitLaunchSummary(config);
|
|
208
|
-
const child = launchCodexAppServer(plan);
|
|
209
|
-
process.stdout.write(`[worker] codex app-server started (pid: ${child.pid ?? "unknown"})
|
|
210
|
-
`);
|
|
211
|
-
child.stdout?.pipe(process.stdout);
|
|
212
|
-
child.stderr?.pipe(process.stderr);
|
|
213
|
-
return await waitForChildProcess(child);
|
|
214
|
-
}
|
|
215
|
-
function loadLauncherEnvironment(env = process.env, cwd = process.cwd()) {
|
|
216
|
-
const mergedEnv = {
|
|
217
|
-
...readEnvFile(resolve(dirname(fileURLToPath2(import.meta.url)), "..", ".env")),
|
|
218
|
-
...readEnvFile(resolve(cwd, ".env")),
|
|
219
|
-
...env
|
|
220
|
-
};
|
|
221
|
-
return mergedEnv;
|
|
222
|
-
}
|
|
223
|
-
function readDirectAgentEnvironment(env) {
|
|
224
|
-
const agentEnv = {};
|
|
225
|
-
for (const key of [
|
|
226
|
-
"OPENAI_API_KEY",
|
|
227
|
-
"OPENAI_BASE_URL",
|
|
228
|
-
"OPENAI_ORG_ID",
|
|
229
|
-
"OPENAI_PROJECT"
|
|
230
|
-
]) {
|
|
231
|
-
const value = env[key];
|
|
232
|
-
if (value) {
|
|
233
|
-
agentEnv[key] = value;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
return Object.keys(agentEnv).length ? agentEnv : void 0;
|
|
237
|
-
}
|
|
238
|
-
function waitForChildProcess(child) {
|
|
239
|
-
return new Promise((resolve2, reject) => {
|
|
240
|
-
child.once("error", reject);
|
|
241
|
-
child.once("exit", (code, signal) => {
|
|
242
|
-
if (signal) {
|
|
243
|
-
reject(new LocalRuntimeLauncherError(`codex app-server exited on ${signal}.`));
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
resolve2(code ?? 0);
|
|
247
|
-
});
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
async function main() {
|
|
251
|
-
const exitCode = await runLocalRuntimeLauncher(process.env);
|
|
252
|
-
process.exitCode = exitCode;
|
|
253
|
-
}
|
|
254
|
-
if (import.meta.url === new URL(process.argv[1] ?? "", "file:").href) {
|
|
255
|
-
main().catch((error) => {
|
|
256
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
257
|
-
process.stderr.write(`${message}
|
|
258
|
-
`);
|
|
259
|
-
process.exitCode = 1;
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
function emitLaunchSummary(config) {
|
|
263
|
-
const githubAuthMode = config.githubToken ? "direct token" : config.githubTokenBrokerUrl && config.githubTokenBrokerSecret ? "broker" : "missing";
|
|
264
|
-
const agentAuthMode = config.agentEnv?.OPENAI_API_KEY ? "direct env" : config.agentCredentialBrokerUrl && config.agentCredentialBrokerSecret ? "broker" : "local codex auth or inherited environment";
|
|
265
|
-
process.stdout.write([
|
|
266
|
-
"[worker] starting local codex runtime",
|
|
267
|
-
`[worker] project: ${config.projectId}`,
|
|
268
|
-
`[worker] cwd: ${config.workingDirectory}`,
|
|
269
|
-
`[worker] github project: ${config.githubProjectId ?? "(unset)"}`,
|
|
270
|
-
`[worker] github auth: ${githubAuthMode}`,
|
|
271
|
-
`[worker] agent auth: ${agentAuthMode}`,
|
|
272
|
-
"[worker] note: codex app-server does not proactively read GitHub issues.",
|
|
273
|
-
"[worker] note: it waits for a client request or tool invocation."
|
|
274
|
-
].join("\n") + "\n");
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// ../runtime-codex/dist/github-graphql-tool.js
|
|
278
|
-
import { readFile, writeFile as writeFile2 } from "fs/promises";
|
|
279
|
-
var DEFAULT_GITHUB_GRAPHQL_API_URL2 = "https://api.github.com/graphql";
|
|
280
|
-
var TOKEN_REUSE_WINDOW_MS = 60 * 1e3;
|
|
281
|
-
async function executeGitHubGraphQL(invocation, config, fetchImpl = fetch) {
|
|
282
|
-
const token = await resolveGitHubGraphQLToken(config, {
|
|
283
|
-
fetchImpl
|
|
284
|
-
});
|
|
285
|
-
const response = await fetchImpl(config.apiUrl ?? DEFAULT_GITHUB_GRAPHQL_API_URL2, {
|
|
286
|
-
method: "POST",
|
|
287
|
-
headers: {
|
|
288
|
-
"content-type": "application/json",
|
|
289
|
-
authorization: `Bearer ${token}`
|
|
290
|
-
},
|
|
291
|
-
body: JSON.stringify(invocation)
|
|
292
|
-
});
|
|
293
|
-
const payload = await response.json();
|
|
294
|
-
if (!response.ok) {
|
|
295
|
-
throw new Error(`GitHub GraphQL request failed with status ${response.status}: ${JSON.stringify(payload)}`);
|
|
296
|
-
}
|
|
297
|
-
if (payload.errors?.length) {
|
|
298
|
-
throw new Error(payload.errors.map((error) => error.message).join("; "));
|
|
299
|
-
}
|
|
300
|
-
return payload;
|
|
301
|
-
}
|
|
302
|
-
async function resolveGitHubGraphQLToken(config, dependencies = {}) {
|
|
303
|
-
if (config.token) {
|
|
304
|
-
return config.token;
|
|
305
|
-
}
|
|
306
|
-
if (!config.tokenBrokerUrl || !config.tokenBrokerSecret) {
|
|
307
|
-
throw new Error("Either GITHUB_GRAPHQL_TOKEN or the runtime token broker configuration is required.");
|
|
308
|
-
}
|
|
309
|
-
const now = dependencies.now ?? /* @__PURE__ */ new Date();
|
|
310
|
-
const readFileImpl = dependencies.readFileImpl ?? readFile;
|
|
311
|
-
const writeFileImpl = dependencies.writeFileImpl ?? writeFile2;
|
|
312
|
-
const cachedToken = config.tokenCachePath ? await readCachedToken(config.tokenCachePath, readFileImpl) : null;
|
|
313
|
-
if (cachedToken && cachedToken.expiresAt.getTime() - now.getTime() > TOKEN_REUSE_WINDOW_MS) {
|
|
314
|
-
return cachedToken.token;
|
|
315
|
-
}
|
|
316
|
-
const fetchImpl = dependencies.fetchImpl ?? fetch;
|
|
317
|
-
const response = await fetchImpl(config.tokenBrokerUrl, {
|
|
318
|
-
method: "POST",
|
|
319
|
-
headers: {
|
|
320
|
-
accept: "application/json",
|
|
321
|
-
authorization: `Bearer ${config.tokenBrokerSecret}`
|
|
322
|
-
}
|
|
323
|
-
});
|
|
324
|
-
const payload = await response.json();
|
|
325
|
-
if (!response.ok || !payload.token || !payload.expiresAt) {
|
|
326
|
-
throw new Error(payload.error ?? `Runtime token broker request failed with status ${response.status}.`);
|
|
327
|
-
}
|
|
328
|
-
if (config.tokenCachePath) {
|
|
329
|
-
await writeFileImpl(config.tokenCachePath, JSON.stringify(payload), "utf8");
|
|
330
|
-
}
|
|
331
|
-
return payload.token;
|
|
332
|
-
}
|
|
333
|
-
async function readStdin() {
|
|
334
|
-
const chunks = [];
|
|
335
|
-
for await (const chunk of process.stdin) {
|
|
336
|
-
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
337
|
-
}
|
|
338
|
-
return Buffer.concat(chunks).toString("utf8");
|
|
339
|
-
}
|
|
340
|
-
async function main2() {
|
|
341
|
-
const rawInput = await readStdin();
|
|
342
|
-
const invocation = JSON.parse(rawInput);
|
|
343
|
-
const result = await executeGitHubGraphQL(invocation, {
|
|
344
|
-
token: process.env.GITHUB_GRAPHQL_TOKEN,
|
|
345
|
-
apiUrl: process.env.GITHUB_GRAPHQL_API_URL,
|
|
346
|
-
tokenBrokerUrl: process.env.GITHUB_TOKEN_BROKER_URL,
|
|
347
|
-
tokenBrokerSecret: process.env.GITHUB_TOKEN_BROKER_SECRET,
|
|
348
|
-
tokenCachePath: process.env.GITHUB_TOKEN_CACHE_PATH
|
|
349
|
-
});
|
|
350
|
-
process.stdout.write(`${JSON.stringify(result)}
|
|
351
|
-
`);
|
|
352
|
-
}
|
|
353
|
-
if (import.meta.url === new URL(process.argv[1] ?? "", "file:").href) {
|
|
354
|
-
main2().catch((error) => {
|
|
355
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
356
|
-
process.stderr.write(`${message}
|
|
357
|
-
`);
|
|
358
|
-
process.exitCode = 1;
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
async function readCachedToken(path, readFileImpl) {
|
|
362
|
-
try {
|
|
363
|
-
const payload = JSON.parse(await readFileImpl(path, "utf8"));
|
|
364
|
-
if (!payload.token || !payload.expiresAt) {
|
|
365
|
-
return null;
|
|
366
|
-
}
|
|
367
|
-
return {
|
|
368
|
-
token: payload.token,
|
|
369
|
-
expiresAt: new Date(payload.expiresAt)
|
|
370
|
-
};
|
|
371
|
-
} catch {
|
|
372
|
-
return null;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// ../runtime-codex/dist/git-credential-helper.js
|
|
377
|
-
var DEFAULT_GITHUB_GIT_HOST2 = "github.com";
|
|
378
|
-
var DEFAULT_GITHUB_GIT_USERNAME2 = "x-access-token";
|
|
379
|
-
async function resolveGitCredential(request, config, fetchImpl = fetch) {
|
|
380
|
-
const requestHost = request.host?.trim();
|
|
381
|
-
const requestProtocol = request.protocol?.trim();
|
|
382
|
-
if (!requestHost || requestProtocol && requestProtocol !== "https") {
|
|
383
|
-
return "";
|
|
384
|
-
}
|
|
385
|
-
const expectedHost = normalizeGitHost(config.gitHost ?? DEFAULT_GITHUB_GIT_HOST2);
|
|
386
|
-
if (normalizeGitHost(requestHost) !== expectedHost) {
|
|
387
|
-
return "";
|
|
388
|
-
}
|
|
389
|
-
const token = await resolveGitHubGraphQLToken(config, {
|
|
390
|
-
fetchImpl
|
|
391
|
-
});
|
|
392
|
-
return formatGitCredentialResponse({
|
|
393
|
-
protocol: requestProtocol || "https",
|
|
394
|
-
host: requestHost,
|
|
395
|
-
username: config.gitUsername ?? DEFAULT_GITHUB_GIT_USERNAME2,
|
|
396
|
-
password: token
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
function parseGitCredentialRequest(rawInput) {
|
|
400
|
-
return rawInput.split("\n").map((line) => line.trim()).filter(Boolean).reduce((request, line) => {
|
|
401
|
-
const separatorIndex = line.indexOf("=");
|
|
402
|
-
if (separatorIndex === -1) {
|
|
403
|
-
return request;
|
|
404
|
-
}
|
|
405
|
-
const key = line.slice(0, separatorIndex);
|
|
406
|
-
const value = line.slice(separatorIndex + 1);
|
|
407
|
-
request[key] = value;
|
|
408
|
-
return request;
|
|
409
|
-
}, {});
|
|
410
|
-
}
|
|
411
|
-
function formatGitCredentialResponse(value) {
|
|
412
|
-
return `${Object.entries(value).map(([key, entry]) => `${key}=${entry}`).join("\n")}
|
|
413
|
-
|
|
414
|
-
`;
|
|
415
|
-
}
|
|
416
|
-
async function readStdin2() {
|
|
417
|
-
const chunks = [];
|
|
418
|
-
for await (const chunk of process.stdin) {
|
|
419
|
-
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
420
|
-
}
|
|
421
|
-
return Buffer.concat(chunks).toString("utf8");
|
|
422
|
-
}
|
|
423
|
-
async function main3() {
|
|
424
|
-
const request = parseGitCredentialRequest(await readStdin2());
|
|
425
|
-
const response = await resolveGitCredential(request, {
|
|
426
|
-
token: process.env.GITHUB_GRAPHQL_TOKEN,
|
|
427
|
-
tokenBrokerUrl: process.env.GITHUB_TOKEN_BROKER_URL,
|
|
428
|
-
tokenBrokerSecret: process.env.GITHUB_TOKEN_BROKER_SECRET,
|
|
429
|
-
tokenCachePath: process.env.GITHUB_TOKEN_CACHE_PATH,
|
|
430
|
-
gitHost: process.env.GITHUB_GIT_HOST,
|
|
431
|
-
gitUsername: process.env.GITHUB_GIT_USERNAME
|
|
432
|
-
});
|
|
433
|
-
process.stdout.write(response);
|
|
434
|
-
}
|
|
435
|
-
if (import.meta.url === new URL(process.argv[1] ?? "", "file:").href) {
|
|
436
|
-
main3().catch((error) => {
|
|
437
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
438
|
-
process.stderr.write(`${message}
|
|
439
|
-
`);
|
|
440
|
-
process.exitCode = 1;
|
|
441
|
-
});
|
|
442
|
-
}
|
|
443
|
-
function normalizeGitHost(host) {
|
|
444
|
-
return host.trim().toLowerCase();
|
|
445
|
-
}
|
|
23
|
+
import { readFile } from "fs/promises";
|
|
24
|
+
import { join as join2 } from "path";
|
|
446
25
|
|
|
447
|
-
// ../worker/
|
|
26
|
+
// ../worker/src/execution-phase.ts
|
|
448
27
|
function resolveInitialExecutionPhase(input) {
|
|
449
28
|
const { issueState, blockerCheckStates, activeStates } = input;
|
|
450
29
|
if (!issueState) {
|
|
@@ -474,7 +53,7 @@ function resolveFinalExecutionPhase(input) {
|
|
|
474
53
|
return resolvePausedExecutionPhase(input.currentPhase) ?? input.currentPhase;
|
|
475
54
|
}
|
|
476
55
|
|
|
477
|
-
// ../worker/
|
|
56
|
+
// ../worker/src/codex-policy.ts
|
|
478
57
|
function resolveCodexPolicySettings(env) {
|
|
479
58
|
return {
|
|
480
59
|
approvalPolicy: env.SYMPHONY_APPROVAL_POLICY || "never",
|
|
@@ -483,7 +62,7 @@ function resolveCodexPolicySettings(env) {
|
|
|
483
62
|
};
|
|
484
63
|
}
|
|
485
64
|
|
|
486
|
-
// ../worker/
|
|
65
|
+
// ../worker/src/convergence-detection.ts
|
|
487
66
|
import { spawnSync } from "child_process";
|
|
488
67
|
var DEFAULT_MAX_NONPRODUCTIVE_TURNS = 3;
|
|
489
68
|
function resolveMaxNonProductiveTurns(env) {
|
|
@@ -492,10 +71,14 @@ function resolveMaxNonProductiveTurns(env) {
|
|
|
492
71
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_MAX_NONPRODUCTIVE_TURNS;
|
|
493
72
|
}
|
|
494
73
|
function captureTurnWorkspaceSnapshot(cwd) {
|
|
495
|
-
const result = spawnSync(
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
74
|
+
const result = spawnSync(
|
|
75
|
+
"git",
|
|
76
|
+
["status", "--porcelain=v1", "--untracked-files=all"],
|
|
77
|
+
{
|
|
78
|
+
cwd,
|
|
79
|
+
encoding: "utf8"
|
|
80
|
+
}
|
|
81
|
+
);
|
|
499
82
|
if (result.status !== 0) {
|
|
500
83
|
return {
|
|
501
84
|
fingerprint: null,
|
|
@@ -541,7 +124,7 @@ function normalizeError(value) {
|
|
|
541
124
|
return normalized.length > 0 ? normalized : null;
|
|
542
125
|
}
|
|
543
126
|
|
|
544
|
-
// ../worker/
|
|
127
|
+
// ../worker/src/run-phase.ts
|
|
545
128
|
var TERMINAL_RUN_PHASES = /* @__PURE__ */ new Set([
|
|
546
129
|
"succeeded",
|
|
547
130
|
"failed",
|
|
@@ -556,111 +139,26 @@ function resolveExitRunPhase(currentRunPhase, exit) {
|
|
|
556
139
|
return exit.code === 0 && !exit.signal ? "succeeded" : "failed";
|
|
557
140
|
}
|
|
558
141
|
|
|
559
|
-
// ../worker/
|
|
560
|
-
function resolveSessionBudgetState(env) {
|
|
561
|
-
return {
|
|
562
|
-
cumulativeTurnCount: parseNonNegativeInteger(env.SYMPHONY_CUMULATIVE_TURN_COUNT),
|
|
563
|
-
tokenUsageBaseline: {
|
|
564
|
-
inputTokens: parseNonNegativeInteger(env.SYMPHONY_CUMULATIVE_INPUT_TOKENS),
|
|
565
|
-
outputTokens: parseNonNegativeInteger(env.SYMPHONY_CUMULATIVE_OUTPUT_TOKENS),
|
|
566
|
-
totalTokens: parseNonNegativeInteger(env.SYMPHONY_CUMULATIVE_TOTAL_TOKENS)
|
|
567
|
-
},
|
|
568
|
-
sessionStartedAt: normalizeTimestamp(env.SYMPHONY_SESSION_STARTED_AT),
|
|
569
|
-
globalMaxTurns: parsePositiveInteger(env.SYMPHONY_GLOBAL_MAX_TURNS),
|
|
570
|
-
maxTokens: parsePositiveInteger(env.SYMPHONY_MAX_TOKENS),
|
|
571
|
-
sessionTimeoutMs: parsePositiveInteger(env.SYMPHONY_SESSION_TIMEOUT_MS)
|
|
572
|
-
};
|
|
573
|
-
}
|
|
574
|
-
function resolveBudgetExceededReason(budget, currentSessionTurnCount, currentTokenUsage, now) {
|
|
575
|
-
const totalTurns = budget.cumulativeTurnCount + currentSessionTurnCount;
|
|
576
|
-
if (budget.globalMaxTurns !== null && totalTurns >= budget.globalMaxTurns) {
|
|
577
|
-
return "global-turns";
|
|
578
|
-
}
|
|
579
|
-
const totalTokens = budget.tokenUsageBaseline.totalTokens + currentTokenUsage.totalTokens;
|
|
580
|
-
if (budget.maxTokens !== null && totalTokens >= budget.maxTokens) {
|
|
581
|
-
return "tokens";
|
|
582
|
-
}
|
|
583
|
-
if (budget.sessionTimeoutMs !== null && budget.sessionStartedAt !== null && now.getTime() - new Date(budget.sessionStartedAt).getTime() >= budget.sessionTimeoutMs) {
|
|
584
|
-
return "session-timeout";
|
|
585
|
-
}
|
|
586
|
-
return null;
|
|
587
|
-
}
|
|
588
|
-
function parsePositiveInteger(value) {
|
|
589
|
-
if (!value) {
|
|
590
|
-
return null;
|
|
591
|
-
}
|
|
592
|
-
const parsed = Number(value);
|
|
593
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
594
|
-
return null;
|
|
595
|
-
}
|
|
596
|
-
return Math.floor(parsed);
|
|
597
|
-
}
|
|
598
|
-
function parseNonNegativeInteger(value) {
|
|
599
|
-
if (!value) {
|
|
600
|
-
return 0;
|
|
601
|
-
}
|
|
602
|
-
const parsed = Number(value);
|
|
603
|
-
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
604
|
-
return 0;
|
|
605
|
-
}
|
|
606
|
-
return Math.floor(parsed);
|
|
607
|
-
}
|
|
608
|
-
function normalizeTimestamp(value) {
|
|
609
|
-
if (!value) {
|
|
610
|
-
return null;
|
|
611
|
-
}
|
|
612
|
-
const parsed = Date.parse(value);
|
|
613
|
-
return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// ../worker/dist/thread-resume.js
|
|
142
|
+
// ../worker/src/thread-resume.ts
|
|
617
143
|
var DEFAULT_CONTINUATION_GUIDANCE = "Continue working on the issue. Review your progress and complete any remaining tasks.";
|
|
618
|
-
function
|
|
144
|
+
function parseNonNegativeInteger(value) {
|
|
619
145
|
const parsed = typeof value === "number" ? value : Number.parseInt(value ?? "", 10);
|
|
620
146
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
621
147
|
return 0;
|
|
622
148
|
}
|
|
623
149
|
return Math.floor(parsed);
|
|
624
150
|
}
|
|
625
|
-
function
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
return renderedPrompt;
|
|
631
|
-
}
|
|
632
|
-
const renderedContinuationGuidance = buildContinuationTurnInput({
|
|
633
|
-
continuationGuidance,
|
|
634
|
-
lastTurnSummary,
|
|
635
|
-
cumulativeTurnCount
|
|
636
|
-
});
|
|
637
|
-
const normalizedSummary = normalizeContinuationVariable(lastTurnSummary) ?? "No previous turn summary was captured.";
|
|
638
|
-
const normalizedCumulativeTurnCount = Math.max(0, parseNonNegativeInteger2(cumulativeTurnCount));
|
|
639
|
-
if (mode === "resume") {
|
|
640
|
-
return [
|
|
641
|
-
"Resume work on this issue using the existing thread context.",
|
|
642
|
-
`Previous worker turns completed: ${normalizedCumulativeTurnCount}.`,
|
|
643
|
-
`Previous session summary: ${normalizedSummary}`,
|
|
644
|
-
renderedContinuationGuidance
|
|
645
|
-
].join("\n");
|
|
646
|
-
}
|
|
647
|
-
return [
|
|
648
|
-
"Resume work on this issue from a previous worker session.",
|
|
649
|
-
"",
|
|
650
|
-
"Original issue instructions:",
|
|
651
|
-
renderedPrompt,
|
|
652
|
-
"",
|
|
653
|
-
"Previous session summary:",
|
|
654
|
-
normalizedSummary,
|
|
655
|
-
"",
|
|
656
|
-
renderedContinuationGuidance
|
|
657
|
-
].join("\n");
|
|
658
|
-
}
|
|
659
|
-
function buildContinuationTurnInput({ continuationGuidance, lastTurnSummary, cumulativeTurnCount = 0 }) {
|
|
151
|
+
function buildContinuationTurnInput({
|
|
152
|
+
continuationGuidance,
|
|
153
|
+
lastTurnSummary,
|
|
154
|
+
cumulativeTurnCount = 0
|
|
155
|
+
}) {
|
|
660
156
|
const template = continuationGuidance?.trim() || DEFAULT_CONTINUATION_GUIDANCE;
|
|
661
157
|
return renderContinuationGuidance(template, {
|
|
662
158
|
lastTurnSummary: normalizeContinuationVariable(lastTurnSummary) ?? "No previous turn summary was captured.",
|
|
663
|
-
cumulativeTurnCount: String(
|
|
159
|
+
cumulativeTurnCount: String(
|
|
160
|
+
Math.max(0, parseNonNegativeInteger(cumulativeTurnCount))
|
|
161
|
+
)
|
|
664
162
|
});
|
|
665
163
|
}
|
|
666
164
|
function normalizeContinuationVariable(value) {
|
|
@@ -669,7 +167,9 @@ function normalizeContinuationVariable(value) {
|
|
|
669
167
|
}
|
|
670
168
|
function renderContinuationGuidance(template, variables) {
|
|
671
169
|
if (template.includes("{%") || template.includes("%}")) {
|
|
672
|
-
throw new Error(
|
|
170
|
+
throw new Error(
|
|
171
|
+
"template_parse_error: continuation guidance does not support Liquid tags."
|
|
172
|
+
);
|
|
673
173
|
}
|
|
674
174
|
let rendered = "";
|
|
675
175
|
let lastIndex = 0;
|
|
@@ -680,7 +180,9 @@ function renderContinuationGuidance(template, variables) {
|
|
|
680
180
|
const index = match.index ?? 0;
|
|
681
181
|
rendered += template.slice(lastIndex, index);
|
|
682
182
|
if (!(expression in variables)) {
|
|
683
|
-
throw new Error(
|
|
183
|
+
throw new Error(
|
|
184
|
+
`template_render_error: unsupported continuation guidance variable '${expression}'.`
|
|
185
|
+
);
|
|
684
186
|
}
|
|
685
187
|
rendered += variables[expression] ?? "";
|
|
686
188
|
lastIndex = index + matchedText.length;
|
|
@@ -688,26 +190,57 @@ function renderContinuationGuidance(template, variables) {
|
|
|
688
190
|
rendered += template.slice(lastIndex);
|
|
689
191
|
const strayLiquidExpression = rendered.match(/\{\{[^}]*\}\}/);
|
|
690
192
|
if (strayLiquidExpression) {
|
|
691
|
-
throw new Error(
|
|
193
|
+
throw new Error(
|
|
194
|
+
`template_parse_error: invalid continuation guidance expression '${strayLiquidExpression[0]}'.`
|
|
195
|
+
);
|
|
692
196
|
}
|
|
693
197
|
return rendered;
|
|
694
198
|
}
|
|
695
199
|
|
|
696
|
-
// ../worker/
|
|
697
|
-
|
|
698
|
-
|
|
200
|
+
// ../worker/src/turn-limits.ts
|
|
201
|
+
var DEFAULT_SESSION_MAX_TURNS = 20;
|
|
202
|
+
function resolveMaxTurns(value) {
|
|
203
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
204
|
+
if (!Number.isFinite(parsed)) {
|
|
205
|
+
return {
|
|
206
|
+
maxTurns: DEFAULT_SESSION_MAX_TURNS,
|
|
207
|
+
exhaustedBeforeStart: false
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
const normalized = Math.trunc(parsed);
|
|
211
|
+
if (normalized <= 0) {
|
|
212
|
+
return {
|
|
213
|
+
maxTurns: 0,
|
|
214
|
+
exhaustedBeforeStart: true
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
maxTurns: normalized,
|
|
219
|
+
exhaustedBeforeStart: false
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ../worker/src/token-usage.ts
|
|
224
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
225
|
+
import { dirname, join } from "path";
|
|
699
226
|
async function persistTokenUsageArtifact(env, tokenUsage) {
|
|
700
227
|
const artifactPath = resolveTokenUsageArtifactPath(env);
|
|
701
228
|
if (!artifactPath) {
|
|
702
229
|
return;
|
|
703
230
|
}
|
|
704
231
|
try {
|
|
705
|
-
await
|
|
706
|
-
await
|
|
232
|
+
await mkdir(dirname(artifactPath), { recursive: true });
|
|
233
|
+
await writeFile(
|
|
234
|
+
artifactPath,
|
|
235
|
+
JSON.stringify(tokenUsage, null, 2) + "\n",
|
|
236
|
+
"utf8"
|
|
237
|
+
);
|
|
707
238
|
} catch (error) {
|
|
708
239
|
const message = error instanceof Error ? error.message : String(error);
|
|
709
|
-
process.stderr.write(
|
|
710
|
-
`
|
|
240
|
+
process.stderr.write(
|
|
241
|
+
`[worker] failed to persist token usage artifact: ${message}
|
|
242
|
+
`
|
|
243
|
+
);
|
|
711
244
|
}
|
|
712
245
|
}
|
|
713
246
|
function resolveTokenUsageArtifactPath(env) {
|
|
@@ -715,12 +248,11 @@ function resolveTokenUsageArtifactPath(env) {
|
|
|
715
248
|
if (!workspaceRuntimeDir) {
|
|
716
249
|
return null;
|
|
717
250
|
}
|
|
718
|
-
return
|
|
251
|
+
return join(workspaceRuntimeDir, "token-usage.json");
|
|
719
252
|
}
|
|
720
253
|
|
|
721
|
-
// ../worker/
|
|
254
|
+
// ../worker/src/index.ts
|
|
722
255
|
var launcherEnv = loadLauncherEnvironment(process.env);
|
|
723
|
-
var sessionBudgetState = resolveSessionBudgetState(launcherEnv);
|
|
724
256
|
var runtimeState = {
|
|
725
257
|
status: launcherEnv.SYMPHONY_RUN_ID ? "starting" : "idle",
|
|
726
258
|
executionPhase: null,
|
|
@@ -741,9 +273,9 @@ var runtimeState = {
|
|
|
741
273
|
lastError: null
|
|
742
274
|
} : null,
|
|
743
275
|
tokenUsage: {
|
|
744
|
-
inputTokens:
|
|
745
|
-
outputTokens:
|
|
746
|
-
totalTokens:
|
|
276
|
+
inputTokens: 0,
|
|
277
|
+
outputTokens: 0,
|
|
278
|
+
totalTokens: 0
|
|
747
279
|
},
|
|
748
280
|
lastEventAt: null,
|
|
749
281
|
rateLimits: null,
|
|
@@ -755,10 +287,16 @@ var runtimeState = {
|
|
|
755
287
|
exitClassification: null
|
|
756
288
|
}
|
|
757
289
|
};
|
|
758
|
-
console.log(
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
290
|
+
console.log(
|
|
291
|
+
JSON.stringify(
|
|
292
|
+
{
|
|
293
|
+
package: "@gh-symphony/worker",
|
|
294
|
+
runtime: "self-hosted-sample"
|
|
295
|
+
},
|
|
296
|
+
null,
|
|
297
|
+
2
|
|
298
|
+
)
|
|
299
|
+
);
|
|
762
300
|
var childProcess = null;
|
|
763
301
|
var shutdownPromise = null;
|
|
764
302
|
var orchestratorChannelDrainPending = false;
|
|
@@ -793,7 +331,9 @@ function shutdown(signal) {
|
|
|
793
331
|
stopOrchestratorHeartbeatTimer();
|
|
794
332
|
emitOrchestratorHeartbeat();
|
|
795
333
|
await persistSessionTokenUsageArtifact(launcherEnv);
|
|
796
|
-
await waitForPendingOrchestratorChannelFlush(
|
|
334
|
+
await waitForPendingOrchestratorChannelFlush(
|
|
335
|
+
resolveTerminalOrchestratorChannelFlushTimeoutMs()
|
|
336
|
+
);
|
|
797
337
|
console.log(`Worker stopped on ${signal}`);
|
|
798
338
|
process.exit(0);
|
|
799
339
|
})();
|
|
@@ -826,13 +366,13 @@ function waitForPendingOrchestratorChannelFlush(timeoutMs = ORCHESTRATOR_CHANNEL
|
|
|
826
366
|
if (!orchestratorChannelDrainPending && pendingOrchestratorChannelPayloads.length === 0) {
|
|
827
367
|
return Promise.resolve();
|
|
828
368
|
}
|
|
829
|
-
return new Promise((
|
|
369
|
+
return new Promise((resolve) => {
|
|
830
370
|
let settled = false;
|
|
831
371
|
let timeout = setTimeout(() => {
|
|
832
372
|
settled = true;
|
|
833
373
|
process.stderr.removeListener("drain", handleDrain);
|
|
834
374
|
timeout = null;
|
|
835
|
-
|
|
375
|
+
resolve();
|
|
836
376
|
}, timeoutMs);
|
|
837
377
|
const handleDrain = () => {
|
|
838
378
|
if (orchestratorChannelDrainPending || pendingOrchestratorChannelPayloads.length > 0) {
|
|
@@ -847,14 +387,17 @@ function waitForPendingOrchestratorChannelFlush(timeoutMs = ORCHESTRATOR_CHANNEL
|
|
|
847
387
|
timeout = null;
|
|
848
388
|
}
|
|
849
389
|
process.stderr.removeListener("drain", handleDrain);
|
|
850
|
-
|
|
390
|
+
resolve();
|
|
851
391
|
};
|
|
852
392
|
process.stderr.on("drain", handleDrain);
|
|
853
393
|
});
|
|
854
394
|
}
|
|
855
395
|
function resolveTerminalOrchestratorChannelFlushTimeoutMs() {
|
|
856
396
|
const pendingPayloadCount = pendingOrchestratorChannelPayloads.length + (orchestratorChannelDrainPending ? 1 : 0);
|
|
857
|
-
return Math.max(
|
|
397
|
+
return Math.max(
|
|
398
|
+
ORCHESTRATOR_CHANNEL_FLUSH_TIMEOUT_MS,
|
|
399
|
+
pendingPayloadCount * ORCHESTRATOR_CHANNEL_FLUSH_TIMEOUT_MS
|
|
400
|
+
);
|
|
858
401
|
}
|
|
859
402
|
function writeOrQueueOrchestratorChannelPayload(serializedPayload) {
|
|
860
403
|
if (orchestratorChannelDrainPending) {
|
|
@@ -932,13 +475,22 @@ function cloneTokenUsageSnapshot() {
|
|
|
932
475
|
}
|
|
933
476
|
function resolveTurnTokenUsageDelta(baseline) {
|
|
934
477
|
return {
|
|
935
|
-
inputTokens: Math.max(
|
|
936
|
-
|
|
937
|
-
|
|
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
|
+
)
|
|
938
490
|
};
|
|
939
491
|
}
|
|
940
492
|
function resolveSessionTokenUsageDelta() {
|
|
941
|
-
return
|
|
493
|
+
return cloneTokenUsageSnapshot();
|
|
942
494
|
}
|
|
943
495
|
async function persistSessionTokenUsageArtifact(env) {
|
|
944
496
|
await persistTokenUsageArtifact(env, resolveSessionTokenUsageDelta());
|
|
@@ -971,7 +523,10 @@ function emitTurnCompletedEvent(turn) {
|
|
|
971
523
|
issueId,
|
|
972
524
|
startedAt: turn.startedAt,
|
|
973
525
|
completedAt,
|
|
974
|
-
durationMs: Math.max(
|
|
526
|
+
durationMs: Math.max(
|
|
527
|
+
0,
|
|
528
|
+
new Date(completedAt).getTime() - new Date(turn.startedAt).getTime()
|
|
529
|
+
),
|
|
975
530
|
threadId: turn.threadId,
|
|
976
531
|
turnId: turn.turnId,
|
|
977
532
|
turnCount: turn.turnCount,
|
|
@@ -992,7 +547,10 @@ function emitTurnFailedEvent(turn, error) {
|
|
|
992
547
|
issueId,
|
|
993
548
|
startedAt: turn.startedAt,
|
|
994
549
|
failedAt,
|
|
995
|
-
durationMs: Math.max(
|
|
550
|
+
durationMs: Math.max(
|
|
551
|
+
0,
|
|
552
|
+
new Date(failedAt).getTime() - new Date(turn.startedAt).getTime()
|
|
553
|
+
),
|
|
996
554
|
threadId: turn.threadId,
|
|
997
555
|
turnId: turn.turnId,
|
|
998
556
|
turnCount: turn.turnCount,
|
|
@@ -1005,16 +563,42 @@ function emitTurnFailedEvent(turn, error) {
|
|
|
1005
563
|
}
|
|
1006
564
|
async function startAssignedRun() {
|
|
1007
565
|
try {
|
|
1008
|
-
const workflowPath = launcherEnv.SYMPHONY_WORKFLOW_PATH ||
|
|
566
|
+
const workflowPath = launcherEnv.SYMPHONY_WORKFLOW_PATH || join2(launcherEnv.WORKING_DIRECTORY, "WORKFLOW.md");
|
|
1009
567
|
runtimeState.runPhase = "building_prompt";
|
|
1010
|
-
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
|
+
}
|
|
1011
595
|
runtimeState.executionPhase = resolveInitialExecutionPhase({
|
|
1012
596
|
issueState: runtimeState.run?.state,
|
|
1013
597
|
blockerCheckStates: workflow.lifecycle.blockerCheckStates,
|
|
1014
598
|
activeStates: workflow.lifecycle.activeStates
|
|
1015
599
|
});
|
|
1016
600
|
const config = resolveLocalRuntimeLaunchConfig(launcherEnv);
|
|
1017
|
-
config.agentCommand = workflow
|
|
601
|
+
config.agentCommand = resolveWorkflowRuntimeCommand(workflow);
|
|
1018
602
|
runtimeState.runPhase = "launching_agent";
|
|
1019
603
|
const plan = await prepareCodexRuntimePlan(config);
|
|
1020
604
|
childProcess = launchCodexAppServer(plan);
|
|
@@ -1026,24 +610,27 @@ async function startAssignedRun() {
|
|
|
1026
610
|
void runCodexClientProtocol(childProcess, plan, launcherEnv, {
|
|
1027
611
|
continuationGuidance: workflow.continuationGuidance
|
|
1028
612
|
});
|
|
1029
|
-
childProcess.once(
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
}
|
|
1039
|
-
runtimeState.runPhase = nextRunPhase;
|
|
1040
|
-
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;
|
|
1041
622
|
if (!preservesTerminalPhase) {
|
|
1042
|
-
runtimeState.
|
|
623
|
+
runtimeState.status = code === 0 && !signal ? "completed" : "failed";
|
|
1043
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);
|
|
1044
632
|
}
|
|
1045
|
-
|
|
1046
|
-
});
|
|
633
|
+
);
|
|
1047
634
|
childProcess.once("error", (error) => {
|
|
1048
635
|
runtimeState.status = "failed";
|
|
1049
636
|
runtimeState.runPhase = "failed";
|
|
@@ -1061,27 +648,45 @@ async function startAssignedRun() {
|
|
|
1061
648
|
await persistSessionTokenUsageArtifact(launcherEnv);
|
|
1062
649
|
}
|
|
1063
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
|
+
}
|
|
1064
667
|
async function runCodexClientProtocol(child, plan, env, options) {
|
|
1065
668
|
const renderedPrompt = env.SYMPHONY_RENDERED_PROMPT;
|
|
1066
669
|
if (!renderedPrompt) {
|
|
1067
|
-
process.stderr.write(
|
|
670
|
+
process.stderr.write(
|
|
671
|
+
"[worker] SYMPHONY_RENDERED_PROMPT not set; skipping codex client protocol\n"
|
|
672
|
+
);
|
|
1068
673
|
return;
|
|
1069
674
|
}
|
|
1070
675
|
if (!child.stdin || !child.stdout) {
|
|
1071
|
-
process.stderr.write(
|
|
676
|
+
process.stderr.write(
|
|
677
|
+
"[worker] codex process has no stdio pipes; cannot run client protocol\n"
|
|
678
|
+
);
|
|
1072
679
|
return;
|
|
1073
680
|
}
|
|
1074
|
-
const maxTurns =
|
|
1075
|
-
|
|
1076
|
-
|
|
681
|
+
const { maxTurns, exhaustedBeforeStart } = resolveMaxTurns(
|
|
682
|
+
env.SYMPHONY_MAX_TURNS
|
|
683
|
+
);
|
|
1077
684
|
const readTimeoutMs = Number(env.SYMPHONY_READ_TIMEOUT_MS) || 5e3;
|
|
1078
685
|
const turnTimeoutMs = Number(env.SYMPHONY_TURN_TIMEOUT_MS) || 36e5;
|
|
1079
686
|
const maxNonProductiveTurns = resolveMaxNonProductiveTurns(env);
|
|
1080
687
|
const issueIdentifier = env.SYMPHONY_ISSUE_IDENTIFIER ?? "";
|
|
1081
|
-
const lastTurnSummary = env.SYMPHONY_LAST_TURN_SUMMARY ?? null;
|
|
1082
688
|
const continuationGuidance = env.SYMPHONY_CONTINUATION_GUIDANCE ?? options.continuationGuidance;
|
|
1083
689
|
const { approvalPolicy, threadSandbox, turnSandboxPolicy } = resolveCodexPolicySettings(env);
|
|
1084
|
-
const budgetState = resolveSessionBudgetState(env);
|
|
1085
690
|
let previousTurnProgressSnapshot = {
|
|
1086
691
|
...captureTurnWorkspaceSnapshot(plan.cwd),
|
|
1087
692
|
lastError: runtimeState.run?.lastError ?? null
|
|
@@ -1090,10 +695,11 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1090
695
|
let lineBuffer = "";
|
|
1091
696
|
let deltaBuffer = null;
|
|
1092
697
|
function flushDeltaBuffer() {
|
|
1093
|
-
if (!deltaBuffer)
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
`
|
|
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
|
+
);
|
|
1097
703
|
deltaBuffer = null;
|
|
1098
704
|
}
|
|
1099
705
|
const pendingRequests = /* @__PURE__ */ new Map();
|
|
@@ -1101,16 +707,8 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1101
707
|
let userInputRequired = false;
|
|
1102
708
|
let turnTerminalFailurePhase = null;
|
|
1103
709
|
let activeTurnTelemetry = null;
|
|
1104
|
-
let budgetExceededReason = null;
|
|
1105
710
|
let consecutiveNonProductiveTurns = 0;
|
|
1106
711
|
let convergenceDetected = false;
|
|
1107
|
-
function checkSessionBudgets(currentSessionTurnCount) {
|
|
1108
|
-
return resolveBudgetExceededReason(budgetState, currentSessionTurnCount, {
|
|
1109
|
-
inputTokens: runtimeState.tokenUsage.inputTokens - budgetState.tokenUsageBaseline.inputTokens,
|
|
1110
|
-
outputTokens: runtimeState.tokenUsage.outputTokens - budgetState.tokenUsageBaseline.outputTokens,
|
|
1111
|
-
totalTokens: runtimeState.tokenUsage.totalTokens - budgetState.tokenUsageBaseline.totalTokens
|
|
1112
|
-
}, /* @__PURE__ */ new Date());
|
|
1113
|
-
}
|
|
1114
712
|
function resolvePendingTurnCompletion() {
|
|
1115
713
|
if (turnCompletedResolve) {
|
|
1116
714
|
turnCompletedResolve();
|
|
@@ -1118,7 +716,8 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1118
716
|
}
|
|
1119
717
|
}
|
|
1120
718
|
function describeTurnTerminalEvent(event, params) {
|
|
1121
|
-
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";
|
|
1122
721
|
if (!params || typeof params !== "object") {
|
|
1123
722
|
return fallback;
|
|
1124
723
|
}
|
|
@@ -1127,18 +726,18 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1127
726
|
for (const key of directReasonKeys) {
|
|
1128
727
|
const value = record[key];
|
|
1129
728
|
if (typeof value === "string" && value.trim()) {
|
|
1130
|
-
return `${
|
|
729
|
+
return `${errorPrefix}: ${value.trim()}`;
|
|
1131
730
|
}
|
|
1132
731
|
if (value && typeof value === "object" && typeof value.message === "string") {
|
|
1133
732
|
const nested = value;
|
|
1134
733
|
const nestedMessage = String(nested.message).trim();
|
|
1135
734
|
if (nestedMessage) {
|
|
1136
|
-
return `${
|
|
735
|
+
return `${errorPrefix}: ${nestedMessage}`;
|
|
1137
736
|
}
|
|
1138
737
|
}
|
|
1139
738
|
}
|
|
1140
739
|
const serialized = JSON.stringify(params).slice(0, 300);
|
|
1141
|
-
return serialized && serialized !== "{}" ? `${
|
|
740
|
+
return serialized && serialized !== "{}" ? `${errorPrefix}: ${serialized}` : fallback;
|
|
1142
741
|
}
|
|
1143
742
|
function markTurnTerminalFailure(runPhase, lastError) {
|
|
1144
743
|
runtimeState.status = "failed";
|
|
@@ -1158,36 +757,45 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1158
757
|
child.stdin?.write(line);
|
|
1159
758
|
}
|
|
1160
759
|
function sendRequest(id, method, params) {
|
|
1161
|
-
return new Promise((
|
|
1162
|
-
pendingRequests.set(id, { resolve
|
|
760
|
+
return new Promise((resolve, reject) => {
|
|
761
|
+
pendingRequests.set(id, { resolve, reject });
|
|
1163
762
|
sendMessage({ jsonrpc: "2.0", id, method, params });
|
|
1164
763
|
});
|
|
1165
764
|
}
|
|
1166
765
|
function sendRequestWithTimeout(id, method, params) {
|
|
1167
|
-
return new Promise((
|
|
766
|
+
return new Promise((resolve, reject) => {
|
|
1168
767
|
const timer = setTimeout(() => {
|
|
1169
768
|
pendingRequests.delete(id);
|
|
1170
|
-
reject(
|
|
769
|
+
reject(
|
|
770
|
+
new Error(
|
|
771
|
+
`response_timeout: ${method} timed out after ${readTimeoutMs}ms`
|
|
772
|
+
)
|
|
773
|
+
);
|
|
1171
774
|
}, readTimeoutMs);
|
|
1172
|
-
sendRequest(id, method, params).then(
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
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
|
+
);
|
|
1179
785
|
});
|
|
1180
786
|
}
|
|
1181
787
|
function waitForTurnCompletion() {
|
|
1182
|
-
return new Promise((
|
|
1183
|
-
turnCompletedResolve =
|
|
788
|
+
return new Promise((resolve) => {
|
|
789
|
+
turnCompletedResolve = resolve;
|
|
1184
790
|
});
|
|
1185
791
|
}
|
|
1186
792
|
function waitForTurnWithTimeout() {
|
|
1187
|
-
return new Promise((
|
|
793
|
+
return new Promise((resolve, reject) => {
|
|
1188
794
|
const timer = setTimeout(() => {
|
|
1189
|
-
process.stderr.write(
|
|
1190
|
-
`
|
|
795
|
+
process.stderr.write(
|
|
796
|
+
`[worker] turn_timeout: turn exceeded ${turnTimeoutMs}ms \u2014 killing codex process
|
|
797
|
+
`
|
|
798
|
+
);
|
|
1191
799
|
if (child.pid) {
|
|
1192
800
|
try {
|
|
1193
801
|
process.kill(child.pid, "SIGTERM");
|
|
@@ -1196,20 +804,27 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1196
804
|
}
|
|
1197
805
|
reject(new Error("turn_timeout: turn exceeded time limit"));
|
|
1198
806
|
}, turnTimeoutMs);
|
|
1199
|
-
waitForTurnCompletion().then(
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
807
|
+
waitForTurnCompletion().then(
|
|
808
|
+
() => {
|
|
809
|
+
clearTimeout(timer);
|
|
810
|
+
resolve();
|
|
811
|
+
},
|
|
812
|
+
(error) => {
|
|
813
|
+
clearTimeout(timer);
|
|
814
|
+
reject(error);
|
|
815
|
+
}
|
|
816
|
+
);
|
|
1206
817
|
});
|
|
1207
818
|
}
|
|
1208
819
|
async function dispatchDynamicToolCall(callId, toolName, threadId, turnId, args) {
|
|
1209
|
-
const toolDef = plan.tools.find(
|
|
820
|
+
const toolDef = plan.tools.find(
|
|
821
|
+
(t) => t.name === toolName
|
|
822
|
+
);
|
|
1210
823
|
if (!toolDef) {
|
|
1211
|
-
process.stderr.write(
|
|
1212
|
-
`
|
|
824
|
+
process.stderr.write(
|
|
825
|
+
`[worker] unknown dynamic tool: ${toolName}; sending error response
|
|
826
|
+
`
|
|
827
|
+
);
|
|
1213
828
|
sendMessage({
|
|
1214
829
|
jsonrpc: "2.0",
|
|
1215
830
|
method: "dynamic_tool_call_response",
|
|
@@ -1229,8 +844,10 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1229
844
|
return;
|
|
1230
845
|
}
|
|
1231
846
|
const inputJson = JSON.stringify(args ?? {});
|
|
1232
|
-
process.stderr.write(
|
|
1233
|
-
`)
|
|
847
|
+
process.stderr.write(
|
|
848
|
+
`[worker] executing dynamic tool "${toolName}" (callId=${callId})
|
|
849
|
+
`
|
|
850
|
+
);
|
|
1234
851
|
try {
|
|
1235
852
|
const output = await runToolProcess(toolDef, inputJson);
|
|
1236
853
|
sendMessage({
|
|
@@ -1261,138 +878,185 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1261
878
|
});
|
|
1262
879
|
}
|
|
1263
880
|
}
|
|
1264
|
-
function
|
|
1265
|
-
if (
|
|
1266
|
-
const id = String(msg.id);
|
|
1267
|
-
const pending = pendingRequests.get(id);
|
|
1268
|
-
if (pending) {
|
|
1269
|
-
pendingRequests.delete(id);
|
|
1270
|
-
if ("error" in msg) {
|
|
1271
|
-
pending.reject(new Error(JSON.stringify(msg.error)));
|
|
1272
|
-
} else {
|
|
1273
|
-
pending.resolve(msg.result);
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
881
|
+
function emitObservedAgentEvent(event) {
|
|
882
|
+
if (event.payload.suppressUpdate) {
|
|
1276
883
|
return;
|
|
1277
884
|
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
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;
|
|
1285
895
|
}
|
|
1286
|
-
if (
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
if (runtimeState.run) {
|
|
1291
|
-
runtimeState.run.lastError = "turn_input_required: agent requires user input";
|
|
1292
|
-
}
|
|
1293
|
-
if (child.pid) {
|
|
1294
|
-
try {
|
|
1295
|
-
process.kill(child.pid, "SIGTERM");
|
|
1296
|
-
} catch {
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
if (activeTurnTelemetry) {
|
|
1300
|
-
emitTurnFailedEvent(activeTurnTelemetry, runtimeState.run?.lastError ?? null);
|
|
1301
|
-
activeTurnTelemetry = null;
|
|
896
|
+
if (child.pid) {
|
|
897
|
+
try {
|
|
898
|
+
process.kill(child.pid, "SIGTERM");
|
|
899
|
+
} catch {
|
|
1302
900
|
}
|
|
1303
|
-
resolvePendingTurnCompletion();
|
|
1304
|
-
emitOrchestratorChannelEvent(orchestratorEventName);
|
|
1305
|
-
return;
|
|
1306
901
|
}
|
|
1307
|
-
if (
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
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;
|
|
1313
948
|
}
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
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;
|
|
1317
959
|
}
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
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;
|
|
1324
967
|
}
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
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;
|
|
1330
976
|
}
|
|
977
|
+
emitObservedAgentEvent(event);
|
|
1331
978
|
if (activeTurnTelemetry) {
|
|
1332
|
-
|
|
979
|
+
emitTurnCompletedEvent(activeTurnTelemetry);
|
|
1333
980
|
activeTurnTelemetry = null;
|
|
1334
981
|
}
|
|
982
|
+
process.stderr.write("[worker] agent turn completed\n");
|
|
1335
983
|
resolvePendingTurnCompletion();
|
|
1336
|
-
|
|
1337
|
-
|
|
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;
|
|
1338
998
|
}
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
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;
|
|
1343
1012
|
}
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
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;
|
|
1347
1024
|
}
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
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
|
+
}
|
|
1355
1038
|
return;
|
|
1356
1039
|
}
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
markTurnTerminalFailure("canceled_by_reconciliation", lastError);
|
|
1363
|
-
emitOrchestratorChannelEvent(orchestratorEventName);
|
|
1364
|
-
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;
|
|
1365
1045
|
}
|
|
1366
|
-
if (
|
|
1367
|
-
const tokenUsage = extractAbsoluteTokenUsage(msg.params);
|
|
1368
|
-
if (tokenUsage) {
|
|
1369
|
-
applyTokenUsageUpdate(msg.method, tokenUsage);
|
|
1370
|
-
}
|
|
1371
|
-
emitOrchestratorChannelEvent(orchestratorEventName);
|
|
1046
|
+
if (handledAgentEvent) {
|
|
1372
1047
|
return;
|
|
1373
1048
|
}
|
|
1374
1049
|
const rateLimits = extractRateLimitPayload(msg.params);
|
|
1375
1050
|
if (rateLimits && typeof msg.method === "string") {
|
|
1376
1051
|
applyRateLimitUpdate(msg.method, rateLimits);
|
|
1377
1052
|
}
|
|
1378
|
-
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")) {
|
|
1379
|
-
const params = msg.params ?? {};
|
|
1380
|
-
const delta = typeof params.delta === "string" ? params.delta : "";
|
|
1381
|
-
const itemId = typeof params.item_id === "string" ? params.item_id : "";
|
|
1382
|
-
if (deltaBuffer?.itemId !== itemId) {
|
|
1383
|
-
flushDeltaBuffer();
|
|
1384
|
-
deltaBuffer = { itemId, text: delta };
|
|
1385
|
-
} else {
|
|
1386
|
-
deltaBuffer.text += delta;
|
|
1387
|
-
}
|
|
1388
|
-
emitOrchestratorChannelEvent(orchestratorEventName);
|
|
1389
|
-
return;
|
|
1390
|
-
}
|
|
1391
1053
|
if (typeof msg.method === "string") {
|
|
1392
1054
|
flushDeltaBuffer();
|
|
1393
|
-
emitOrchestratorChannelEvent(
|
|
1394
|
-
process.stderr.write(
|
|
1395
|
-
`)
|
|
1055
|
+
emitOrchestratorChannelEvent(msg.method);
|
|
1056
|
+
process.stderr.write(
|
|
1057
|
+
`[worker] codex \u2192 ${msg.method} ${JSON.stringify(msg.params ?? {}).slice(0, 300)}
|
|
1058
|
+
`
|
|
1059
|
+
);
|
|
1396
1060
|
}
|
|
1397
1061
|
}
|
|
1398
1062
|
child.stdout.on("data", (chunk) => {
|
|
@@ -1401,8 +1065,7 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1401
1065
|
lineBuffer = lines.pop() ?? "";
|
|
1402
1066
|
for (const line of lines) {
|
|
1403
1067
|
const trimmed = line.trim();
|
|
1404
|
-
if (!trimmed)
|
|
1405
|
-
continue;
|
|
1068
|
+
if (!trimmed) continue;
|
|
1406
1069
|
try {
|
|
1407
1070
|
const msg = JSON.parse(trimmed);
|
|
1408
1071
|
handleServerMessage(msg);
|
|
@@ -1428,27 +1091,6 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1428
1091
|
env: t.env
|
|
1429
1092
|
};
|
|
1430
1093
|
}
|
|
1431
|
-
if (remainingTurns <= 0) {
|
|
1432
|
-
process.stderr.write(`[worker] max_turns already exhausted by previous sessions (${cumulativeTurnCount}/${maxTurns})
|
|
1433
|
-
`);
|
|
1434
|
-
runtimeState.status = "completed";
|
|
1435
|
-
runtimeState.runPhase = "succeeded";
|
|
1436
|
-
runtimeState.sessionInfo.exitClassification = classifySessionExit({
|
|
1437
|
-
runPhase: runtimeState.runPhase,
|
|
1438
|
-
userInputRequired: false,
|
|
1439
|
-
budgetExceeded: false,
|
|
1440
|
-
convergenceDetected: false,
|
|
1441
|
-
maxTurnsReached: true
|
|
1442
|
-
});
|
|
1443
|
-
stopOrchestratorHeartbeatTimer();
|
|
1444
|
-
emitOrchestratorHeartbeat();
|
|
1445
|
-
await persistSessionTokenUsageArtifact(env);
|
|
1446
|
-
await waitForPendingOrchestratorChannelFlush(resolveTerminalOrchestratorChannelFlushTimeoutMs());
|
|
1447
|
-
setTimeout(() => {
|
|
1448
|
-
process.exit(0);
|
|
1449
|
-
}, 1500);
|
|
1450
|
-
return;
|
|
1451
|
-
}
|
|
1452
1094
|
const baseThreadParams = {
|
|
1453
1095
|
cwd: plan.cwd,
|
|
1454
1096
|
developerInstructions: renderedPrompt,
|
|
@@ -1458,86 +1100,70 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1458
1100
|
mcp_servers: mcpServers
|
|
1459
1101
|
}
|
|
1460
1102
|
};
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
try {
|
|
1470
|
-
threadResult = await sendRequestWithTimeout("thread-resume-1", "thread/resume", {
|
|
1471
|
-
...baseThreadParams,
|
|
1472
|
-
threadId: resumeThreadId
|
|
1473
|
-
});
|
|
1474
|
-
threadBootstrapMode = "resume";
|
|
1475
|
-
} catch (error) {
|
|
1476
|
-
const message = error instanceof Error ? error.message : String(error ?? "unknown");
|
|
1477
|
-
threadBootstrapMode = "soft-resume";
|
|
1478
|
-
process.stderr.write(`[worker] thread/resume failed for ${resumeThreadId}: ${message}; falling back to thread/start
|
|
1479
|
-
`);
|
|
1480
|
-
threadResult = await sendRequestWithTimeout("thread-1", "thread/start", {
|
|
1481
|
-
...baseThreadParams,
|
|
1482
|
-
ephemeral: false
|
|
1483
|
-
});
|
|
1484
|
-
}
|
|
1485
|
-
} else {
|
|
1486
|
-
threadResult = await sendRequestWithTimeout("thread-1", "thread/start", {
|
|
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
|
+
{
|
|
1487
1111
|
...baseThreadParams,
|
|
1488
1112
|
ephemeral: false
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1113
|
+
}
|
|
1114
|
+
);
|
|
1491
1115
|
const threadId = threadResult.thread_id ?? threadResult.thread?.id;
|
|
1492
1116
|
runtimeState.sessionInfo.threadId = threadId ?? null;
|
|
1493
1117
|
runtimeState.sessionInfo.turnId = null;
|
|
1494
1118
|
runtimeState.sessionInfo.sessionId = null;
|
|
1495
1119
|
runtimeState.sessionInfo.exitClassification = null;
|
|
1496
1120
|
runtimeState.sessionId = null;
|
|
1497
|
-
process.stderr.write(
|
|
1498
|
-
`)
|
|
1121
|
+
process.stderr.write(
|
|
1122
|
+
`[worker] codex thread started (id=${String(threadId ?? "unknown")})
|
|
1123
|
+
`
|
|
1124
|
+
);
|
|
1499
1125
|
if (!threadId) {
|
|
1500
|
-
process.stderr.write(
|
|
1126
|
+
process.stderr.write(
|
|
1127
|
+
"[worker] warning: no threadId returned; cannot start turn\n"
|
|
1128
|
+
);
|
|
1501
1129
|
return;
|
|
1502
1130
|
}
|
|
1503
1131
|
let turnCount = 0;
|
|
1504
1132
|
let requestIdCounter = 0;
|
|
1505
|
-
let maxTurnsReached =
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1133
|
+
let maxTurnsReached = exhaustedBeforeStart;
|
|
1134
|
+
if (exhaustedBeforeStart) {
|
|
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
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
for (let turn = 0; turn < maxTurns; turn++) {
|
|
1513
1141
|
turnCount = turn + 1;
|
|
1514
|
-
const globalTurnCount = cumulativeTurnCount + turnCount;
|
|
1515
1142
|
runtimeState.sessionInfo.turnCount = turnCount;
|
|
1516
1143
|
runtimeState.runPhase = "streaming_turn";
|
|
1517
1144
|
const isFirstTurn = turn === 0;
|
|
1518
|
-
const turnInput = isFirstTurn ?
|
|
1519
|
-
renderedPrompt,
|
|
1520
|
-
mode: threadBootstrapMode,
|
|
1521
|
-
lastTurnSummary,
|
|
1522
|
-
cumulativeTurnCount,
|
|
1523
|
-
continuationGuidance
|
|
1524
|
-
}) : buildContinuationTurnInput({
|
|
1145
|
+
const turnInput = isFirstTurn ? renderedPrompt : buildContinuationTurnInput({
|
|
1525
1146
|
continuationGuidance,
|
|
1526
|
-
|
|
1527
|
-
cumulativeTurnCount: globalTurnCount - 1
|
|
1147
|
+
cumulativeTurnCount: turn
|
|
1528
1148
|
});
|
|
1529
|
-
process.stderr.write(
|
|
1530
|
-
`)
|
|
1149
|
+
process.stderr.write(
|
|
1150
|
+
`[worker] starting codex turn ${turnCount}/${maxTurns}${isFirstTurn ? " (initial)" : " (continuation)"}
|
|
1151
|
+
`
|
|
1152
|
+
);
|
|
1531
1153
|
requestIdCounter += 1;
|
|
1532
1154
|
const turnRequestId = `turn-${requestIdCounter}`;
|
|
1533
|
-
const turnResult = await sendRequestWithTimeout(
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
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
|
+
);
|
|
1541
1167
|
const turnId = turnResult.turn_id ?? turnResult.turn?.id;
|
|
1542
1168
|
const sessionId = threadId && turnId ? `${threadId}-${turnId}` : null;
|
|
1543
1169
|
runtimeState.sessionInfo.turnId = turnId ?? null;
|
|
@@ -1551,10 +1177,14 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1551
1177
|
sessionId,
|
|
1552
1178
|
tokenUsageBaseline: cloneTokenUsageSnapshot()
|
|
1553
1179
|
};
|
|
1554
|
-
process.stderr.write(
|
|
1555
|
-
`)
|
|
1556
|
-
|
|
1557
|
-
|
|
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
|
+
);
|
|
1558
1188
|
emitTurnStartedEvent(activeTurnTelemetry);
|
|
1559
1189
|
await waitForTurnWithTimeout();
|
|
1560
1190
|
if (userInputRequired) {
|
|
@@ -1562,20 +1192,18 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1562
1192
|
break;
|
|
1563
1193
|
}
|
|
1564
1194
|
if (turnTerminalFailurePhase) {
|
|
1565
|
-
process.stderr.write(
|
|
1566
|
-
`
|
|
1195
|
+
process.stderr.write(
|
|
1196
|
+
`[worker] exiting due to ${turnTerminalFailurePhase}
|
|
1197
|
+
`
|
|
1198
|
+
);
|
|
1567
1199
|
break;
|
|
1568
1200
|
}
|
|
1569
|
-
|
|
1570
|
-
if (budgetExceededReason) {
|
|
1571
|
-
process.stderr.write(`[worker] session budget exceeded (${budgetExceededReason}) \u2014 exiting
|
|
1572
|
-
`);
|
|
1573
|
-
break;
|
|
1574
|
-
}
|
|
1575
|
-
if (turn + 1 >= remainingTurns) {
|
|
1201
|
+
if (turn + 1 >= maxTurns) {
|
|
1576
1202
|
maxTurnsReached = true;
|
|
1577
|
-
process.stderr.write(
|
|
1578
|
-
`)
|
|
1203
|
+
process.stderr.write(
|
|
1204
|
+
`[worker] max_turns (${maxTurns}) reached for this worker session \u2014 exiting
|
|
1205
|
+
`
|
|
1206
|
+
);
|
|
1579
1207
|
break;
|
|
1580
1208
|
}
|
|
1581
1209
|
const trackerState = await refreshTrackerState(env);
|
|
@@ -1588,19 +1216,26 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1588
1216
|
trackerState,
|
|
1589
1217
|
userInputRequired: false
|
|
1590
1218
|
});
|
|
1591
|
-
process.stderr.write(
|
|
1219
|
+
process.stderr.write(
|
|
1220
|
+
"[worker] issue no longer actionable \u2014 exiting multi-turn loop\n"
|
|
1221
|
+
);
|
|
1592
1222
|
break;
|
|
1593
1223
|
}
|
|
1594
1224
|
const currentTurnProgressSnapshot = {
|
|
1595
1225
|
...captureTurnWorkspaceSnapshot(plan.cwd),
|
|
1596
1226
|
lastError: runtimeState.run?.lastError ?? null
|
|
1597
1227
|
};
|
|
1598
|
-
const turnProgress = evaluateTurnProgress(
|
|
1228
|
+
const turnProgress = evaluateTurnProgress(
|
|
1229
|
+
previousTurnProgressSnapshot,
|
|
1230
|
+
currentTurnProgressSnapshot
|
|
1231
|
+
);
|
|
1599
1232
|
previousTurnProgressSnapshot = currentTurnProgressSnapshot;
|
|
1600
1233
|
if (turnProgress.nonProductive) {
|
|
1601
1234
|
consecutiveNonProductiveTurns += 1;
|
|
1602
|
-
process.stderr.write(
|
|
1603
|
-
`)
|
|
1235
|
+
process.stderr.write(
|
|
1236
|
+
`[worker] non-productive turn detected (${consecutiveNonProductiveTurns}/${maxNonProductiveTurns})${turnProgress.reason ? `: ${turnProgress.reason}` : ""}
|
|
1237
|
+
`
|
|
1238
|
+
);
|
|
1604
1239
|
} else {
|
|
1605
1240
|
consecutiveNonProductiveTurns = 0;
|
|
1606
1241
|
}
|
|
@@ -1609,27 +1244,33 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1609
1244
|
if (runtimeState.run) {
|
|
1610
1245
|
runtimeState.run.lastError = turnProgress.reason ? `convergence_detected: ${turnProgress.reason}` : "convergence_detected: repeated non-productive turn results";
|
|
1611
1246
|
}
|
|
1612
|
-
process.stderr.write(
|
|
1613
|
-
`
|
|
1247
|
+
process.stderr.write(
|
|
1248
|
+
`[worker] convergence detected after ${consecutiveNonProductiveTurns} non-productive turns \u2014 exiting
|
|
1249
|
+
`
|
|
1250
|
+
);
|
|
1614
1251
|
break;
|
|
1615
1252
|
}
|
|
1616
1253
|
}
|
|
1617
|
-
process.stderr.write(
|
|
1618
|
-
`)
|
|
1254
|
+
process.stderr.write(
|
|
1255
|
+
`[worker] multi-turn loop complete after ${turnCount} turn(s) \u2014 exiting worker
|
|
1256
|
+
`
|
|
1257
|
+
);
|
|
1619
1258
|
runtimeState.runPhase = "finishing";
|
|
1620
1259
|
runtimeState.status = userInputRequired || turnTerminalFailurePhase ? "failed" : "completed";
|
|
1621
1260
|
runtimeState.runPhase = convergenceDetected ? "failed" : userInputRequired ? "failed" : turnTerminalFailurePhase ?? "succeeded";
|
|
1622
1261
|
runtimeState.sessionInfo.exitClassification = classifySessionExit({
|
|
1623
1262
|
runPhase: runtimeState.runPhase,
|
|
1624
1263
|
userInputRequired,
|
|
1625
|
-
budgetExceeded:
|
|
1264
|
+
budgetExceeded: false,
|
|
1626
1265
|
convergenceDetected,
|
|
1627
1266
|
maxTurnsReached
|
|
1628
1267
|
});
|
|
1629
1268
|
stopOrchestratorHeartbeatTimer();
|
|
1630
1269
|
emitOrchestratorHeartbeat();
|
|
1631
1270
|
await persistSessionTokenUsageArtifact(env);
|
|
1632
|
-
await waitForPendingOrchestratorChannelFlush(
|
|
1271
|
+
await waitForPendingOrchestratorChannelFlush(
|
|
1272
|
+
resolveTerminalOrchestratorChannelFlushTimeoutMs()
|
|
1273
|
+
);
|
|
1633
1274
|
setTimeout(() => {
|
|
1634
1275
|
process.exit(userInputRequired || turnTerminalFailurePhase ? 1 : 0);
|
|
1635
1276
|
}, 1500);
|
|
@@ -1661,13 +1302,18 @@ async function runCodexClientProtocol(child, plan, env, options) {
|
|
|
1661
1302
|
maxTurnsReached: false
|
|
1662
1303
|
});
|
|
1663
1304
|
if (activeTurnTelemetry) {
|
|
1664
|
-
emitTurnFailedEvent(
|
|
1305
|
+
emitTurnFailedEvent(
|
|
1306
|
+
activeTurnTelemetry,
|
|
1307
|
+
runtimeState.run?.lastError ?? errMsg
|
|
1308
|
+
);
|
|
1665
1309
|
activeTurnTelemetry = null;
|
|
1666
1310
|
}
|
|
1667
1311
|
stopOrchestratorHeartbeatTimer();
|
|
1668
1312
|
emitOrchestratorHeartbeat();
|
|
1669
1313
|
await persistSessionTokenUsageArtifact(env);
|
|
1670
|
-
await waitForPendingOrchestratorChannelFlush(
|
|
1314
|
+
await waitForPendingOrchestratorChannelFlush(
|
|
1315
|
+
resolveTerminalOrchestratorChannelFlushTimeoutMs()
|
|
1316
|
+
);
|
|
1671
1317
|
setTimeout(() => {
|
|
1672
1318
|
process.exit(1);
|
|
1673
1319
|
}, 1500);
|
|
@@ -1677,16 +1323,20 @@ function applyTokenUsageUpdate(source, tokenUsage) {
|
|
|
1677
1323
|
runtimeState.tokenUsage.inputTokens = tokenUsage.inputTokens;
|
|
1678
1324
|
runtimeState.tokenUsage.outputTokens = tokenUsage.outputTokens;
|
|
1679
1325
|
runtimeState.tokenUsage.totalTokens = tokenUsage.totalTokens;
|
|
1680
|
-
process.stderr.write(
|
|
1681
|
-
`
|
|
1326
|
+
process.stderr.write(
|
|
1327
|
+
`[worker] token_usage source=${source} input=${tokenUsage.inputTokens} output=${tokenUsage.outputTokens} total=${tokenUsage.totalTokens}
|
|
1328
|
+
`
|
|
1329
|
+
);
|
|
1682
1330
|
}
|
|
1683
1331
|
function applyRateLimitUpdate(source, rateLimits) {
|
|
1684
1332
|
runtimeState.rateLimits = {
|
|
1685
1333
|
...rateLimits,
|
|
1686
1334
|
source: "codex"
|
|
1687
1335
|
};
|
|
1688
|
-
process.stderr.write(
|
|
1689
|
-
`)
|
|
1336
|
+
process.stderr.write(
|
|
1337
|
+
`[worker] rate_limits source=${source} payload=${JSON.stringify(runtimeState.rateLimits).slice(0, 300)}
|
|
1338
|
+
`
|
|
1339
|
+
);
|
|
1690
1340
|
}
|
|
1691
1341
|
function extractRateLimitPayload(value) {
|
|
1692
1342
|
if (!value || typeof value !== "object") {
|
|
@@ -1817,22 +1467,23 @@ async function refreshTrackerState(env) {
|
|
|
1817
1467
|
}
|
|
1818
1468
|
try {
|
|
1819
1469
|
const response = await fetch(`${orchestratorUrl}/api/v1/state`);
|
|
1820
|
-
if (!response.ok)
|
|
1821
|
-
return "unknown";
|
|
1470
|
+
if (!response.ok) return "unknown";
|
|
1822
1471
|
const status = await response.json();
|
|
1823
|
-
const isActive = status.activeRuns?.some(
|
|
1472
|
+
const isActive = status.activeRuns?.some(
|
|
1473
|
+
(run) => run.issueIdentifier === issueIdentifier
|
|
1474
|
+
);
|
|
1824
1475
|
return isActive ? "active" : "non-actionable";
|
|
1825
1476
|
} catch {
|
|
1826
1477
|
return "unknown";
|
|
1827
1478
|
}
|
|
1828
1479
|
}
|
|
1829
1480
|
function runToolProcess(toolDef, inputJson) {
|
|
1830
|
-
return new Promise((
|
|
1481
|
+
return new Promise((resolve, reject) => {
|
|
1831
1482
|
const toolEnv = {
|
|
1832
1483
|
...process.env,
|
|
1833
1484
|
...toolDef.env
|
|
1834
1485
|
};
|
|
1835
|
-
const toolProc =
|
|
1486
|
+
const toolProc = spawn(toolDef.command, toolDef.args, {
|
|
1836
1487
|
env: toolEnv,
|
|
1837
1488
|
stdio: "pipe"
|
|
1838
1489
|
});
|
|
@@ -1844,10 +1495,14 @@ function runToolProcess(toolDef, inputJson) {
|
|
|
1844
1495
|
toolProc.once("exit", (code) => {
|
|
1845
1496
|
const output = Buffer.concat(stdout).toString("utf8").trim();
|
|
1846
1497
|
if (code === 0) {
|
|
1847
|
-
|
|
1498
|
+
resolve(output || "{}");
|
|
1848
1499
|
} else {
|
|
1849
1500
|
const errOutput = Buffer.concat(stderr).toString("utf8").trim();
|
|
1850
|
-
reject(
|
|
1501
|
+
reject(
|
|
1502
|
+
new Error(
|
|
1503
|
+
`Tool exited with code ${code ?? "unknown"}: ${errOutput || output}`
|
|
1504
|
+
)
|
|
1505
|
+
);
|
|
1851
1506
|
}
|
|
1852
1507
|
});
|
|
1853
1508
|
toolProc.stdin?.write(inputJson);
|