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.
- package/README.md +1 -1
- package/dist/agent-codex-cli.js +26 -0
- package/dist/agent-daemon-rpc.js +475 -0
- package/dist/agent-fs-rpc.js +5 -0
- package/dist/agent-run-state.js +61 -3
- package/dist/agent-runtime-utils.js +91 -14
- package/dist/agent-runtime-utils.test.js +38 -0
- package/dist/agent-task-execution.js +1 -215
- package/dist/agent.js +29 -29
- package/dist/daemon-log-runner.js +176 -0
- package/dist/daemon-mcp-server.js +166 -0
- package/package.json +1 -1
- package/runtime/bin/apply_patch +0 -5
|
@@ -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:
|
|
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 {
|
|
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,
|
|
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:
|
|
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
|
-
|
|
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),
|