doer-agent 0.4.6 → 0.4.8

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.
@@ -1,4 +1,5 @@
1
1
  import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
2
3
  export function sanitizeUserId(userId) {
3
4
  const normalized = userId.trim().replace(/[^a-zA-Z0-9_-]/g, "_");
4
5
  return normalized.length > 0 ? normalized : "anonymous";
@@ -27,6 +28,9 @@ export function buildAgentSkillRpcSubject(userId, agentId) {
27
28
  export function buildAgentFsRpcSubject(userId, agentId) {
28
29
  return `doer.agent.fs.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
29
30
  }
31
+ export function buildAgentDaemonRpcSubject(userId, agentId) {
32
+ return `doer.agent.daemon.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
33
+ }
30
34
  export function parseBootstrapTaskConfig(value) {
31
35
  if (!value || typeof value !== "object" || Array.isArray(value)) {
32
36
  return null;
@@ -93,6 +97,93 @@ export function normalizeRunImagePaths(value) {
93
97
  }
94
98
  return out;
95
99
  }
100
+ const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
101
+ const crc32Table = (() => {
102
+ const table = new Uint32Array(256);
103
+ for (let index = 0; index < 256; index += 1) {
104
+ let crc = index;
105
+ for (let bit = 0; bit < 8; bit += 1) {
106
+ crc = (crc & 1) !== 0 ? 0xedb88320 ^ (crc >>> 1) : crc >>> 1;
107
+ }
108
+ table[index] = crc >>> 0;
109
+ }
110
+ return table;
111
+ })();
112
+ function crc32(parts) {
113
+ let crc = 0xffffffff;
114
+ for (const part of parts) {
115
+ for (let index = 0; index < part.length; index += 1) {
116
+ crc = crc32Table[(crc ^ part[index]) & 0xff] ^ (crc >>> 8);
117
+ }
118
+ }
119
+ return (crc ^ 0xffffffff) >>> 0;
120
+ }
121
+ function validatePngBytes(bytes) {
122
+ if (bytes.length < PNG_SIGNATURE.length || !bytes.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)) {
123
+ return "missing PNG signature";
124
+ }
125
+ let offset = PNG_SIGNATURE.length;
126
+ let sawIend = false;
127
+ while (offset < bytes.length) {
128
+ if (offset + 12 > bytes.length) {
129
+ return "truncated PNG chunk";
130
+ }
131
+ const chunkLength = bytes.readUInt32BE(offset);
132
+ const typeStart = offset + 4;
133
+ const dataStart = offset + 8;
134
+ const dataEnd = dataStart + chunkLength;
135
+ const crcOffset = dataEnd;
136
+ const nextOffset = crcOffset + 4;
137
+ if (dataEnd > bytes.length || nextOffset > bytes.length) {
138
+ return "truncated PNG chunk payload";
139
+ }
140
+ const type = bytes.subarray(typeStart, dataStart);
141
+ const expectedCrc = bytes.readUInt32BE(crcOffset);
142
+ const actualCrc = crc32([type, bytes.subarray(dataStart, dataEnd)]);
143
+ if (expectedCrc !== actualCrc) {
144
+ const chunkName = type.toString("ascii");
145
+ return `PNG CRC mismatch in ${chunkName} chunk`;
146
+ }
147
+ if (type.equals(Buffer.from("IEND"))) {
148
+ sawIend = true;
149
+ if (nextOffset !== bytes.length) {
150
+ return "unexpected trailing bytes after PNG IEND";
151
+ }
152
+ break;
153
+ }
154
+ offset = nextOffset;
155
+ }
156
+ return sawIend ? null : "missing PNG IEND chunk";
157
+ }
158
+ export function validateImageBytes(filePath, bytes) {
159
+ const ext = path.extname(filePath).toLowerCase();
160
+ if (ext === ".png") {
161
+ return validatePngBytes(bytes);
162
+ }
163
+ return null;
164
+ }
165
+ export async function filterValidRunImagePaths(args) {
166
+ const valid = [];
167
+ for (const imagePath of args.imagePaths) {
168
+ const absPath = path.isAbsolute(imagePath) ? imagePath : path.resolve(args.workspaceRoot, imagePath);
169
+ let bytes;
170
+ try {
171
+ bytes = await readFile(absPath);
172
+ }
173
+ catch (error) {
174
+ const reason = error instanceof Error ? error.message : "failed to read image";
175
+ args.onInvalidImage?.(imagePath, reason);
176
+ continue;
177
+ }
178
+ const validationError = validateImageBytes(absPath, bytes);
179
+ if (validationError) {
180
+ args.onInvalidImage?.(imagePath, validationError);
181
+ continue;
182
+ }
183
+ valid.push(imagePath);
184
+ }
185
+ return valid;
186
+ }
96
187
  export function fatalExit(message, error, writeAgentError) {
97
188
  const detail = error instanceof Error ? error.message : typeof error === "string" ? error : error ? String(error) : "";
98
189
  const full = detail ? `${message}: ${detail}` : message;
@@ -102,17 +193,6 @@ export function fatalExit(message, error, writeAgentError) {
102
193
  export function sleep(ms) {
103
194
  return new Promise((resolve) => setTimeout(resolve, ms));
104
195
  }
105
- export function writeTaskStream(taskId, stream, chunk) {
106
- const target = stream === "stdout" ? process.stdout : process.stderr;
107
- const lines = chunk.replace(/\r/g, "\n").split("\n");
108
- for (let i = 0; i < lines.length; i += 1) {
109
- const line = lines[i];
110
- if (line.length === 0 && i === lines.length - 1) {
111
- continue;
112
- }
113
- target.write(`[doer-agent][task=${taskId}][${stream}] ${line}\n`);
114
- }
115
- }
116
196
  export function writeTaskUpload(taskId, message) {
117
197
  process.stdout.write(`[doer-agent][task=${taskId}][upload] ${message}\n`);
118
198
  }
@@ -127,9 +207,6 @@ export function writeRpcStream(requestId, stream, chunk) {
127
207
  target.write(`[doer-agent][rpc=${requestId}][${stream}] ${line}\n`);
128
208
  }
129
209
  }
130
- export function writeRpcStatus(requestId, message) {
131
- process.stdout.write(`[doer-agent][rpc=${requestId}][status] ${message}\n`);
132
- }
133
210
  export function writeRunStatus(runId, message) {
134
211
  process.stdout.write(`[doer-agent][run=${runId}][status] ${message}\n`);
135
212
  }
@@ -0,0 +1,38 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
6
+ import { filterValidRunImagePaths } from "./agent-runtime-utils.js";
7
+ const invalidTinyPngBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aK1cAAAAASUVORK5CYII=";
8
+ function buildValidTinyPng() {
9
+ const bytes = Buffer.from(invalidTinyPngBase64, "base64");
10
+ bytes.writeUInt32BE(0xefa2a75b, 0x34);
11
+ return bytes;
12
+ }
13
+ test("filterValidRunImagePaths drops PNGs with CRC mismatches", async () => {
14
+ const workspaceRoot = await mkdtemp(path.join(os.tmpdir(), "doer-agent-image-filter-"));
15
+ try {
16
+ const invalidPngPath = path.join(workspaceRoot, "bad.png");
17
+ const validPngPath = path.join(workspaceRoot, "good.png");
18
+ const validJpgPath = path.join(workspaceRoot, "photo.jpg");
19
+ await writeFile(invalidPngPath, Buffer.from(invalidTinyPngBase64, "base64"));
20
+ await writeFile(validPngPath, buildValidTinyPng());
21
+ await writeFile(validJpgPath, Buffer.from([0xff, 0xd8, 0xff, 0xd9]));
22
+ const invalid = [];
23
+ const result = await filterValidRunImagePaths({
24
+ workspaceRoot,
25
+ imagePaths: ["bad.png", "good.png", "photo.jpg"],
26
+ onInvalidImage: (imagePath, reason) => {
27
+ invalid.push({ imagePath, reason });
28
+ },
29
+ });
30
+ assert.deepEqual(result, ["good.png", "photo.jpg"]);
31
+ assert.equal(invalid.length, 1);
32
+ assert.equal(invalid[0]?.imagePath, "bad.png");
33
+ assert.match(invalid[0]?.reason ?? "", /CRC mismatch/i);
34
+ }
35
+ finally {
36
+ await rm(workspaceRoot, { recursive: true, force: true });
37
+ }
38
+ });
@@ -1,6 +1,3 @@
1
- import { spawn } from "node:child_process";
2
- import { mkdir } from "node:fs/promises";
3
- import path from "node:path";
4
1
  export function sendSignalToTaskProcess(child, signal) {
5
2
  if (process.platform !== "win32" && typeof child.pid === "number") {
6
3
  try {
@@ -30,17 +27,8 @@ export function sendSignalToPid(pid, signal) {
30
27
  }
31
28
  process.kill(pid, signal);
32
29
  }
33
- async function checkCancelRequested(args) {
34
- const query = new URLSearchParams({
35
- userId: args.userId,
36
- agentToken: args.agentToken,
37
- });
38
- const response = await args.getJson(`${args.serverBaseUrl}/api/agent/tasks/${encodeURIComponent(args.taskId)}/events?${query.toString()}`);
39
- return Boolean(response.task?.cancelRequested);
40
- }
41
30
  async function syncCodexAuthState(args) {
42
31
  const envPatch = {};
43
- const synced = false;
44
32
  if (args.authMode === "api_key" && args.apiKey) {
45
33
  envPatch.OPENAI_API_KEY = args.apiKey;
46
34
  }
@@ -54,19 +42,10 @@ async function syncCodexAuthState(args) {
54
42
  codexAuthHasAuthJson: Boolean(args.authJson),
55
43
  codexAuthIssuedAt: args.issuedAt ?? null,
56
44
  codexAuthExpiresAt: args.expiresAt ?? null,
57
- codexAuthSynced: synced,
45
+ codexAuthSynced: false,
58
46
  },
59
47
  };
60
48
  }
61
- async function prepareTaskCodexAuth(args) {
62
- void args;
63
- return await syncCodexAuthState({
64
- source: "agent_local",
65
- authJson: null,
66
- issuedAt: null,
67
- expiresAt: null,
68
- });
69
- }
70
49
  export async function prepareCodexAuthBundle(bundle) {
71
50
  if (!bundle) {
72
51
  return null;
@@ -80,196 +59,3 @@ export async function prepareCodexAuthBundle(bundle) {
80
59
  expiresAt: bundle.expiresAt ?? null,
81
60
  });
82
61
  }
83
- export async function runTask(args) {
84
- args.setActiveTaskLogContext({
85
- jetstream: args.jetstream,
86
- serverBaseUrl: args.serverBaseUrl,
87
- taskId: args.taskId,
88
- userId: args.userId,
89
- });
90
- const shellPath = args.resolveShellPath();
91
- const taskWorkspace = args.resolveTaskWorkspace(args.cwd);
92
- const codexHome = args.resolveCodexHomePath();
93
- await mkdir(codexHome, { recursive: true });
94
- const runtimeConfig = await args.prepareTaskRuntimeConfig({
95
- serverBaseUrl: args.serverBaseUrl,
96
- taskId: args.taskId,
97
- userId: args.userId,
98
- agentToken: args.agentToken,
99
- });
100
- const codexAuth = await prepareTaskCodexAuth({
101
- serverBaseUrl: args.serverBaseUrl,
102
- taskId: args.taskId,
103
- userId: args.userId,
104
- agentToken: args.agentToken,
105
- });
106
- const localAgentSettings = await args.readAgentSettingsConfig({ workspaceRoot: args.resolveWorkspaceRoot() });
107
- const baseTaskEnvPatch = {
108
- CODEX_HOME: codexHome,
109
- ...args.buildAgentSettingsEnvPatch(localAgentSettings),
110
- ...(runtimeConfig?.envPatch ?? {}),
111
- ...(codexAuth?.envPatch ?? {}),
112
- WORKSPACE: taskWorkspace,
113
- };
114
- const taskGitEnv = await args.prepareTaskGitEnv({
115
- cwd: taskWorkspace,
116
- baseEnvPatch: baseTaskEnvPatch,
117
- });
118
- await args.recordAgentEvent({
119
- jetstream: args.jetstream,
120
- serverBaseUrl: args.serverBaseUrl,
121
- taskId: args.taskId,
122
- userId: args.userId,
123
- type: "meta",
124
- seq: args.reserveNextEventSeq(args.taskId),
125
- payload: {
126
- host: process.platform,
127
- pid: process.pid,
128
- startedAt: args.formatLocalTimestamp(),
129
- command: args.command,
130
- cwd: taskWorkspace,
131
- requestedCwd: args.cwd,
132
- shell: shellPath,
133
- ...(runtimeConfig?.meta ?? { runtimeConfigSynced: false }),
134
- ...(codexAuth?.meta ?? { codexAuthSynced: false }),
135
- ...(taskGitEnv.meta ?? {}),
136
- },
137
- });
138
- try {
139
- let terminationReason = null;
140
- let cancelStage1Timer = null;
141
- let cancelStage2Timer = null;
142
- let stopCancelPolling = false;
143
- let cancelSignalSent = false;
144
- const runtimeBinPath = path.join(args.agentProjectDir, "runtime/bin");
145
- const taskPath = [runtimeBinPath, process.env.PATH || ""].filter(Boolean).join(path.delimiter);
146
- const child = spawn(args.command, {
147
- cwd: taskWorkspace,
148
- shell: shellPath,
149
- detached: process.platform !== "win32",
150
- env: {
151
- ...process.env,
152
- ...baseTaskEnvPatch,
153
- ...taskGitEnv.envPatch,
154
- PATH: taskPath,
155
- DOER_AGENT_TOKEN: args.agentToken,
156
- },
157
- stdio: ["ignore", "pipe", "pipe"],
158
- });
159
- child.stdout?.setEncoding("utf8");
160
- child.stderr?.setEncoding("utf8");
161
- const requestCancel = () => {
162
- if (cancelSignalSent || terminationReason === "cancel") {
163
- return;
164
- }
165
- cancelSignalSent = true;
166
- terminationReason = "cancel";
167
- sendSignalToTaskProcess(child, "SIGINT");
168
- cancelStage1Timer = setTimeout(() => {
169
- sendSignalToTaskProcess(child, "SIGTERM");
170
- }, 1200);
171
- cancelStage1Timer.unref?.();
172
- cancelStage2Timer = setTimeout(() => {
173
- sendSignalToTaskProcess(child, "SIGKILL");
174
- }, 3500);
175
- cancelStage2Timer.unref?.();
176
- };
177
- child.stdout?.on("data", (chunk) => {
178
- args.writeTaskStream(args.taskId, "stdout", chunk);
179
- const seq = args.reserveNextEventSeq(args.taskId);
180
- args.persistEventOrFatal({
181
- jetstream: args.jetstream,
182
- serverBaseUrl: args.serverBaseUrl,
183
- taskId: args.taskId,
184
- userId: args.userId,
185
- type: "stdout",
186
- seq,
187
- payload: { chunk, at: args.formatLocalTimestamp() },
188
- context: "stdout persist failed",
189
- });
190
- });
191
- child.stderr?.on("data", (chunk) => {
192
- args.writeTaskStream(args.taskId, "stderr", chunk);
193
- const seq = args.reserveNextEventSeq(args.taskId);
194
- args.persistEventOrFatal({
195
- jetstream: args.jetstream,
196
- serverBaseUrl: args.serverBaseUrl,
197
- taskId: args.taskId,
198
- userId: args.userId,
199
- type: "stderr",
200
- seq,
201
- payload: { chunk, at: args.formatLocalTimestamp() },
202
- context: "stderr persist failed",
203
- });
204
- });
205
- const cancelPoller = (async () => {
206
- while (!stopCancelPolling) {
207
- await args.sleep(5000);
208
- if (stopCancelPolling || terminationReason === "cancel") {
209
- continue;
210
- }
211
- const cancelRequested = await checkCancelRequested({
212
- serverBaseUrl: args.serverBaseUrl,
213
- taskId: args.taskId,
214
- userId: args.userId,
215
- agentToken: args.agentToken,
216
- getJson: args.getJson,
217
- }).catch(() => false);
218
- if (!cancelRequested) {
219
- continue;
220
- }
221
- requestCancel();
222
- }
223
- })();
224
- const result = await new Promise((resolve, reject) => {
225
- child.once("error", reject);
226
- child.once("close", (code, signal) => {
227
- resolve({ code, signal });
228
- });
229
- }).finally(() => {
230
- stopCancelPolling = true;
231
- if (cancelStage1Timer) {
232
- clearTimeout(cancelStage1Timer);
233
- }
234
- if (cancelStage2Timer) {
235
- clearTimeout(cancelStage2Timer);
236
- }
237
- });
238
- await cancelPoller.catch(() => undefined);
239
- const canceled = await checkCancelRequested({
240
- serverBaseUrl: args.serverBaseUrl,
241
- taskId: args.taskId,
242
- userId: args.userId,
243
- agentToken: args.agentToken,
244
- getJson: args.getJson,
245
- }).catch(() => false);
246
- const status = canceled || terminationReason === "cancel"
247
- ? "canceled"
248
- : (result.code ?? 1) === 0
249
- ? "completed"
250
- : "failed";
251
- const statusPayload = {
252
- status,
253
- exitCode: typeof result.code === "number" ? result.code : null,
254
- signal: result.signal,
255
- finishedAt: args.formatLocalTimestamp(),
256
- error: status === "failed"
257
- ? `Command exited with code ${result.code ?? "null"}`
258
- : null,
259
- };
260
- await args.recordAgentEvent({
261
- jetstream: args.jetstream,
262
- serverBaseUrl: args.serverBaseUrl,
263
- taskId: args.taskId,
264
- userId: args.userId,
265
- type: "status",
266
- seq: args.reserveNextEventSeq(args.taskId),
267
- payload: statusPayload,
268
- });
269
- args.writeAgentInfo(`task=${args.taskId} status=${status} exitCode=${typeof result.code === "number" ? result.code : "null"} signal=${result.signal ?? "null"}`);
270
- }
271
- finally {
272
- args.clearActiveTaskLogContext(args.taskId);
273
- await codexAuth?.cleanup().catch(() => undefined);
274
- }
275
- }
package/dist/agent.js CHANGED
@@ -6,17 +6,18 @@ import { buildAgentSettingsEnvPatch, readAgentModelInstructions, readAgentSettin
6
6
  import { handleFsRpcMessage } from "./agent-fs-rpc.js";
7
7
  import { handleGitRpcMessage } from "./agent-git-rpc.js";
8
8
  import { subscribeToCodexAuthRpc } from "./agent-codex-auth-rpc.js";
9
- import { buildManagedCodexArgs, createLocalCodexCliTools, normalizeCodexModel, normalizeShellRpcCodexAuthBundle, spawnManagedCodexCommand, } from "./agent-codex-cli.js";
9
+ import { subscribeToDaemonRpc } from "./agent-daemon-rpc.js";
10
+ import { buildDaemonMcpConfigArgs, buildManagedCodexArgs, createLocalCodexCliTools, normalizeCodexModel, normalizeShellRpcCodexAuthBundle, spawnManagedCodexCommand, } from "./agent-codex-cli.js";
10
11
  import { connectBootstrapWithRetry } from "./agent-jetstream.js";
11
12
  import { prepareCommandExecution } from "./agent-run-execution.js";
12
13
  import { attachManagedRunProcessLifecycle, createPendingRunSessionTracker } from "./agent-run-lifecycle.js";
13
- import { claimRunStartSlot, cloneRunTask, getStoredRun, listPersistedRunTasks, persistRunTask, publishImmediateRunEvent, releaseRunStartSlot, resetRunsDir, removeRunTask, updateRunStartSlotSession, } from "./agent-run-state.js";
14
+ import { claimRunStartSlot, cloneRunTask, getStoredRun, listPersistedRunTasks, persistRunTask, publishImmediateRunEvent, pruneStaleRunsDir, releaseRunStartSlot, removeRunTask, updateRunStartSlotSession, } from "./agent-run-state.js";
14
15
  import { runConnectedAgentSession } from "./agent-session-loop.js";
15
16
  import { subscribeToSkillRpc } from "./agent-skill-rpc.js";
16
17
  import { prepareCodexAuthBundle, sendSignalToPid, sendSignalToTaskProcess, } from "./agent-task-execution.js";
17
18
  import { collectSessionJsonlFiles, detectPendingRunSession, findSessionFilePathBySessionId, stopAllSessionWatchers, subscribeToSessionRpc, } from "./agent-session-rpc.js";
18
19
  import { handleNonStartRunRpc, normalizeRunRpcRequest, publishRunRpcResponse, } from "./agent-run-rpc.js";
19
- import { buildAgentCodexAuthRpcSubject, buildAgentFsRpcSubject, buildAgentGitRpcSubject, buildAgentRunEventsSubject, buildAgentRunRpcSubject, buildAgentSessionRpcSubject, buildAgentSettingsRpcSubject, buildAgentSkillRpcSubject, formatLocalTimestamp, normalizeEnvPatch, normalizeRunImagePaths, parseArgs, resolveAgentVersion, resolveArgOrEnv, resolveContainerReachableServerBaseUrl, sanitizeUserId, sleep, writeRunStatus, writeRunStream, } from "./agent-runtime-utils.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";
20
21
  import { createRuntimeEnvHelpers } from "./agent-runtime-env.js";
21
22
  import { createEventPersistenceHelpers, heartbeatAgentSession, postJson, } from "./agent-runtime-io.js";
22
23
  import { handleSettingsRpcMessage } from "./agent-settings-rpc.js";
@@ -32,30 +33,6 @@ function resolveWorkspaceRoot() {
32
33
  return workspaceRootOverride ?? (process.env.WORKSPACE?.trim() || process.cwd());
33
34
  }
34
35
  const runRpcCodec = StringCodec();
35
- async function prepareTaskRuntimeConfig(args) {
36
- const bundle = await postJson(`${args.serverBaseUrl}/api/agent/tasks/${encodeURIComponent(args.taskId)}/runtime-config`, {
37
- userId: args.userId,
38
- agentToken: args.agentToken,
39
- }).catch((error) => {
40
- const message = error instanceof Error ? error.message : String(error);
41
- writeAgentError(`task=${args.taskId} runtime config sync skipped: ${message}`);
42
- return null;
43
- });
44
- if (!bundle) {
45
- return null;
46
- }
47
- const envPatch = normalizeEnvPatch(bundle.envPatch);
48
- return {
49
- envPatch,
50
- meta: {
51
- runtimeConfigIssuedAt: bundle.issuedAt ?? null,
52
- runtimeConfigExpiresAt: bundle.expiresAt ?? null,
53
- runtimeConfigVarCount: Object.keys(envPatch).length,
54
- runtimeConfigSynced: true,
55
- ...(bundle.meta && typeof bundle.meta === "object" && !Array.isArray(bundle.meta) ? bundle.meta : {}),
56
- },
57
- };
58
- }
59
36
  function writeAgentInfo(message) {
60
37
  process.stdout.write(`[doer-agent] ${message}\n`);
61
38
  eventPersistenceHelpers.emitAgentMetaLog("info", message);
@@ -275,6 +252,13 @@ async function handleRunRpcMessage(args) {
275
252
  const workspaceRoot = resolveWorkspaceRoot();
276
253
  const localAgentSettings = await readAgentSettingsConfig({ workspaceRoot });
277
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
+ });
278
262
  const task = await startManagedRun({
279
263
  requestId,
280
264
  runId,
@@ -285,11 +269,15 @@ async function handleRunRpcMessage(args) {
285
269
  sessionId: request.sessionId,
286
270
  codexArgs: buildManagedCodexArgs({
287
271
  prompt: request.prompt ?? "",
288
- imagePaths: request.imagePaths,
272
+ imagePaths: validImagePaths,
289
273
  sessionId: request.sessionId,
290
274
  model: request.model,
291
275
  personality: localAgentSettings.general.personality,
292
276
  modelInstructionsFile: customInstructions ? resolveAgentModelInstructionsFilePath(workspaceRoot) : null,
277
+ configOverrides: buildDaemonMcpConfigArgs({
278
+ agentProjectDir: AGENT_PROJECT_DIR,
279
+ workspaceRoot,
280
+ }),
293
281
  }),
294
282
  cwd: request.cwd,
295
283
  runtimeEnvPatch: request.runtimeEnvPatch,
@@ -423,7 +411,8 @@ async function main() {
423
411
  process.env.WORKSPACE = startupWorkspaceRoot;
424
412
  process.env.CODEX_HOME = path.join(startupWorkspaceRoot, ".codex");
425
413
  await mkdir(process.env.CODEX_HOME, { recursive: true }).catch(() => undefined);
426
- await resetRunsDir(resolveWorkspaceRoot());
414
+ // Preserve run state for processes that are still alive after an agent restart.
415
+ await pruneStaleRunsDir(resolveWorkspaceRoot());
427
416
  const serverBaseUrlRaw = resolveArgOrEnv(args, ["server", "url"], ["DOER_AGENT_SERVER"], DEFAULT_SERVER_BASE_URL);
428
417
  const requestedServerBaseUrl = serverBaseUrlRaw.replace(/\/$/, "");
429
418
  const serverBaseUrl = resolveContainerReachableServerBaseUrl(requestedServerBaseUrl);
@@ -489,6 +478,17 @@ async function main() {
489
478
  agentId: initialAgentId,
490
479
  agentToken,
491
480
  });
481
+ subscribeToDaemonRpc({
482
+ nc: jetstream.nc,
483
+ subject: buildAgentDaemonRpcSubject(userId, initialAgentId),
484
+ workspaceRoot: resolveWorkspaceRoot(),
485
+ agentProjectDir: AGENT_PROJECT_DIR,
486
+ resolveShellPath: runtimeEnvHelpers.resolveShellPath,
487
+ resolveTaskWorkspace: runtimeEnvHelpers.resolveTaskWorkspace,
488
+ readAgentSettingsConfig,
489
+ onInfo: writeAgentInfo,
490
+ onError: writeAgentError,
491
+ });
492
492
  subscribeToSessionRpc({
493
493
  nc: jetstream.nc,
494
494
  subject: buildAgentSessionRpcSubject(userId, initialAgentId),