doer-agent 0.5.9 → 0.6.0
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/dist/agent-bundled-skills.js +79 -0
- package/dist/agent-codex-app-rpc.js +63 -0
- package/dist/agent-codex-auth-rpc.js +96 -238
- package/dist/agent-codex-cli.js +1 -11
- package/dist/agent-runtime-utils.js +4 -65
- package/dist/agent-session-loop.js +0 -1
- package/dist/agent-settings-rpc.js +10 -82
- package/dist/agent-settings.js +1 -115
- package/dist/agent.js +47 -311
- package/dist/codex-app-server-client.js +148 -0
- package/dist/codex-app-server-manager.js +108 -0
- package/package.json +1 -4
- package/dist/agent-run-execution.js +0 -39
- package/dist/agent-run-lifecycle.js +0 -67
- package/dist/agent-run-rpc.js +0 -93
- package/dist/agent-run-state.js +0 -287
- package/dist/agent-runtime-utils.test.js +0 -38
- package/dist/agent-session-rpc.js +0 -1033
- package/dist/db-mcp-server.js +0 -377
package/dist/agent.js
CHANGED
|
@@ -2,22 +2,18 @@ import { mkdir } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { StringCodec } from "nats";
|
|
5
|
-
import { buildAgentSettingsEnvPatch,
|
|
5
|
+
import { buildAgentSettingsEnvPatch, readAgentSettingsConfig, } from "./agent-settings.js";
|
|
6
6
|
import { handleFsRpcMessage } from "./agent-fs-rpc.js";
|
|
7
7
|
import { handleGitRpcMessage } from "./agent-git-rpc.js";
|
|
8
|
-
import {
|
|
8
|
+
import { subscribeToCodexAppRpc } from "./agent-codex-app-rpc.js";
|
|
9
|
+
import { createCodexAppServerManager } from "./codex-app-server-manager.js";
|
|
9
10
|
import { subscribeToDaemonRpc } from "./agent-daemon-rpc.js";
|
|
10
|
-
import {
|
|
11
|
+
import { createLocalCodexCliTools } from "./agent-codex-cli.js";
|
|
11
12
|
import { connectBootstrapWithRetry } from "./agent-jetstream.js";
|
|
12
|
-
import { prepareCommandExecution } from "./agent-run-execution.js";
|
|
13
|
-
import { attachManagedRunProcessLifecycle, createPendingRunSessionTracker } from "./agent-run-lifecycle.js";
|
|
14
|
-
import { claimRunStartSlot, cloneRunTask, getStoredRun, listPersistedRunTasks, persistRunTask, publishImmediateRunEvent, pruneStaleRunsDir, releaseRunStartSlot, removeRunTask, updateRunStartSlotSession, } from "./agent-run-state.js";
|
|
15
13
|
import { runConnectedAgentSession } from "./agent-session-loop.js";
|
|
16
14
|
import { subscribeToSkillRpc } from "./agent-skill-rpc.js";
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import { handleNonStartRunRpc, normalizeRunRpcRequest, publishRunRpcResponse, } from "./agent-run-rpc.js";
|
|
20
|
-
import { buildAgentCodexAuthRpcSubject, buildAgentDaemonRpcSubject, buildAgentFsRpcSubject, buildAgentGitRpcSubject, buildAgentRunEventsSubject, buildAgentRunRpcSubject, buildAgentSessionRpcSubject, buildAgentSettingsRpcSubject, buildAgentSkillRpcSubject, formatLocalTimestamp, normalizeEnvPatch, filterValidRunImagePaths, normalizeRunImagePaths, parseArgs, resolveAgentVersion, resolveArgOrEnv, resolveContainerReachableServerBaseUrl, sanitizeUserId, sleep, writeRunStatus, writeRunStream, } from "./agent-runtime-utils.js";
|
|
15
|
+
import { sendSignalToTaskProcess } from "./agent-task-execution.js";
|
|
16
|
+
import { buildAgentCodexAppEventsSubject, buildAgentCodexAppRpcSubject, buildAgentDaemonRpcSubject, buildAgentFsRpcSubject, buildAgentGitRpcSubject, buildAgentSettingsRpcSubject, buildAgentSkillRpcSubject, formatLocalTimestamp, parseArgs, resolveAgentVersion, resolveArgOrEnv, resolveContainerReachableServerBaseUrl, sanitizeUserId, sleep, } from "./agent-runtime-utils.js";
|
|
21
17
|
import { createRuntimeEnvHelpers } from "./agent-runtime-env.js";
|
|
22
18
|
import { createEventPersistenceHelpers, heartbeatAgentSession, postJson, } from "./agent-runtime-io.js";
|
|
23
19
|
import { handleSettingsRpcMessage } from "./agent-settings-rpc.js";
|
|
@@ -27,12 +23,12 @@ const AGENT_PROJECT_DIR = path.join(AGENT_MODULE_DIR, "..");
|
|
|
27
23
|
const AGENT_PACKAGE_JSON_PATH = path.join(AGENT_PROJECT_DIR, "package.json");
|
|
28
24
|
const HEARTBEAT_INTERVAL_MS = 5_000;
|
|
29
25
|
const HEARTBEAT_FAILURE_THRESHOLD = 3;
|
|
26
|
+
const codexAppEventCodec = StringCodec();
|
|
30
27
|
let activeTaskLogContext = null;
|
|
31
28
|
let workspaceRootOverride = null;
|
|
32
29
|
function resolveWorkspaceRoot() {
|
|
33
30
|
return workspaceRootOverride ?? (process.env.WORKSPACE?.trim() || process.cwd());
|
|
34
31
|
}
|
|
35
|
-
const runRpcCodec = StringCodec();
|
|
36
32
|
function writeAgentInfo(message) {
|
|
37
33
|
process.stdout.write(`[doer-agent] ${message}\n`);
|
|
38
34
|
eventPersistenceHelpers.emitAgentMetaLog("info", message);
|
|
@@ -49,143 +45,6 @@ function writeAgentInfraError(message) {
|
|
|
49
45
|
// Keep heartbeat/connectivity failures non-fatal.
|
|
50
46
|
}
|
|
51
47
|
}
|
|
52
|
-
async function updateRunSessionMetadata(task, metadata) {
|
|
53
|
-
let changed = false;
|
|
54
|
-
const previousSessionId = task.sessionId;
|
|
55
|
-
if (!task.sessionId && typeof metadata.sessionId === "string" && metadata.sessionId.trim()) {
|
|
56
|
-
task.sessionId = metadata.sessionId.trim();
|
|
57
|
-
changed = true;
|
|
58
|
-
}
|
|
59
|
-
if (!task.sessionFilePath && typeof metadata.sessionFilePath === "string" && metadata.sessionFilePath.trim()) {
|
|
60
|
-
task.sessionFilePath = metadata.sessionFilePath.trim();
|
|
61
|
-
changed = true;
|
|
62
|
-
}
|
|
63
|
-
if (!task.sessionFilePath && task.sessionId) {
|
|
64
|
-
const resolvedSessionFilePath = await findSessionFilePathBySessionId(resolveWorkspaceRoot(), task.sessionId).catch(() => null);
|
|
65
|
-
if (resolvedSessionFilePath) {
|
|
66
|
-
task.sessionFilePath = resolvedSessionFilePath;
|
|
67
|
-
changed = true;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
if (!changed) {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
task.updatedAt = formatLocalTimestamp();
|
|
74
|
-
await persistRunTask(resolveWorkspaceRoot(), task).catch(() => undefined);
|
|
75
|
-
if (metadata.nc) {
|
|
76
|
-
publishImmediateRunEvent({
|
|
77
|
-
nc: metadata.nc,
|
|
78
|
-
userId: task.userId,
|
|
79
|
-
task,
|
|
80
|
-
buildRunEventsSubject: buildAgentRunEventsSubject,
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
if (!previousSessionId && task.sessionId) {
|
|
84
|
-
await updateRunStartSlotSession({
|
|
85
|
-
workspaceRoot: resolveWorkspaceRoot(),
|
|
86
|
-
runId: task.id,
|
|
87
|
-
previousSessionId,
|
|
88
|
-
sessionId: task.sessionId,
|
|
89
|
-
formatTimestamp: formatLocalTimestamp,
|
|
90
|
-
}).catch(() => undefined);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
async function startManagedRun(args) {
|
|
94
|
-
const prepared = await prepareCommandExecution({
|
|
95
|
-
cwd: args.cwd,
|
|
96
|
-
userId: args.userId,
|
|
97
|
-
taskId: args.runId,
|
|
98
|
-
codexAuthBundle: args.codexAuthBundle,
|
|
99
|
-
runtimeEnvPatch: args.runtimeEnvPatch,
|
|
100
|
-
agentProjectDir: AGENT_PROJECT_DIR,
|
|
101
|
-
resolveShellPath: runtimeEnvHelpers.resolveShellPath,
|
|
102
|
-
resolveTaskWorkspace: runtimeEnvHelpers.resolveTaskWorkspace,
|
|
103
|
-
resolveCodexHomePath: runtimeEnvHelpers.resolveCodexHomePath,
|
|
104
|
-
prepareCodexAuthBundle,
|
|
105
|
-
readAgentSettingsConfig,
|
|
106
|
-
resolveWorkspaceRoot,
|
|
107
|
-
buildAgentSettingsEnvPatch,
|
|
108
|
-
prepareTaskGitEnv: runtimeEnvHelpers.prepareTaskGitEnv,
|
|
109
|
-
});
|
|
110
|
-
const child = spawnManagedCodexCommand({
|
|
111
|
-
codexArgs: args.codexArgs,
|
|
112
|
-
taskWorkspace: prepared.taskWorkspace,
|
|
113
|
-
env: prepared.env,
|
|
114
|
-
agentToken: args.agentToken,
|
|
115
|
-
});
|
|
116
|
-
const now = formatLocalTimestamp();
|
|
117
|
-
const task = {
|
|
118
|
-
id: args.runId,
|
|
119
|
-
userId: args.userId,
|
|
120
|
-
agentId: args.agentId,
|
|
121
|
-
processPid: typeof child.pid === "number" ? child.pid : null,
|
|
122
|
-
sessionId: typeof args.sessionId === "string" && args.sessionId.trim() ? args.sessionId.trim() : null,
|
|
123
|
-
sessionFilePath: null,
|
|
124
|
-
status: "running",
|
|
125
|
-
cancelRequested: false,
|
|
126
|
-
resultExitCode: null,
|
|
127
|
-
resultSignal: null,
|
|
128
|
-
error: null,
|
|
129
|
-
createdAt: now,
|
|
130
|
-
updatedAt: now,
|
|
131
|
-
startedAt: now,
|
|
132
|
-
finishedAt: null,
|
|
133
|
-
};
|
|
134
|
-
let pendingSessionPollClosed = false;
|
|
135
|
-
const knownPendingSessionFiles = new Set();
|
|
136
|
-
const pendingSessionTracker = createPendingRunSessionTracker({
|
|
137
|
-
task,
|
|
138
|
-
detectPendingRunSession: async () => detectPendingRunSession(resolveWorkspaceRoot(), knownPendingSessionFiles),
|
|
139
|
-
updateRunSessionMetadata: async (metadata) => updateRunSessionMetadata(task, { ...metadata, nc: args.nc }),
|
|
140
|
-
});
|
|
141
|
-
const stopPendingSessionPoll = () => {
|
|
142
|
-
pendingSessionPollClosed = true;
|
|
143
|
-
pendingSessionTracker.stop();
|
|
144
|
-
};
|
|
145
|
-
const pollPendingSession = async () => {
|
|
146
|
-
if (pendingSessionPollClosed) {
|
|
147
|
-
stopPendingSessionPoll();
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
await pendingSessionTracker.poll();
|
|
151
|
-
};
|
|
152
|
-
if (!task.sessionId) {
|
|
153
|
-
const existingFiles = await collectSessionJsonlFiles(resolveWorkspaceRoot()).catch(() => []);
|
|
154
|
-
for (const file of existingFiles) {
|
|
155
|
-
knownPendingSessionFiles.add(file.filePath);
|
|
156
|
-
}
|
|
157
|
-
pendingSessionTracker.start();
|
|
158
|
-
}
|
|
159
|
-
child.stdout.on("data", (chunk) => writeRunStream(task.id, "stdout", chunk));
|
|
160
|
-
child.stderr.on("data", (chunk) => writeRunStream(task.id, "stderr", chunk));
|
|
161
|
-
attachManagedRunProcessLifecycle({
|
|
162
|
-
child,
|
|
163
|
-
task,
|
|
164
|
-
nc: args.nc,
|
|
165
|
-
stopPendingSessionPoll,
|
|
166
|
-
getStoredRun: (runId) => getStoredRun(resolveWorkspaceRoot(), runId),
|
|
167
|
-
publishImmediateRunEvent: (eventArgs) => publishImmediateRunEvent({
|
|
168
|
-
...eventArgs,
|
|
169
|
-
buildRunEventsSubject: buildAgentRunEventsSubject,
|
|
170
|
-
}),
|
|
171
|
-
removeRunTask: (runId) => removeRunTask(resolveWorkspaceRoot(), runId),
|
|
172
|
-
releaseRunStartSlot: ({ runId, sessionId }) => releaseRunStartSlot({
|
|
173
|
-
workspaceRoot: resolveWorkspaceRoot(),
|
|
174
|
-
runId,
|
|
175
|
-
sessionId,
|
|
176
|
-
}),
|
|
177
|
-
codexAuthCleanup: prepared.codexAuthCleanup,
|
|
178
|
-
writeRunStatus,
|
|
179
|
-
formatTimestamp: formatLocalTimestamp,
|
|
180
|
-
});
|
|
181
|
-
void persistRunTask(resolveWorkspaceRoot(), task).catch(() => undefined);
|
|
182
|
-
publishImmediateRunEvent({ nc: args.nc, userId: task.userId, task, buildRunEventsSubject: buildAgentRunEventsSubject });
|
|
183
|
-
writeRunStatus(task.id, `started requestId=${args.requestId} cwd=${prepared.taskWorkspace}`);
|
|
184
|
-
if (!task.sessionId) {
|
|
185
|
-
void pollPendingSession();
|
|
186
|
-
}
|
|
187
|
-
return cloneRunTask(task);
|
|
188
|
-
}
|
|
189
48
|
function subscribeToSettingsRpc(args) {
|
|
190
49
|
const subject = buildAgentSettingsRpcSubject(args.userId, args.agentId);
|
|
191
50
|
args.jetstream.nc.subscribe(subject, {
|
|
@@ -200,6 +59,7 @@ function subscribeToSettingsRpc(args) {
|
|
|
200
59
|
nc: args.jetstream.nc,
|
|
201
60
|
agentId: args.agentId,
|
|
202
61
|
workspaceRoot: resolveWorkspaceRoot(),
|
|
62
|
+
onSettingsUpdated: () => args.codexAppServerManager.restart("settings updated"),
|
|
203
63
|
onError: writeAgentError,
|
|
204
64
|
});
|
|
205
65
|
},
|
|
@@ -225,140 +85,6 @@ function subscribeToGitRpc(args) {
|
|
|
225
85
|
});
|
|
226
86
|
writeAgentInfo(`git rpc subscribed subject=${subject}`);
|
|
227
87
|
}
|
|
228
|
-
async function handleRunRpcMessage(args) {
|
|
229
|
-
let requestId = "unknown";
|
|
230
|
-
let responseSubject = "";
|
|
231
|
-
try {
|
|
232
|
-
const payload = JSON.parse(runRpcCodec.decode(args.msg.data));
|
|
233
|
-
const request = normalizeRunRpcRequest({
|
|
234
|
-
request: payload,
|
|
235
|
-
agentId: args.agentId,
|
|
236
|
-
normalizeModel: normalizeCodexModel,
|
|
237
|
-
normalizeImagePaths: normalizeRunImagePaths,
|
|
238
|
-
normalizeEnvPatch,
|
|
239
|
-
normalizeCodexAuthBundle: normalizeShellRpcCodexAuthBundle,
|
|
240
|
-
});
|
|
241
|
-
requestId = request.requestId;
|
|
242
|
-
responseSubject = request.responseSubject;
|
|
243
|
-
if (request.action === "start") {
|
|
244
|
-
const runId = request.runId ?? requestId;
|
|
245
|
-
await claimRunStartSlot({
|
|
246
|
-
workspaceRoot: resolveWorkspaceRoot(),
|
|
247
|
-
runId,
|
|
248
|
-
sessionId: request.sessionId,
|
|
249
|
-
formatTimestamp: formatLocalTimestamp,
|
|
250
|
-
});
|
|
251
|
-
try {
|
|
252
|
-
const workspaceRoot = resolveWorkspaceRoot();
|
|
253
|
-
const localAgentSettings = await readAgentSettingsConfig({ workspaceRoot });
|
|
254
|
-
const customInstructions = await readAgentModelInstructions(workspaceRoot);
|
|
255
|
-
const validImagePaths = await filterValidRunImagePaths({
|
|
256
|
-
workspaceRoot,
|
|
257
|
-
imagePaths: request.imagePaths,
|
|
258
|
-
onInvalidImage: (imagePath, reason) => {
|
|
259
|
-
writeRunStatus(runId, `skipping invalid image path=${imagePath} reason=${reason}`);
|
|
260
|
-
},
|
|
261
|
-
});
|
|
262
|
-
const task = await startManagedRun({
|
|
263
|
-
requestId,
|
|
264
|
-
runId,
|
|
265
|
-
serverBaseUrl: args.serverBaseUrl,
|
|
266
|
-
userId: args.userId,
|
|
267
|
-
agentId: args.agentId,
|
|
268
|
-
nc: args.jetstream.nc,
|
|
269
|
-
sessionId: request.sessionId,
|
|
270
|
-
codexArgs: buildManagedCodexArgs({
|
|
271
|
-
prompt: request.prompt ?? "",
|
|
272
|
-
imagePaths: validImagePaths,
|
|
273
|
-
sessionId: request.sessionId,
|
|
274
|
-
model: request.model,
|
|
275
|
-
personality: localAgentSettings.general.personality,
|
|
276
|
-
modelInstructionsFile: customInstructions ? resolveAgentModelInstructionsFilePath(workspaceRoot) : null,
|
|
277
|
-
configOverrides: [
|
|
278
|
-
...buildDaemonMcpConfigArgs({
|
|
279
|
-
agentProjectDir: AGENT_PROJECT_DIR,
|
|
280
|
-
workspaceRoot,
|
|
281
|
-
}),
|
|
282
|
-
...buildDatabaseMcpConfigArgs({
|
|
283
|
-
agentProjectDir: AGENT_PROJECT_DIR,
|
|
284
|
-
workspaceRoot,
|
|
285
|
-
}),
|
|
286
|
-
"--config",
|
|
287
|
-
`features.computer_use=${localAgentSettings.codex.computerUseEnabled ? "true" : "false"}`,
|
|
288
|
-
"--config",
|
|
289
|
-
`features.browser_use=${localAgentSettings.codex.browserUseEnabled ? "true" : "false"}`,
|
|
290
|
-
],
|
|
291
|
-
}),
|
|
292
|
-
cwd: request.cwd,
|
|
293
|
-
runtimeEnvPatch: request.runtimeEnvPatch,
|
|
294
|
-
codexAuthBundle: request.codexAuthBundle,
|
|
295
|
-
agentToken: args.agentToken,
|
|
296
|
-
});
|
|
297
|
-
publishRunRpcResponse({ nc: args.jetstream.nc, responseSubject, payload: { requestId, ok: true, task } });
|
|
298
|
-
}
|
|
299
|
-
catch (error) {
|
|
300
|
-
await releaseRunStartSlot({
|
|
301
|
-
workspaceRoot: resolveWorkspaceRoot(),
|
|
302
|
-
runId,
|
|
303
|
-
sessionId: request.sessionId,
|
|
304
|
-
}).catch(() => undefined);
|
|
305
|
-
throw error;
|
|
306
|
-
}
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
await handleNonStartRunRpc({
|
|
310
|
-
request,
|
|
311
|
-
nc: args.jetstream.nc,
|
|
312
|
-
userId: args.userId,
|
|
313
|
-
agentId: args.agentId,
|
|
314
|
-
listPersistedRunTasks: async () => listPersistedRunTasks(resolveWorkspaceRoot()),
|
|
315
|
-
cloneRunTask,
|
|
316
|
-
getStoredRun: async (runId) => getStoredRun(resolveWorkspaceRoot(), runId),
|
|
317
|
-
persistRunTask: async (task) => persistRunTask(resolveWorkspaceRoot(), task),
|
|
318
|
-
publishImmediateRunEvent: (task) => publishImmediateRunEvent({
|
|
319
|
-
nc: args.jetstream.nc,
|
|
320
|
-
userId: task.userId,
|
|
321
|
-
task,
|
|
322
|
-
buildRunEventsSubject: buildAgentRunEventsSubject,
|
|
323
|
-
}),
|
|
324
|
-
writeRunStatus,
|
|
325
|
-
sendSignalToPid,
|
|
326
|
-
formatTimestamp: formatLocalTimestamp,
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
catch (error) {
|
|
330
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
331
|
-
if (responseSubject) {
|
|
332
|
-
publishRunRpcResponse({
|
|
333
|
-
nc: args.jetstream.nc,
|
|
334
|
-
responseSubject,
|
|
335
|
-
payload: { requestId, ok: false, error: message },
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
writeAgentError(`run rpc failed requestId=${requestId} error=${message}`);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
function subscribeToRunRpc(args) {
|
|
342
|
-
const subject = buildAgentRunRpcSubject(args.userId, args.agentId);
|
|
343
|
-
args.jetstream.nc.subscribe(subject, {
|
|
344
|
-
callback: (error, msg) => {
|
|
345
|
-
if (error) {
|
|
346
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
347
|
-
writeAgentError(`run rpc subscription error: ${message}`);
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
void handleRunRpcMessage({
|
|
351
|
-
msg,
|
|
352
|
-
jetstream: args.jetstream,
|
|
353
|
-
serverBaseUrl: args.serverBaseUrl,
|
|
354
|
-
userId: args.userId,
|
|
355
|
-
agentId: args.agentId,
|
|
356
|
-
agentToken: args.agentToken,
|
|
357
|
-
});
|
|
358
|
-
},
|
|
359
|
-
});
|
|
360
|
-
writeAgentInfo(`run rpc subscribed subject=${subject}`);
|
|
361
|
-
}
|
|
362
88
|
function subscribeToFsRpc(args) {
|
|
363
89
|
const subject = buildAgentFsRpcSubject(args.userId, args.agentId);
|
|
364
90
|
args.jetstream.nc.subscribe(subject, {
|
|
@@ -380,6 +106,20 @@ function subscribeToFsRpc(args) {
|
|
|
380
106
|
});
|
|
381
107
|
writeAgentInfo(`fs rpc subscribed subject=${subject}`);
|
|
382
108
|
}
|
|
109
|
+
function formatCodexAppNotificationParams(params) {
|
|
110
|
+
if (params === undefined) {
|
|
111
|
+
return "undefined";
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
return JSON.stringify(params);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return String(params);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function shouldForwardCodexAppNotification(method) {
|
|
121
|
+
return method !== "item/agentMessage/delta";
|
|
122
|
+
}
|
|
383
123
|
const runtimeEnvHelpers = createRuntimeEnvHelpers({
|
|
384
124
|
resolveWorkspaceRoot,
|
|
385
125
|
agentProjectDir: AGENT_PROJECT_DIR,
|
|
@@ -421,8 +161,6 @@ async function main() {
|
|
|
421
161
|
process.env.WORKSPACE = startupWorkspaceRoot;
|
|
422
162
|
process.env.CODEX_HOME = path.join(startupWorkspaceRoot, ".codex");
|
|
423
163
|
await mkdir(process.env.CODEX_HOME, { recursive: true }).catch(() => undefined);
|
|
424
|
-
// Preserve run state for processes that are still alive after an agent restart.
|
|
425
|
-
await pruneStaleRunsDir(resolveWorkspaceRoot());
|
|
426
164
|
const serverBaseUrlRaw = resolveArgOrEnv(args, ["server", "url"], ["DOER_AGENT_SERVER"], DEFAULT_SERVER_BASE_URL);
|
|
427
165
|
const requestedServerBaseUrl = serverBaseUrlRaw.replace(/\/$/, "");
|
|
428
166
|
const serverBaseUrl = resolveContainerReachableServerBaseUrl(requestedServerBaseUrl);
|
|
@@ -481,6 +219,26 @@ async function main() {
|
|
|
481
219
|
formatTimestamp: formatLocalTimestamp,
|
|
482
220
|
heartbeatAgentSession: heartbeatSession,
|
|
483
221
|
subscribeAll: () => {
|
|
222
|
+
const codexAppServerManager = createCodexAppServerManager({
|
|
223
|
+
workspaceRoot: resolveWorkspaceRoot(),
|
|
224
|
+
resolveCodexHomePath: runtimeEnvHelpers.resolveCodexHomePath,
|
|
225
|
+
readAgentSettingsConfig,
|
|
226
|
+
onLog: writeAgentInfo,
|
|
227
|
+
onNotification: (method, params) => {
|
|
228
|
+
if (!shouldForwardCodexAppNotification(method)) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
writeAgentInfo(`codex app-server notification method=${method} params=${formatCodexAppNotificationParams(params)}`);
|
|
232
|
+
const event = {
|
|
233
|
+
type: "codex.app.notification",
|
|
234
|
+
agentId: initialAgentId,
|
|
235
|
+
method,
|
|
236
|
+
params,
|
|
237
|
+
emittedAt: new Date().toISOString(),
|
|
238
|
+
};
|
|
239
|
+
jetstream.nc.publish(buildAgentCodexAppEventsSubject(userId, initialAgentId), codexAppEventCodec.encode(JSON.stringify(event)));
|
|
240
|
+
},
|
|
241
|
+
});
|
|
484
242
|
subscribeToFsRpc({
|
|
485
243
|
jetstream,
|
|
486
244
|
serverBaseUrl,
|
|
@@ -499,27 +257,12 @@ async function main() {
|
|
|
499
257
|
onInfo: writeAgentInfo,
|
|
500
258
|
onError: writeAgentError,
|
|
501
259
|
});
|
|
502
|
-
|
|
503
|
-
nc: jetstream.nc,
|
|
504
|
-
subject: buildAgentSessionRpcSubject(userId, initialAgentId),
|
|
505
|
-
agentId: initialAgentId,
|
|
506
|
-
workspaceRoot: resolveWorkspaceRoot(),
|
|
507
|
-
onInfo: writeAgentInfo,
|
|
508
|
-
onError: writeAgentError,
|
|
509
|
-
formatTimestamp: formatLocalTimestamp,
|
|
510
|
-
});
|
|
511
|
-
subscribeToCodexAuthRpc({
|
|
260
|
+
subscribeToCodexAppRpc({
|
|
512
261
|
nc: jetstream.nc,
|
|
513
|
-
subject:
|
|
262
|
+
subject: buildAgentCodexAppRpcSubject(userId, initialAgentId),
|
|
263
|
+
eventsSubject: buildAgentCodexAppEventsSubject(userId, initialAgentId),
|
|
514
264
|
agentId: initialAgentId,
|
|
515
|
-
|
|
516
|
-
buildLocalCodexCliCommand: localCodexCliTools.buildLocalCodexCliCommand,
|
|
517
|
-
resolveShellPath: runtimeEnvHelpers.resolveShellPath,
|
|
518
|
-
resolveCodexHomePath: runtimeEnvHelpers.resolveCodexHomePath,
|
|
519
|
-
runLocalCodexCli: localCodexCliTools.runLocalCodexCli,
|
|
520
|
-
runLocalCodexCliWithInput: localCodexCliTools.runLocalCodexCliWithInput,
|
|
521
|
-
sendSignalToTaskProcess,
|
|
522
|
-
stripAnsi: localCodexCliTools.stripAnsi,
|
|
265
|
+
manager: codexAppServerManager,
|
|
523
266
|
onInfo: writeAgentInfo,
|
|
524
267
|
onError: writeAgentError,
|
|
525
268
|
});
|
|
@@ -527,6 +270,7 @@ async function main() {
|
|
|
527
270
|
jetstream,
|
|
528
271
|
userId,
|
|
529
272
|
agentId: initialAgentId,
|
|
273
|
+
codexAppServerManager,
|
|
530
274
|
});
|
|
531
275
|
subscribeToGitRpc({
|
|
532
276
|
jetstream,
|
|
@@ -546,15 +290,7 @@ async function main() {
|
|
|
546
290
|
onInfo: writeAgentInfo,
|
|
547
291
|
onError: writeAgentError,
|
|
548
292
|
});
|
|
549
|
-
subscribeToRunRpc({
|
|
550
|
-
jetstream,
|
|
551
|
-
serverBaseUrl,
|
|
552
|
-
userId,
|
|
553
|
-
agentId: initialAgentId,
|
|
554
|
-
agentToken,
|
|
555
|
-
});
|
|
556
293
|
},
|
|
557
|
-
stopAllSessionWatchers: () => stopAllSessionWatchers({ onError: writeAgentError }),
|
|
558
294
|
onInfraError: writeAgentInfraError,
|
|
559
295
|
sleep,
|
|
560
296
|
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
export class CodexAppServerClient {
|
|
4
|
+
options;
|
|
5
|
+
child = null;
|
|
6
|
+
stdoutLines = null;
|
|
7
|
+
nextRequestId = 1;
|
|
8
|
+
startPromise = null;
|
|
9
|
+
pending = new Map();
|
|
10
|
+
constructor(options) {
|
|
11
|
+
this.options = options;
|
|
12
|
+
}
|
|
13
|
+
async request(method, params) {
|
|
14
|
+
await this.start();
|
|
15
|
+
return await this.requestStarted(method, params);
|
|
16
|
+
}
|
|
17
|
+
async notify(method, params) {
|
|
18
|
+
await this.start();
|
|
19
|
+
const child = this.child;
|
|
20
|
+
if (!child || child.killed) {
|
|
21
|
+
throw new Error("Codex app-server is not running");
|
|
22
|
+
}
|
|
23
|
+
const payload = params === undefined ? { method } : { method, params };
|
|
24
|
+
child.stdin.write(`${JSON.stringify(payload)}\n`, "utf8");
|
|
25
|
+
}
|
|
26
|
+
async stop() {
|
|
27
|
+
const child = this.child;
|
|
28
|
+
if (!child || child.killed) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
child.kill("SIGTERM");
|
|
32
|
+
}
|
|
33
|
+
async start() {
|
|
34
|
+
if (this.child && !this.child.killed) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (this.startPromise) {
|
|
38
|
+
return await this.startPromise;
|
|
39
|
+
}
|
|
40
|
+
this.startPromise = this.startInner();
|
|
41
|
+
try {
|
|
42
|
+
await this.startPromise;
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
this.startPromise = null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async startInner() {
|
|
49
|
+
this.child = spawn("codex", this.options.args, {
|
|
50
|
+
cwd: this.options.cwd,
|
|
51
|
+
env: this.options.env,
|
|
52
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
53
|
+
});
|
|
54
|
+
this.child.stdout.setEncoding("utf8");
|
|
55
|
+
this.child.stderr.setEncoding("utf8");
|
|
56
|
+
this.stdoutLines = createInterface({ input: this.child.stdout });
|
|
57
|
+
this.stdoutLines.on("line", (line) => this.handleLine(line));
|
|
58
|
+
this.child.stderr.on("data", (chunk) => {
|
|
59
|
+
const message = chunk.trim();
|
|
60
|
+
if (message) {
|
|
61
|
+
this.options.onLog?.(`[codex-app-server] ${message}`);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
this.child.once("exit", (code, signal) => {
|
|
65
|
+
this.options.onLog?.(`[codex-app-server] exited code=${code ?? "null"} signal=${signal ?? "null"}`);
|
|
66
|
+
this.rejectPending(new Error("Codex app-server exited"));
|
|
67
|
+
this.stdoutLines?.close();
|
|
68
|
+
this.stdoutLines = null;
|
|
69
|
+
this.child = null;
|
|
70
|
+
});
|
|
71
|
+
await this.requestStarted("initialize", {
|
|
72
|
+
clientInfo: {
|
|
73
|
+
name: "doer-agent",
|
|
74
|
+
title: "Doer Agent",
|
|
75
|
+
version: "0.5.9",
|
|
76
|
+
},
|
|
77
|
+
capabilities: {
|
|
78
|
+
experimentalApi: true,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
await this.notify("initialized");
|
|
82
|
+
}
|
|
83
|
+
async requestStarted(method, params) {
|
|
84
|
+
const child = this.child;
|
|
85
|
+
if (!child || child.killed) {
|
|
86
|
+
throw new Error("Codex app-server is not running");
|
|
87
|
+
}
|
|
88
|
+
const id = this.nextRequestId++;
|
|
89
|
+
const payload = params === undefined ? { id, method } : { id, method, params };
|
|
90
|
+
const timeoutMs = this.options.requestTimeoutMs ?? 30_000;
|
|
91
|
+
return await new Promise((resolve, reject) => {
|
|
92
|
+
const timer = setTimeout(() => {
|
|
93
|
+
this.pending.delete(id);
|
|
94
|
+
reject(new Error(`Timed out waiting for Codex app-server response: ${method}`));
|
|
95
|
+
}, timeoutMs);
|
|
96
|
+
this.pending.set(id, { resolve, reject, timer });
|
|
97
|
+
child.stdin.write(`${JSON.stringify(payload)}\n`, "utf8", (error) => {
|
|
98
|
+
if (!error) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
clearTimeout(timer);
|
|
102
|
+
this.pending.delete(id);
|
|
103
|
+
reject(error);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
handleLine(line) {
|
|
108
|
+
let message;
|
|
109
|
+
try {
|
|
110
|
+
message = JSON.parse(line);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
this.options.onLog?.("[codex-app-server] ignored malformed JSON line");
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (!message || typeof message !== "object" || Array.isArray(message)) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const record = message;
|
|
120
|
+
if (record.id !== undefined) {
|
|
121
|
+
const id = Number(record.id);
|
|
122
|
+
const pending = Number.isInteger(id) ? this.pending.get(id) : null;
|
|
123
|
+
if (!pending) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
this.pending.delete(id);
|
|
127
|
+
clearTimeout(pending.timer);
|
|
128
|
+
if (record.error && typeof record.error === "object" && !Array.isArray(record.error)) {
|
|
129
|
+
const error = record.error;
|
|
130
|
+
pending.reject(new Error(typeof error.message === "string" ? error.message : "Codex app-server request failed"));
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
pending.resolve(record.result);
|
|
134
|
+
}
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (typeof record.method === "string") {
|
|
138
|
+
this.options.onNotification?.(record.method, record.params);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
rejectPending(error) {
|
|
142
|
+
for (const [id, pending] of this.pending) {
|
|
143
|
+
this.pending.delete(id);
|
|
144
|
+
clearTimeout(pending.timer);
|
|
145
|
+
pending.reject(error);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|