doer-agent 0.4.2 → 0.4.3
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-codex-auth-rpc.js +322 -0
- package/dist/agent-codex-cli.js +210 -0
- package/dist/agent-fs-rpc.js +402 -0
- package/dist/agent-git-rpc.js +299 -0
- package/dist/agent-jetstream.js +120 -0
- package/dist/agent-run-execution.js +39 -0
- package/dist/agent-run-lifecycle.js +67 -0
- package/dist/agent-run-rpc.js +93 -0
- package/dist/agent-run-state.js +229 -0
- package/dist/agent-runtime-env.js +147 -0
- package/dist/agent-runtime-io.js +112 -0
- package/dist/agent-runtime-utils.js +253 -0
- package/dist/agent-session-loop.js +53 -0
- package/dist/agent-session-rpc.js +867 -0
- package/dist/agent-settings-rpc.js +75 -0
- package/dist/agent-settings.js +397 -0
- package/dist/agent-skill-rpc.js +164 -0
- package/dist/agent-task-execution.js +275 -0
- package/dist/agent.js +376 -4275
- package/package.json +1 -1
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { StringCodec } from "nats";
|
|
5
|
+
const codexAuthRpcCodec = StringCodec();
|
|
6
|
+
let pendingCodexDeviceAuth = null;
|
|
7
|
+
function parseCodexDeviceAuthOutput(raw, stripAnsi) {
|
|
8
|
+
const text = stripAnsi(raw);
|
|
9
|
+
const urlMatch = text.match(/https?:\/\/[^\s]+/i);
|
|
10
|
+
const codeMatch = text.match(/\b[A-Z0-9]{4,}(?:-[A-Z0-9]{4,})+\b/);
|
|
11
|
+
return {
|
|
12
|
+
verificationUri: urlMatch?.[0] ?? null,
|
|
13
|
+
userCode: codeMatch?.[0] ?? null,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function pendingCodexDeviceAuthMessage(state, stripAnsi) {
|
|
17
|
+
const parsed = parseCodexDeviceAuthOutput(state.output, stripAnsi);
|
|
18
|
+
if (parsed.verificationUri && parsed.userCode) {
|
|
19
|
+
return `Waiting for approval. Enter code ${parsed.userCode} at ${parsed.verificationUri}`;
|
|
20
|
+
}
|
|
21
|
+
return stripAnsi(state.output).trim() || "Waiting for approval";
|
|
22
|
+
}
|
|
23
|
+
async function getLocalCodexLoginStatus(args) {
|
|
24
|
+
const result = await args.runLocalCodexCli(["login", "status"], 5000);
|
|
25
|
+
const merged = args.stripAnsi([result.stdout, result.stderr].filter(Boolean).join("\n")).trim();
|
|
26
|
+
return {
|
|
27
|
+
loggedIn: (result.code ?? 1) === 0,
|
|
28
|
+
output: merged || ((result.code ?? 1) === 0 ? "Logged in" : "Not logged in"),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
async function waitForCodexDeviceCode(state, timeoutMs, stripAnsi) {
|
|
32
|
+
const startedAt = Date.now();
|
|
33
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
34
|
+
const parsed = parseCodexDeviceAuthOutput(state.output, stripAnsi);
|
|
35
|
+
if (parsed.verificationUri && parsed.userCode) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (state.child.exitCode !== null) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function startLocalCodexDeviceAuth(args) {
|
|
45
|
+
if (pendingCodexDeviceAuth && pendingCodexDeviceAuth.child.exitCode === null) {
|
|
46
|
+
const parsed = parseCodexDeviceAuthOutput(pendingCodexDeviceAuth.output, args.stripAnsi);
|
|
47
|
+
return {
|
|
48
|
+
loggedIn: false,
|
|
49
|
+
output: pendingCodexDeviceAuthMessage(pendingCodexDeviceAuth, args.stripAnsi),
|
|
50
|
+
verificationUri: parsed.verificationUri,
|
|
51
|
+
userCode: parsed.userCode,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const child = spawn(args.buildLocalCodexCliCommand(["login", "--device-auth"]), {
|
|
55
|
+
cwd: args.workspaceRoot,
|
|
56
|
+
shell: args.resolveShellPath(),
|
|
57
|
+
detached: process.platform !== "win32",
|
|
58
|
+
env: {
|
|
59
|
+
...process.env,
|
|
60
|
+
WORKSPACE: args.workspaceRoot,
|
|
61
|
+
CODEX_HOME: args.resolveCodexHomePath(),
|
|
62
|
+
},
|
|
63
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
64
|
+
});
|
|
65
|
+
child.stdout?.setEncoding("utf8");
|
|
66
|
+
child.stderr?.setEncoding("utf8");
|
|
67
|
+
const state = { child, output: "" };
|
|
68
|
+
pendingCodexDeviceAuth = state;
|
|
69
|
+
const appendOutput = (chunk) => {
|
|
70
|
+
state.output += chunk;
|
|
71
|
+
};
|
|
72
|
+
child.stdout?.on("data", appendOutput);
|
|
73
|
+
child.stderr?.on("data", appendOutput);
|
|
74
|
+
child.once("exit", () => {
|
|
75
|
+
if (pendingCodexDeviceAuth === state) {
|
|
76
|
+
pendingCodexDeviceAuth = null;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
await waitForCodexDeviceCode(state, 8000, args.stripAnsi);
|
|
80
|
+
const parsed = parseCodexDeviceAuthOutput(state.output, args.stripAnsi);
|
|
81
|
+
if ((!parsed.verificationUri || !parsed.userCode) && state.child.exitCode !== null) {
|
|
82
|
+
throw new Error("Failed to read device code from Codex CLI");
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
loggedIn: false,
|
|
86
|
+
output: pendingCodexDeviceAuthMessage(state, args.stripAnsi),
|
|
87
|
+
verificationUri: parsed.verificationUri,
|
|
88
|
+
userCode: parsed.userCode,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
async function startLocalCodexLogin(args) {
|
|
92
|
+
const child = spawn(args.buildLocalCodexCliCommand(["login"]), {
|
|
93
|
+
cwd: args.workspaceRoot,
|
|
94
|
+
shell: args.resolveShellPath(),
|
|
95
|
+
detached: process.platform !== "win32",
|
|
96
|
+
env: {
|
|
97
|
+
...process.env,
|
|
98
|
+
WORKSPACE: args.workspaceRoot,
|
|
99
|
+
CODEX_HOME: args.resolveCodexHomePath(),
|
|
100
|
+
},
|
|
101
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
102
|
+
});
|
|
103
|
+
let output = "";
|
|
104
|
+
child.stdout?.setEncoding("utf8");
|
|
105
|
+
child.stderr?.setEncoding("utf8");
|
|
106
|
+
child.stdout?.on("data", (chunk) => {
|
|
107
|
+
output += chunk;
|
|
108
|
+
});
|
|
109
|
+
child.stderr?.on("data", (chunk) => {
|
|
110
|
+
output += chunk;
|
|
111
|
+
});
|
|
112
|
+
const result = await new Promise((resolve, reject) => {
|
|
113
|
+
child.once("error", reject);
|
|
114
|
+
child.once("exit", (code) => resolve({ code, output }));
|
|
115
|
+
});
|
|
116
|
+
const normalized = args.stripAnsi(result.output).trim();
|
|
117
|
+
const parsed = parseCodexDeviceAuthOutput(result.output, args.stripAnsi);
|
|
118
|
+
if ((result.code ?? 1) === 0) {
|
|
119
|
+
const status = await args.getLocalCodexLoginStatus().catch(() => null);
|
|
120
|
+
return {
|
|
121
|
+
loggedIn: status?.loggedIn === true,
|
|
122
|
+
output: status?.output || normalized || "Login started",
|
|
123
|
+
verificationUri: parsed.verificationUri,
|
|
124
|
+
userCode: parsed.userCode,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
throw new Error(normalized || `Codex login failed with code ${result.code ?? "null"}`);
|
|
128
|
+
}
|
|
129
|
+
async function loginLocalCodexWithApiKey(args) {
|
|
130
|
+
const result = await args.runLocalCodexCliWithInput(["login", "--with-api-key"], args.apiKey, 15000);
|
|
131
|
+
const normalized = args.stripAnsi([result.stdout, result.stderr].filter(Boolean).join("\n")).trim();
|
|
132
|
+
if ((result.code ?? 1) !== 0) {
|
|
133
|
+
throw new Error(normalized || `Codex API key login failed with code ${result.code ?? "null"}`);
|
|
134
|
+
}
|
|
135
|
+
const status = await args.getLocalCodexLoginStatus().catch(() => null);
|
|
136
|
+
return {
|
|
137
|
+
loggedIn: status?.loggedIn === true,
|
|
138
|
+
output: status?.output || normalized || "Logged in",
|
|
139
|
+
verificationUri: null,
|
|
140
|
+
userCode: null,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
async function logoutLocalCodexAuth(args) {
|
|
144
|
+
if (pendingCodexDeviceAuth && pendingCodexDeviceAuth.child.exitCode === null) {
|
|
145
|
+
args.sendSignalToTaskProcess(pendingCodexDeviceAuth.child, "SIGTERM");
|
|
146
|
+
setTimeout(() => {
|
|
147
|
+
if (pendingCodexDeviceAuth?.child.exitCode === null) {
|
|
148
|
+
args.sendSignalToTaskProcess(pendingCodexDeviceAuth.child, "SIGKILL");
|
|
149
|
+
}
|
|
150
|
+
}, 1000);
|
|
151
|
+
pendingCodexDeviceAuth = null;
|
|
152
|
+
}
|
|
153
|
+
const result = await args.runLocalCodexCli(["logout"], 5000);
|
|
154
|
+
let merged = args.stripAnsi([result.stdout, result.stderr].filter(Boolean).join("\n")).trim();
|
|
155
|
+
const statusAfterLogout = await args.getLocalCodexLoginStatus().catch(() => null);
|
|
156
|
+
if (statusAfterLogout?.loggedIn) {
|
|
157
|
+
const authFile = path.join(args.resolveCodexHomePath(), "auth.json");
|
|
158
|
+
await unlink(authFile).catch(() => undefined);
|
|
159
|
+
const statusAfterDelete = await args.getLocalCodexLoginStatus().catch(() => null);
|
|
160
|
+
if (statusAfterDelete?.output) {
|
|
161
|
+
merged = [merged, statusAfterDelete.output].filter(Boolean).join("\n");
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
loggedIn: false,
|
|
166
|
+
output: merged || "Logged out",
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function normalizeCodexAuthRpcRequest(args) {
|
|
170
|
+
const requestId = typeof args.request.requestId === "string" ? args.request.requestId.trim() : "";
|
|
171
|
+
const responseSubject = typeof args.request.responseSubject === "string" ? args.request.responseSubject.trim() : "";
|
|
172
|
+
const requestAgentId = typeof args.request.agentId === "string" ? args.request.agentId.trim() : "";
|
|
173
|
+
const actionRaw = typeof args.request.action === "string" ? args.request.action.trim() : "";
|
|
174
|
+
const action = actionRaw === "start" || actionRaw === "logout" || actionRaw === "login_api_key" ? actionRaw : "status";
|
|
175
|
+
const apiKey = typeof args.request.apiKey === "string" && args.request.apiKey.trim() ? args.request.apiKey.trim() : null;
|
|
176
|
+
if (!requestId || !responseSubject || !requestAgentId || requestAgentId !== args.agentId) {
|
|
177
|
+
throw new Error("invalid codex auth rpc request");
|
|
178
|
+
}
|
|
179
|
+
if (action === "login_api_key" && !apiKey) {
|
|
180
|
+
throw new Error("api key is required");
|
|
181
|
+
}
|
|
182
|
+
return { requestId, responseSubject, action, apiKey };
|
|
183
|
+
}
|
|
184
|
+
function publishCodexAuthRpcResponse(args) {
|
|
185
|
+
args.nc.publish(args.responseSubject, codexAuthRpcCodec.encode(JSON.stringify(args.payload)));
|
|
186
|
+
}
|
|
187
|
+
async function handleCodexAuthRpcMessage(args) {
|
|
188
|
+
let requestId = "unknown";
|
|
189
|
+
let responseSubject = "";
|
|
190
|
+
try {
|
|
191
|
+
const payload = JSON.parse(codexAuthRpcCodec.decode(args.msg.data));
|
|
192
|
+
const request = normalizeCodexAuthRpcRequest({ request: payload, agentId: args.agentId });
|
|
193
|
+
requestId = request.requestId;
|
|
194
|
+
responseSubject = request.responseSubject;
|
|
195
|
+
const getStatus = () => getLocalCodexLoginStatus({
|
|
196
|
+
runLocalCodexCli: args.runLocalCodexCli,
|
|
197
|
+
stripAnsi: args.stripAnsi,
|
|
198
|
+
});
|
|
199
|
+
let result = null;
|
|
200
|
+
if (request.action === "login_api_key") {
|
|
201
|
+
result = await loginLocalCodexWithApiKey({
|
|
202
|
+
apiKey: request.apiKey ?? "",
|
|
203
|
+
runLocalCodexCliWithInput: args.runLocalCodexCliWithInput,
|
|
204
|
+
stripAnsi: args.stripAnsi,
|
|
205
|
+
getLocalCodexLoginStatus: getStatus,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
else if (request.action === "start") {
|
|
209
|
+
const status = await getStatus();
|
|
210
|
+
if (status.loggedIn) {
|
|
211
|
+
result = { loggedIn: true, output: status.output };
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
try {
|
|
215
|
+
result = await startLocalCodexDeviceAuth({
|
|
216
|
+
workspaceRoot: args.workspaceRoot,
|
|
217
|
+
buildLocalCodexCliCommand: args.buildLocalCodexCliCommand,
|
|
218
|
+
resolveShellPath: args.resolveShellPath,
|
|
219
|
+
resolveCodexHomePath: args.resolveCodexHomePath,
|
|
220
|
+
stripAnsi: args.stripAnsi,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
225
|
+
const normalized = message.toLowerCase();
|
|
226
|
+
if (normalized.includes("operation not permitted") ||
|
|
227
|
+
normalized.includes("failed to read device code") ||
|
|
228
|
+
normalized.includes("panic") ||
|
|
229
|
+
normalized.includes("null object")) {
|
|
230
|
+
result = await startLocalCodexLogin({
|
|
231
|
+
workspaceRoot: args.workspaceRoot,
|
|
232
|
+
buildLocalCodexCliCommand: args.buildLocalCodexCliCommand,
|
|
233
|
+
resolveShellPath: args.resolveShellPath,
|
|
234
|
+
resolveCodexHomePath: args.resolveCodexHomePath,
|
|
235
|
+
stripAnsi: args.stripAnsi,
|
|
236
|
+
getLocalCodexLoginStatus: getStatus,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else if (request.action === "logout") {
|
|
246
|
+
result = await logoutLocalCodexAuth({
|
|
247
|
+
sendSignalToTaskProcess: args.sendSignalToTaskProcess,
|
|
248
|
+
runLocalCodexCli: args.runLocalCodexCli,
|
|
249
|
+
stripAnsi: args.stripAnsi,
|
|
250
|
+
resolveCodexHomePath: args.resolveCodexHomePath,
|
|
251
|
+
getLocalCodexLoginStatus: getStatus,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
const status = await getStatus();
|
|
256
|
+
if (status.loggedIn) {
|
|
257
|
+
result = { loggedIn: true, output: status.output };
|
|
258
|
+
}
|
|
259
|
+
else if (pendingCodexDeviceAuth && pendingCodexDeviceAuth.child.exitCode === null) {
|
|
260
|
+
const parsed = parseCodexDeviceAuthOutput(pendingCodexDeviceAuth.output, args.stripAnsi);
|
|
261
|
+
result = {
|
|
262
|
+
loggedIn: false,
|
|
263
|
+
output: pendingCodexDeviceAuthMessage(pendingCodexDeviceAuth, args.stripAnsi),
|
|
264
|
+
verificationUri: parsed.verificationUri,
|
|
265
|
+
userCode: parsed.userCode,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
result = { loggedIn: false, output: status.output || "Not logged in" };
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
publishCodexAuthRpcResponse({
|
|
273
|
+
nc: args.nc,
|
|
274
|
+
responseSubject,
|
|
275
|
+
payload: {
|
|
276
|
+
requestId,
|
|
277
|
+
ok: true,
|
|
278
|
+
loggedIn: result.loggedIn,
|
|
279
|
+
output: result.output,
|
|
280
|
+
verificationUri: result.verificationUri ?? null,
|
|
281
|
+
userCode: result.userCode ?? null,
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
287
|
+
if (responseSubject) {
|
|
288
|
+
publishCodexAuthRpcResponse({
|
|
289
|
+
nc: args.nc,
|
|
290
|
+
responseSubject,
|
|
291
|
+
payload: { requestId, ok: false, error: message },
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
args.onError(`codex auth rpc failed requestId=${requestId} error=${message}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
export function subscribeToCodexAuthRpc(args) {
|
|
298
|
+
args.nc.subscribe(args.subject, {
|
|
299
|
+
callback: (error, msg) => {
|
|
300
|
+
if (error) {
|
|
301
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
302
|
+
args.onError(`codex auth rpc subscription error: ${message}`);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
void handleCodexAuthRpcMessage({
|
|
306
|
+
msg,
|
|
307
|
+
nc: args.nc,
|
|
308
|
+
agentId: args.agentId,
|
|
309
|
+
workspaceRoot: args.workspaceRoot,
|
|
310
|
+
buildLocalCodexCliCommand: args.buildLocalCodexCliCommand,
|
|
311
|
+
resolveShellPath: args.resolveShellPath,
|
|
312
|
+
resolveCodexHomePath: args.resolveCodexHomePath,
|
|
313
|
+
runLocalCodexCli: args.runLocalCodexCli,
|
|
314
|
+
runLocalCodexCliWithInput: args.runLocalCodexCliWithInput,
|
|
315
|
+
sendSignalToTaskProcess: args.sendSignalToTaskProcess,
|
|
316
|
+
stripAnsi: args.stripAnsi,
|
|
317
|
+
onError: args.onError,
|
|
318
|
+
});
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
args.onInfo(`codex auth rpc subscribed subject=${args.subject}`);
|
|
322
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
const ANSI_RE = /\u001b\[[0-9;]*m/g;
|
|
3
|
+
function shellSingleQuote(value) {
|
|
4
|
+
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
5
|
+
}
|
|
6
|
+
function toTomlStringLiteral(value) {
|
|
7
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
8
|
+
}
|
|
9
|
+
function hasDirectCodexBinary() {
|
|
10
|
+
const result = spawnSync("bash", ["-lc", "command -v codex >/dev/null 2>&1"], {
|
|
11
|
+
stdio: "ignore",
|
|
12
|
+
});
|
|
13
|
+
return result.status === 0;
|
|
14
|
+
}
|
|
15
|
+
export function stripAnsi(value) {
|
|
16
|
+
return value.replace(ANSI_RE, "");
|
|
17
|
+
}
|
|
18
|
+
export function normalizeCodexModel(value) {
|
|
19
|
+
const normalized = typeof value === "string" ? value.trim() : "";
|
|
20
|
+
return normalized || "gpt-5.4";
|
|
21
|
+
}
|
|
22
|
+
export function buildManagedCodexArgs(args) {
|
|
23
|
+
const promptArgs = ["--", args.prompt];
|
|
24
|
+
const fixedArgs = ["--dangerously-bypass-approvals-and-sandbox"];
|
|
25
|
+
const configArgs = [
|
|
26
|
+
...(args.personality ? ["--config", `personality=${toTomlStringLiteral(args.personality)}`] : []),
|
|
27
|
+
...(args.modelInstructionsFile
|
|
28
|
+
? ["--config", `model_instructions_file=${toTomlStringLiteral(args.modelInstructionsFile)}`]
|
|
29
|
+
: []),
|
|
30
|
+
];
|
|
31
|
+
const imageArgs = args.imagePaths.flatMap((imagePath) => ["--image", imagePath]);
|
|
32
|
+
return [
|
|
33
|
+
...fixedArgs,
|
|
34
|
+
...configArgs,
|
|
35
|
+
"--model",
|
|
36
|
+
args.model,
|
|
37
|
+
...(args.sessionId
|
|
38
|
+
? ["exec", "resume", ...imageArgs, args.sessionId, ...promptArgs]
|
|
39
|
+
: ["exec", ...imageArgs, ...promptArgs]),
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
export function buildLocalCodexCliCommand(args) {
|
|
43
|
+
const quotedArgs = args.map(shellSingleQuote).join(" ");
|
|
44
|
+
const direct = `exec codex ${quotedArgs}`;
|
|
45
|
+
const fallback = `exec npm exec --yes --package doer-agent -- codex ${quotedArgs}`;
|
|
46
|
+
const script = [
|
|
47
|
+
"if command -v codex >/dev/null 2>&1; then",
|
|
48
|
+
` ${direct}`,
|
|
49
|
+
"fi",
|
|
50
|
+
fallback,
|
|
51
|
+
].join("\n");
|
|
52
|
+
return `bash -lc ${shellSingleQuote(script)}`;
|
|
53
|
+
}
|
|
54
|
+
export function spawnManagedCodexCommand(args) {
|
|
55
|
+
const env = {
|
|
56
|
+
...args.env,
|
|
57
|
+
DOER_AGENT_TOKEN: args.agentToken,
|
|
58
|
+
};
|
|
59
|
+
const child = hasDirectCodexBinary()
|
|
60
|
+
? spawn("codex", args.codexArgs, {
|
|
61
|
+
cwd: args.taskWorkspace,
|
|
62
|
+
detached: process.platform !== "win32",
|
|
63
|
+
env,
|
|
64
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
65
|
+
})
|
|
66
|
+
: spawn("npm", ["exec", "--yes", "--package", "doer-agent", "--", "codex", ...args.codexArgs], {
|
|
67
|
+
cwd: args.taskWorkspace,
|
|
68
|
+
detached: process.platform !== "win32",
|
|
69
|
+
env,
|
|
70
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
71
|
+
});
|
|
72
|
+
child.stdout?.setEncoding("utf8");
|
|
73
|
+
child.stderr?.setEncoding("utf8");
|
|
74
|
+
return child;
|
|
75
|
+
}
|
|
76
|
+
export function createLocalCodexCliTools(args) {
|
|
77
|
+
async function runLocalCodexCli(cmdArgs, timeoutMs, envPatch) {
|
|
78
|
+
const command = buildLocalCodexCliCommand(cmdArgs);
|
|
79
|
+
const workspaceRoot = args.resolveWorkspaceRoot();
|
|
80
|
+
const env = {
|
|
81
|
+
...process.env,
|
|
82
|
+
...(envPatch ?? {}),
|
|
83
|
+
WORKSPACE: workspaceRoot,
|
|
84
|
+
CODEX_HOME: args.resolveCodexHomePath(),
|
|
85
|
+
};
|
|
86
|
+
return await new Promise((resolve, reject) => {
|
|
87
|
+
const child = spawn(command, {
|
|
88
|
+
cwd: workspaceRoot,
|
|
89
|
+
shell: args.resolveShellPath(),
|
|
90
|
+
env,
|
|
91
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
92
|
+
});
|
|
93
|
+
let stdout = "";
|
|
94
|
+
let stderr = "";
|
|
95
|
+
let done = false;
|
|
96
|
+
let timedOut = false;
|
|
97
|
+
child.stdout.setEncoding("utf8");
|
|
98
|
+
child.stderr.setEncoding("utf8");
|
|
99
|
+
child.stdout.on("data", (chunk) => {
|
|
100
|
+
stdout += chunk;
|
|
101
|
+
});
|
|
102
|
+
child.stderr.on("data", (chunk) => {
|
|
103
|
+
stderr += chunk;
|
|
104
|
+
});
|
|
105
|
+
const timer = setTimeout(() => {
|
|
106
|
+
timedOut = true;
|
|
107
|
+
args.sendSignalToTaskProcess(child, "SIGTERM");
|
|
108
|
+
setTimeout(() => args.sendSignalToTaskProcess(child, "SIGKILL"), 1000);
|
|
109
|
+
}, Math.max(500, timeoutMs));
|
|
110
|
+
child.once("error", (error) => {
|
|
111
|
+
if (done) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
done = true;
|
|
115
|
+
clearTimeout(timer);
|
|
116
|
+
reject(error);
|
|
117
|
+
});
|
|
118
|
+
child.once("exit", (code) => {
|
|
119
|
+
if (done) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
done = true;
|
|
123
|
+
clearTimeout(timer);
|
|
124
|
+
resolve({ code, stdout, stderr, timedOut });
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
async function runLocalCodexCliWithInput(cmdArgs, input, timeoutMs, envPatch) {
|
|
129
|
+
const command = buildLocalCodexCliCommand(cmdArgs);
|
|
130
|
+
const workspaceRoot = args.resolveWorkspaceRoot();
|
|
131
|
+
const env = {
|
|
132
|
+
...process.env,
|
|
133
|
+
...(envPatch ?? {}),
|
|
134
|
+
WORKSPACE: workspaceRoot,
|
|
135
|
+
CODEX_HOME: args.resolveCodexHomePath(),
|
|
136
|
+
};
|
|
137
|
+
return await new Promise((resolve, reject) => {
|
|
138
|
+
const child = spawn(command, {
|
|
139
|
+
cwd: workspaceRoot,
|
|
140
|
+
shell: args.resolveShellPath(),
|
|
141
|
+
env,
|
|
142
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
143
|
+
});
|
|
144
|
+
let stdout = "";
|
|
145
|
+
let stderr = "";
|
|
146
|
+
let done = false;
|
|
147
|
+
let timedOut = false;
|
|
148
|
+
child.stdout.setEncoding("utf8");
|
|
149
|
+
child.stderr.setEncoding("utf8");
|
|
150
|
+
child.stdout.on("data", (chunk) => {
|
|
151
|
+
stdout += chunk;
|
|
152
|
+
});
|
|
153
|
+
child.stderr.on("data", (chunk) => {
|
|
154
|
+
stderr += chunk;
|
|
155
|
+
});
|
|
156
|
+
child.stdin?.write(input);
|
|
157
|
+
if (!input.endsWith("\n")) {
|
|
158
|
+
child.stdin?.write("\n");
|
|
159
|
+
}
|
|
160
|
+
child.stdin?.end();
|
|
161
|
+
const timer = setTimeout(() => {
|
|
162
|
+
timedOut = true;
|
|
163
|
+
args.sendSignalToTaskProcess(child, "SIGTERM");
|
|
164
|
+
setTimeout(() => args.sendSignalToTaskProcess(child, "SIGKILL"), 1000);
|
|
165
|
+
}, Math.max(500, timeoutMs));
|
|
166
|
+
child.once("error", (error) => {
|
|
167
|
+
if (done) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
done = true;
|
|
171
|
+
clearTimeout(timer);
|
|
172
|
+
reject(error);
|
|
173
|
+
});
|
|
174
|
+
child.once("exit", (code) => {
|
|
175
|
+
if (done) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
done = true;
|
|
179
|
+
clearTimeout(timer);
|
|
180
|
+
resolve({ code, stdout, stderr, timedOut });
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
buildLocalCodexCliCommand,
|
|
186
|
+
runLocalCodexCli,
|
|
187
|
+
runLocalCodexCliWithInput,
|
|
188
|
+
stripAnsi,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
export function normalizeShellRpcCodexAuthBundle(value) {
|
|
192
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
const row = value;
|
|
196
|
+
const authJson = typeof row.authJson === "string" ? row.authJson : null;
|
|
197
|
+
const authMode = row.authMode === "oauth" ? "oauth" : row.authMode === "api_key" ? "api_key" : undefined;
|
|
198
|
+
const apiKey = typeof row.apiKey === "string" || row.apiKey === null ? row.apiKey : undefined;
|
|
199
|
+
if (!authJson && authMode !== "api_key" && apiKey === undefined) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
taskId: typeof row.taskId === "string" ? row.taskId : undefined,
|
|
204
|
+
authMode,
|
|
205
|
+
issuedAt: typeof row.issuedAt === "string" ? row.issuedAt : undefined,
|
|
206
|
+
expiresAt: typeof row.expiresAt === "string" ? row.expiresAt : undefined,
|
|
207
|
+
authJson: authJson ?? undefined,
|
|
208
|
+
apiKey,
|
|
209
|
+
};
|
|
210
|
+
}
|