doer-agent 0.5.9 → 0.6.1
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 +48 -311
- package/dist/codex-app-server-client.js +148 -0
- package/dist/codex-app-server-manager.js +117 -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,27 @@ async function main() {
|
|
|
481
219
|
formatTimestamp: formatLocalTimestamp,
|
|
482
220
|
heartbeatAgentSession: heartbeatSession,
|
|
483
221
|
subscribeAll: () => {
|
|
222
|
+
const codexAppServerManager = createCodexAppServerManager({
|
|
223
|
+
workspaceRoot: resolveWorkspaceRoot(),
|
|
224
|
+
agentProjectDir: AGENT_PROJECT_DIR,
|
|
225
|
+
resolveCodexHomePath: runtimeEnvHelpers.resolveCodexHomePath,
|
|
226
|
+
readAgentSettingsConfig,
|
|
227
|
+
onLog: writeAgentInfo,
|
|
228
|
+
onNotification: (method, params) => {
|
|
229
|
+
if (!shouldForwardCodexAppNotification(method)) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
writeAgentInfo(`codex app-server notification method=${method} params=${formatCodexAppNotificationParams(params)}`);
|
|
233
|
+
const event = {
|
|
234
|
+
type: "codex.app.notification",
|
|
235
|
+
agentId: initialAgentId,
|
|
236
|
+
method,
|
|
237
|
+
params,
|
|
238
|
+
emittedAt: new Date().toISOString(),
|
|
239
|
+
};
|
|
240
|
+
jetstream.nc.publish(buildAgentCodexAppEventsSubject(userId, initialAgentId), codexAppEventCodec.encode(JSON.stringify(event)));
|
|
241
|
+
},
|
|
242
|
+
});
|
|
484
243
|
subscribeToFsRpc({
|
|
485
244
|
jetstream,
|
|
486
245
|
serverBaseUrl,
|
|
@@ -499,27 +258,12 @@ async function main() {
|
|
|
499
258
|
onInfo: writeAgentInfo,
|
|
500
259
|
onError: writeAgentError,
|
|
501
260
|
});
|
|
502
|
-
|
|
261
|
+
subscribeToCodexAppRpc({
|
|
503
262
|
nc: jetstream.nc,
|
|
504
|
-
subject:
|
|
263
|
+
subject: buildAgentCodexAppRpcSubject(userId, initialAgentId),
|
|
264
|
+
eventsSubject: buildAgentCodexAppEventsSubject(userId, initialAgentId),
|
|
505
265
|
agentId: initialAgentId,
|
|
506
|
-
|
|
507
|
-
onInfo: writeAgentInfo,
|
|
508
|
-
onError: writeAgentError,
|
|
509
|
-
formatTimestamp: formatLocalTimestamp,
|
|
510
|
-
});
|
|
511
|
-
subscribeToCodexAuthRpc({
|
|
512
|
-
nc: jetstream.nc,
|
|
513
|
-
subject: buildAgentCodexAuthRpcSubject(userId, initialAgentId),
|
|
514
|
-
agentId: initialAgentId,
|
|
515
|
-
workspaceRoot: resolveWorkspaceRoot(),
|
|
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,
|
|
266
|
+
manager: codexAppServerManager,
|
|
523
267
|
onInfo: writeAgentInfo,
|
|
524
268
|
onError: writeAgentError,
|
|
525
269
|
});
|
|
@@ -527,6 +271,7 @@ async function main() {
|
|
|
527
271
|
jetstream,
|
|
528
272
|
userId,
|
|
529
273
|
agentId: initialAgentId,
|
|
274
|
+
codexAppServerManager,
|
|
530
275
|
});
|
|
531
276
|
subscribeToGitRpc({
|
|
532
277
|
jetstream,
|
|
@@ -546,15 +291,7 @@ async function main() {
|
|
|
546
291
|
onInfo: writeAgentInfo,
|
|
547
292
|
onError: writeAgentError,
|
|
548
293
|
});
|
|
549
|
-
subscribeToRunRpc({
|
|
550
|
-
jetstream,
|
|
551
|
-
serverBaseUrl,
|
|
552
|
-
userId,
|
|
553
|
-
agentId: initialAgentId,
|
|
554
|
-
agentToken,
|
|
555
|
-
});
|
|
556
294
|
},
|
|
557
|
-
stopAllSessionWatchers: () => stopAllSessionWatchers({ onError: writeAgentError }),
|
|
558
295
|
onInfraError: writeAgentInfraError,
|
|
559
296
|
sleep,
|
|
560
297
|
});
|
|
@@ -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
|
+
}
|