doer-agent 0.2.5 → 0.2.7
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.js +383 -388
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawn, spawnSync } from "node:child_process";
|
|
2
2
|
import { existsSync, statSync, watch } from "node:fs";
|
|
3
|
-
import { chmod, mkdir, open, readFile, readdir, rm, rmdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { chmod, mkdir, open, readFile, readdir, rename, rm, rmdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { AckPolicy, connect, DeliverPolicy, JSONCodec, RetentionPolicy, StorageType, StringCodec } from "nats";
|
|
@@ -9,17 +9,13 @@ const AGENT_MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
|
9
9
|
const AGENT_PROJECT_DIR = path.join(AGENT_MODULE_DIR, "..");
|
|
10
10
|
const AGENT_PACKAGE_JSON_PATH = path.join(AGENT_PROJECT_DIR, "package.json");
|
|
11
11
|
let activeTaskLogContext = null;
|
|
12
|
-
const activeTaskCancelRequests = new Map();
|
|
13
12
|
let workspaceRootOverride = null;
|
|
14
13
|
const fsRpcCodec = StringCodec();
|
|
15
|
-
const shellRpcCodec = StringCodec();
|
|
16
14
|
const runRpcCodec = StringCodec();
|
|
17
15
|
const sessionRpcCodec = StringCodec();
|
|
18
|
-
const codexRpcCodec = StringCodec();
|
|
19
16
|
const codexAuthRpcCodec = StringCodec();
|
|
20
17
|
const settingsRpcCodec = StringCodec();
|
|
21
18
|
const gitRpcCodec = StringCodec();
|
|
22
|
-
const activeRuns = new Map();
|
|
23
19
|
const retainedRuns = new Map();
|
|
24
20
|
const activeSessionWatchers = new Map();
|
|
25
21
|
const sessionLineIndexCache = new Map();
|
|
@@ -35,9 +31,6 @@ function buildAgentRunRpcSubject(userId, agentId) {
|
|
|
35
31
|
function buildAgentSessionRpcSubject(userId, agentId) {
|
|
36
32
|
return `doer.agent.session.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
|
|
37
33
|
}
|
|
38
|
-
function buildAgentCodexRpcSubject(userId, agentId) {
|
|
39
|
-
return `doer.agent.codex.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
|
|
40
|
-
}
|
|
41
34
|
function buildAgentCodexAuthRpcSubject(userId, agentId) {
|
|
42
35
|
return `doer.agent.codex.auth.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
|
|
43
36
|
}
|
|
@@ -421,9 +414,11 @@ function normalizeRunRpcRequest(args) {
|
|
|
421
414
|
throw new Error("missing responseSubject");
|
|
422
415
|
}
|
|
423
416
|
const runId = typeof args.request.runId === "string" && args.request.runId.trim() ? args.request.runId.trim() : null;
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
417
|
+
const prompt = typeof args.request.prompt === "string" && args.request.prompt.trim() ? args.request.prompt.trim() : null;
|
|
418
|
+
const sessionId = typeof args.request.sessionId === "string" && args.request.sessionId.trim() ? args.request.sessionId.trim() : null;
|
|
419
|
+
const model = normalizeCodexModel(args.request.model);
|
|
420
|
+
if (action === "start" && !prompt) {
|
|
421
|
+
throw new Error("missing prompt");
|
|
427
422
|
}
|
|
428
423
|
if ((action === "get" || action === "cancel") && !runId) {
|
|
429
424
|
throw new Error("missing runId");
|
|
@@ -437,7 +432,9 @@ function normalizeRunRpcRequest(args) {
|
|
|
437
432
|
requestId,
|
|
438
433
|
action,
|
|
439
434
|
runId,
|
|
440
|
-
|
|
435
|
+
prompt,
|
|
436
|
+
sessionId,
|
|
437
|
+
model,
|
|
441
438
|
cwd,
|
|
442
439
|
responseSubject,
|
|
443
440
|
sinceSeq,
|
|
@@ -466,10 +463,13 @@ async function persistRunTask(task) {
|
|
|
466
463
|
runId: task.id,
|
|
467
464
|
agentId: task.agentId,
|
|
468
465
|
userId: task.userId,
|
|
466
|
+
processPid: task.processPid,
|
|
469
467
|
sessionId: task.sessionId,
|
|
470
468
|
sessionFilePath: task.sessionFilePath,
|
|
471
469
|
status: task.status,
|
|
472
470
|
cancelRequested: task.cancelRequested,
|
|
471
|
+
resultExitCode: task.resultExitCode,
|
|
472
|
+
resultSignal: task.resultSignal,
|
|
473
473
|
createdAt: task.createdAt,
|
|
474
474
|
updatedAt: task.updatedAt,
|
|
475
475
|
startedAt: task.startedAt,
|
|
@@ -482,6 +482,99 @@ async function removeRunTask(runId) {
|
|
|
482
482
|
const dir = await resolveRunsDir();
|
|
483
483
|
await unlink(path.join(dir, `${runId}.json`)).catch(() => undefined);
|
|
484
484
|
}
|
|
485
|
+
function sanitizeRunLockSegment(value) {
|
|
486
|
+
return value.trim().replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 160) || "lock";
|
|
487
|
+
}
|
|
488
|
+
async function resolveRunLocksDir() {
|
|
489
|
+
const dir = path.join(await resolveRunsDir(), "locks");
|
|
490
|
+
await mkdir(dir, { recursive: true });
|
|
491
|
+
return dir;
|
|
492
|
+
}
|
|
493
|
+
async function resolveRunStartLockPath(args) {
|
|
494
|
+
const dir = await resolveRunLocksDir();
|
|
495
|
+
if (typeof args.sessionId === "string" && args.sessionId.trim()) {
|
|
496
|
+
return path.join(dir, `session__${sanitizeRunLockSegment(args.sessionId)}.lock`);
|
|
497
|
+
}
|
|
498
|
+
return path.join(dir, `run__${sanitizeRunLockSegment(args.runId)}.lock`);
|
|
499
|
+
}
|
|
500
|
+
async function claimRunStartSlot(args) {
|
|
501
|
+
const lockPath = await resolveRunStartLockPath(args);
|
|
502
|
+
try {
|
|
503
|
+
const handle = await open(lockPath, "wx");
|
|
504
|
+
try {
|
|
505
|
+
const payload = {
|
|
506
|
+
runId: args.runId,
|
|
507
|
+
sessionId: typeof args.sessionId === "string" && args.sessionId.trim() ? args.sessionId.trim() : null,
|
|
508
|
+
pid: process.pid,
|
|
509
|
+
createdAt: formatLocalTimestamp(),
|
|
510
|
+
};
|
|
511
|
+
await handle.writeFile(`${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
512
|
+
}
|
|
513
|
+
finally {
|
|
514
|
+
await handle.close().catch(() => undefined);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
catch (error) {
|
|
518
|
+
if (error?.code === "EEXIST") {
|
|
519
|
+
const lockContents = await readFile(lockPath, "utf8").catch(() => "");
|
|
520
|
+
const existingRunId = (() => {
|
|
521
|
+
try {
|
|
522
|
+
const parsed = JSON.parse(lockContents);
|
|
523
|
+
return typeof parsed.runId === "string" && parsed.runId.trim() ? parsed.runId.trim() : null;
|
|
524
|
+
}
|
|
525
|
+
catch {
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
})();
|
|
529
|
+
throw new Error(existingRunId ? `Another run is already active: ${existingRunId}` : "Another run is already active");
|
|
530
|
+
}
|
|
531
|
+
throw error;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
async function updateRunStartSlotSession(args) {
|
|
535
|
+
const nextSessionId = args.sessionId.trim();
|
|
536
|
+
if (!nextSessionId) {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
const previousSessionId = typeof args.previousSessionId === "string" && args.previousSessionId.trim() ? args.previousSessionId.trim() : null;
|
|
540
|
+
if (previousSessionId === nextSessionId) {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
const currentPath = await resolveRunStartLockPath({ runId: args.runId, sessionId: previousSessionId });
|
|
544
|
+
const nextPath = await resolveRunStartLockPath({ runId: args.runId, sessionId: nextSessionId });
|
|
545
|
+
if (currentPath === nextPath) {
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
try {
|
|
549
|
+
await rename(currentPath, nextPath);
|
|
550
|
+
}
|
|
551
|
+
catch (error) {
|
|
552
|
+
const code = error?.code;
|
|
553
|
+
if (code === "ENOENT") {
|
|
554
|
+
// Lock may already be released; nothing to migrate.
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
if (code === "EEXIST") {
|
|
558
|
+
throw new Error(`Another run is already active for session: ${nextSessionId}`);
|
|
559
|
+
}
|
|
560
|
+
throw error;
|
|
561
|
+
}
|
|
562
|
+
const payload = {
|
|
563
|
+
runId: args.runId,
|
|
564
|
+
sessionId: nextSessionId,
|
|
565
|
+
pid: process.pid,
|
|
566
|
+
createdAt: formatLocalTimestamp(),
|
|
567
|
+
};
|
|
568
|
+
await writeFile(nextPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
569
|
+
}
|
|
570
|
+
async function releaseRunStartSlot(args) {
|
|
571
|
+
const paths = new Set();
|
|
572
|
+
paths.add(await resolveRunStartLockPath({ runId: args.runId, sessionId: args.sessionId ?? null }));
|
|
573
|
+
paths.add(await resolveRunStartLockPath({ runId: args.runId, sessionId: null }));
|
|
574
|
+
for (const lockPath of paths) {
|
|
575
|
+
await unlink(lockPath).catch(() => undefined);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
485
578
|
function resolveAgentSettingsDir() {
|
|
486
579
|
const workspaceRoot = workspaceRootOverride ?? (process.env.WORKSPACE?.trim() || process.cwd());
|
|
487
580
|
return path.join(workspaceRoot, ".doer-agent");
|
|
@@ -902,6 +995,7 @@ function extractCodexSessionMetadata(value) {
|
|
|
902
995
|
}
|
|
903
996
|
async function updateRunSessionMetadata(task, metadata) {
|
|
904
997
|
let changed = false;
|
|
998
|
+
const previousSessionId = task.sessionId;
|
|
905
999
|
if (!task.sessionId && typeof metadata.sessionId === "string" && metadata.sessionId.trim()) {
|
|
906
1000
|
task.sessionId = metadata.sessionId.trim();
|
|
907
1001
|
changed = true;
|
|
@@ -915,14 +1009,82 @@ async function updateRunSessionMetadata(task, metadata) {
|
|
|
915
1009
|
}
|
|
916
1010
|
task.updatedAt = formatLocalTimestamp();
|
|
917
1011
|
await persistRunTask(task).catch(() => undefined);
|
|
1012
|
+
if (!previousSessionId && task.sessionId) {
|
|
1013
|
+
await updateRunStartSlotSession({
|
|
1014
|
+
runId: task.id,
|
|
1015
|
+
previousSessionId,
|
|
1016
|
+
sessionId: task.sessionId,
|
|
1017
|
+
}).catch(() => undefined);
|
|
1018
|
+
}
|
|
918
1019
|
}
|
|
919
1020
|
function persistRetainedRun(task) {
|
|
920
1021
|
retainedRuns.set(task.id, cloneRunTask(task));
|
|
921
1022
|
}
|
|
922
|
-
function
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
1023
|
+
function normalizePersistedRunTask(value) {
|
|
1024
|
+
if (!value || typeof value !== "object") {
|
|
1025
|
+
return null;
|
|
1026
|
+
}
|
|
1027
|
+
const record = value;
|
|
1028
|
+
const id = typeof record.runId === "string" && record.runId.trim()
|
|
1029
|
+
? record.runId.trim()
|
|
1030
|
+
: typeof record.id === "string" && record.id.trim()
|
|
1031
|
+
? record.id.trim()
|
|
1032
|
+
: "";
|
|
1033
|
+
const userId = typeof record.userId === "string" ? record.userId : "";
|
|
1034
|
+
const agentId = typeof record.agentId === "string" ? record.agentId : "";
|
|
1035
|
+
const status = record.status;
|
|
1036
|
+
if (!id || !userId || !agentId || !["queued", "running", "completed", "failed", "canceled"].includes(String(status))) {
|
|
1037
|
+
return null;
|
|
1038
|
+
}
|
|
1039
|
+
return {
|
|
1040
|
+
id,
|
|
1041
|
+
userId,
|
|
1042
|
+
agentId,
|
|
1043
|
+
processPid: typeof record.processPid === "number" ? record.processPid : null,
|
|
1044
|
+
sessionId: typeof record.sessionId === "string" && record.sessionId.trim() ? record.sessionId.trim() : null,
|
|
1045
|
+
sessionFilePath: typeof record.sessionFilePath === "string" && record.sessionFilePath.trim() ? record.sessionFilePath.trim() : null,
|
|
1046
|
+
status: status,
|
|
1047
|
+
cancelRequested: Boolean(record.cancelRequested),
|
|
1048
|
+
resultExitCode: typeof record.resultExitCode === "number" ? record.resultExitCode : null,
|
|
1049
|
+
resultSignal: typeof record.resultSignal === "string" && record.resultSignal.trim() ? record.resultSignal.trim() : null,
|
|
1050
|
+
error: typeof record.error === "string" && record.error.trim() ? record.error : null,
|
|
1051
|
+
createdAt: typeof record.createdAt === "string" ? record.createdAt : "",
|
|
1052
|
+
updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : "",
|
|
1053
|
+
startedAt: typeof record.startedAt === "string" && record.startedAt.trim() ? record.startedAt : null,
|
|
1054
|
+
finishedAt: typeof record.finishedAt === "string" && record.finishedAt.trim() ? record.finishedAt : null,
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
async function listPersistedRunTasks() {
|
|
1058
|
+
const dir = await resolveRunsDir();
|
|
1059
|
+
const names = await readdir(dir).catch(() => []);
|
|
1060
|
+
const tasks = await Promise.all(names
|
|
1061
|
+
.filter((name) => name.endsWith(".json"))
|
|
1062
|
+
.map(async (name) => {
|
|
1063
|
+
const raw = await readFile(path.join(dir, name), "utf8").catch(() => null);
|
|
1064
|
+
if (!raw) {
|
|
1065
|
+
return null;
|
|
1066
|
+
}
|
|
1067
|
+
try {
|
|
1068
|
+
return normalizePersistedRunTask(JSON.parse(raw));
|
|
1069
|
+
}
|
|
1070
|
+
catch {
|
|
1071
|
+
return null;
|
|
1072
|
+
}
|
|
1073
|
+
}));
|
|
1074
|
+
return tasks.filter((task) => task !== null);
|
|
1075
|
+
}
|
|
1076
|
+
async function getStoredRun(runId) {
|
|
1077
|
+
const persisted = await readFile(path.join(await resolveRunsDir(), `${runId}.json`), "utf8").catch(() => null);
|
|
1078
|
+
if (persisted) {
|
|
1079
|
+
try {
|
|
1080
|
+
const parsed = normalizePersistedRunTask(JSON.parse(persisted));
|
|
1081
|
+
if (parsed) {
|
|
1082
|
+
return parsed;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
catch {
|
|
1086
|
+
// Ignore malformed persisted state and fall back to retained memory.
|
|
1087
|
+
}
|
|
926
1088
|
}
|
|
927
1089
|
return retainedRuns.get(runId) ?? null;
|
|
928
1090
|
}
|
|
@@ -933,11 +1095,8 @@ async function startManagedRun(args) {
|
|
|
933
1095
|
taskId: args.runId,
|
|
934
1096
|
codexAuthBundle: args.codexAuthBundle,
|
|
935
1097
|
});
|
|
936
|
-
const child =
|
|
937
|
-
|
|
938
|
-
command: args.command,
|
|
939
|
-
patch: null,
|
|
940
|
-
shellPath: prepared.shellPath,
|
|
1098
|
+
const child = spawnManagedCodexCommand({
|
|
1099
|
+
codexArgs: args.codexArgs,
|
|
941
1100
|
taskWorkspace: prepared.taskWorkspace,
|
|
942
1101
|
env: prepared.env,
|
|
943
1102
|
agentToken: args.agentToken,
|
|
@@ -947,6 +1106,7 @@ async function startManagedRun(args) {
|
|
|
947
1106
|
id: args.runId,
|
|
948
1107
|
userId: args.userId,
|
|
949
1108
|
agentId: args.agentId,
|
|
1109
|
+
processPid: typeof child.pid === "number" ? child.pid : null,
|
|
950
1110
|
sessionId: typeof args.sessionId === "string" && args.sessionId.trim() ? args.sessionId.trim() : null,
|
|
951
1111
|
sessionFilePath: null,
|
|
952
1112
|
status: "running",
|
|
@@ -959,17 +1119,6 @@ async function startManagedRun(args) {
|
|
|
959
1119
|
startedAt: now,
|
|
960
1120
|
finishedAt: null,
|
|
961
1121
|
};
|
|
962
|
-
const cancellation = createManagedCancellation(child);
|
|
963
|
-
const requestCancel = () => {
|
|
964
|
-
if (task.status === "completed" || task.status === "failed" || task.status === "canceled") {
|
|
965
|
-
return;
|
|
966
|
-
}
|
|
967
|
-
task.cancelRequested = true;
|
|
968
|
-
task.updatedAt = formatLocalTimestamp();
|
|
969
|
-
void persistRunTask(task).catch(() => undefined);
|
|
970
|
-
writeRunStatus(task.id, "cancel requested");
|
|
971
|
-
cancellation.requestCancel();
|
|
972
|
-
};
|
|
973
1122
|
let stdoutBuffer = "";
|
|
974
1123
|
const recordChunk = (stream, chunk) => {
|
|
975
1124
|
writeRunStream(task.id, stream, chunk);
|
|
@@ -998,31 +1147,33 @@ async function startManagedRun(args) {
|
|
|
998
1147
|
task.error = message;
|
|
999
1148
|
task.finishedAt = formatLocalTimestamp();
|
|
1000
1149
|
persistRetainedRun(task);
|
|
1001
|
-
activeRuns.delete(task.id);
|
|
1002
1150
|
void removeRunTask(task.id).catch(() => undefined);
|
|
1151
|
+
void releaseRunStartSlot({ runId: task.id, sessionId: task.sessionId }).catch(() => undefined);
|
|
1003
1152
|
void prepared.codexAuthCleanup().catch(() => undefined);
|
|
1004
1153
|
writeRunStatus(task.id, `failed error=${message}`);
|
|
1005
1154
|
});
|
|
1006
|
-
child.once("close", (code, signal) => {
|
|
1007
|
-
cancellation.clear();
|
|
1155
|
+
child.once("close", async (code, signal) => {
|
|
1008
1156
|
if (stdoutBuffer.trim() && (!task.sessionId || !task.sessionFilePath)) {
|
|
1009
1157
|
const metadata = extractCodexSessionMetadata(stdoutBuffer.trim());
|
|
1010
1158
|
if (metadata.sessionId || metadata.sessionFilePath) {
|
|
1011
1159
|
void updateRunSessionMetadata(task, metadata);
|
|
1012
1160
|
}
|
|
1013
1161
|
}
|
|
1162
|
+
const latest = await getStoredRun(task.id).catch(() => null);
|
|
1163
|
+
if (latest?.cancelRequested) {
|
|
1164
|
+
task.cancelRequested = true;
|
|
1165
|
+
}
|
|
1014
1166
|
task.resultExitCode = typeof code === "number" ? code : null;
|
|
1015
1167
|
task.resultSignal = signal;
|
|
1016
1168
|
task.finishedAt = formatLocalTimestamp();
|
|
1017
1169
|
task.status = task.cancelRequested ? "canceled" : (task.resultExitCode ?? 1) === 0 ? "completed" : "failed";
|
|
1018
1170
|
task.error = task.status === "failed" ? `Command exited with code ${task.resultExitCode ?? "null"}` : null;
|
|
1019
1171
|
persistRetainedRun(task);
|
|
1020
|
-
activeRuns.delete(task.id);
|
|
1021
1172
|
void removeRunTask(task.id).catch(() => undefined);
|
|
1173
|
+
void releaseRunStartSlot({ runId: task.id, sessionId: task.sessionId }).catch(() => undefined);
|
|
1022
1174
|
void prepared.codexAuthCleanup().catch(() => undefined);
|
|
1023
1175
|
writeRunStatus(task.id, `completed status=${task.status} exitCode=${task.resultExitCode ?? "null"} signal=${task.resultSignal ?? "null"}`);
|
|
1024
1176
|
});
|
|
1025
|
-
activeRuns.set(task.id, { task, child, requestCancel });
|
|
1026
1177
|
persistRetainedRun(task);
|
|
1027
1178
|
void persistRunTask(task).catch(() => undefined);
|
|
1028
1179
|
writeRunStatus(task.id, `started requestId=${args.requestId} cwd=${prepared.taskWorkspace}`);
|
|
@@ -1038,45 +1189,17 @@ function normalizeCodexModel(value) {
|
|
|
1038
1189
|
const normalized = typeof value === "string" ? value.trim() : "";
|
|
1039
1190
|
return normalized || "gpt-5.4";
|
|
1040
1191
|
}
|
|
1041
|
-
function
|
|
1042
|
-
const
|
|
1043
|
-
const responseSubject = typeof args.request.responseSubject === "string" ? args.request.responseSubject.trim() : "";
|
|
1044
|
-
const requestAgentId = typeof args.request.agentId === "string" ? args.request.agentId.trim() : "";
|
|
1045
|
-
const runId = typeof args.request.runId === "string" ? args.request.runId.trim() : "";
|
|
1046
|
-
const prompt = typeof args.request.prompt === "string" ? args.request.prompt.trim() : "";
|
|
1047
|
-
if (!requestId || !responseSubject || !requestAgentId || requestAgentId !== args.agentId || !runId || !prompt) {
|
|
1048
|
-
throw new Error("invalid codex rpc request");
|
|
1049
|
-
}
|
|
1050
|
-
return {
|
|
1051
|
-
requestId,
|
|
1052
|
-
responseSubject,
|
|
1053
|
-
runId,
|
|
1054
|
-
prompt,
|
|
1055
|
-
sessionId: typeof args.request.sessionId === "string" && args.request.sessionId.trim() ? args.request.sessionId.trim() : null,
|
|
1056
|
-
cwd: typeof args.request.cwd === "string" && args.request.cwd.trim() ? args.request.cwd.trim() : null,
|
|
1057
|
-
model: normalizeCodexModel(args.request.model),
|
|
1058
|
-
runtimeEnvPatch: normalizeEnvPatch(args.request.runtimeEnvPatch),
|
|
1059
|
-
codexAuthBundle: normalizeShellRpcCodexAuthBundle(args.request.codexAuth),
|
|
1060
|
-
};
|
|
1061
|
-
}
|
|
1062
|
-
function buildManagedCodexCommand(args) {
|
|
1192
|
+
function buildManagedCodexArgs(args) {
|
|
1193
|
+
const promptArgs = ["--", args.prompt];
|
|
1063
1194
|
const fixedArgs = ["--dangerously-bypass-approvals-and-sandbox"];
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
` ${direct}`,
|
|
1073
|
-
"fi",
|
|
1074
|
-
fallback,
|
|
1075
|
-
].join("\n");
|
|
1076
|
-
return `bash -lc ${shellSingleQuote(script)}`;
|
|
1077
|
-
}
|
|
1078
|
-
function publishCodexRpcResponse(args) {
|
|
1079
|
-
args.nc.publish(args.responseSubject, codexRpcCodec.encode(JSON.stringify(args.payload)));
|
|
1195
|
+
return [
|
|
1196
|
+
...fixedArgs,
|
|
1197
|
+
"--model",
|
|
1198
|
+
args.model,
|
|
1199
|
+
...(args.sessionId
|
|
1200
|
+
? ["exec", "resume", "--json", args.sessionId, ...promptArgs]
|
|
1201
|
+
: ["exec", "--json", ...promptArgs]),
|
|
1202
|
+
];
|
|
1080
1203
|
}
|
|
1081
1204
|
function buildLocalCodexCliCommand(args) {
|
|
1082
1205
|
const quotedArgs = args.map(shellSingleQuote).join(" ");
|
|
@@ -1090,6 +1213,34 @@ function buildLocalCodexCliCommand(args) {
|
|
|
1090
1213
|
].join("\n");
|
|
1091
1214
|
return `bash -lc ${shellSingleQuote(script)}`;
|
|
1092
1215
|
}
|
|
1216
|
+
function hasDirectCodexBinary() {
|
|
1217
|
+
const result = spawnSync("bash", ["-lc", "command -v codex >/dev/null 2>&1"], {
|
|
1218
|
+
stdio: "ignore",
|
|
1219
|
+
});
|
|
1220
|
+
return result.status === 0;
|
|
1221
|
+
}
|
|
1222
|
+
function spawnManagedCodexCommand(args) {
|
|
1223
|
+
const env = {
|
|
1224
|
+
...args.env,
|
|
1225
|
+
DOER_AGENT_TOKEN: args.agentToken,
|
|
1226
|
+
};
|
|
1227
|
+
const child = hasDirectCodexBinary()
|
|
1228
|
+
? spawn("codex", args.codexArgs, {
|
|
1229
|
+
cwd: args.taskWorkspace,
|
|
1230
|
+
detached: process.platform !== "win32",
|
|
1231
|
+
env,
|
|
1232
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1233
|
+
})
|
|
1234
|
+
: spawn("npm", ["exec", "--yes", "--package", "doer-agent", "--", "codex", ...args.codexArgs], {
|
|
1235
|
+
cwd: args.taskWorkspace,
|
|
1236
|
+
detached: process.platform !== "win32",
|
|
1237
|
+
env,
|
|
1238
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1239
|
+
});
|
|
1240
|
+
child.stdout?.setEncoding("utf8");
|
|
1241
|
+
child.stderr?.setEncoding("utf8");
|
|
1242
|
+
return child;
|
|
1243
|
+
}
|
|
1093
1244
|
async function runLocalCodexCli(args, timeoutMs) {
|
|
1094
1245
|
const command = buildLocalCodexCliCommand(args);
|
|
1095
1246
|
const workspaceRoot = workspaceRootOverride ?? (process.env.WORKSPACE?.trim() || process.cwd());
|
|
@@ -1482,70 +1633,6 @@ function subscribeToCodexAuthRpc(args) {
|
|
|
1482
1633
|
});
|
|
1483
1634
|
writeAgentInfo(`codex auth rpc subscribed subject=${subject}`);
|
|
1484
1635
|
}
|
|
1485
|
-
async function handleCodexRpcMessage(args) {
|
|
1486
|
-
let requestId = "unknown";
|
|
1487
|
-
let responseSubject = "";
|
|
1488
|
-
try {
|
|
1489
|
-
const payload = JSON.parse(codexRpcCodec.decode(args.msg.data));
|
|
1490
|
-
const request = normalizeCodexRpcRequest({ request: payload, agentId: args.agentId });
|
|
1491
|
-
requestId = request.requestId;
|
|
1492
|
-
responseSubject = request.responseSubject;
|
|
1493
|
-
const task = await startManagedRun({
|
|
1494
|
-
requestId,
|
|
1495
|
-
runId: request.runId,
|
|
1496
|
-
serverBaseUrl: args.serverBaseUrl,
|
|
1497
|
-
userId: args.userId,
|
|
1498
|
-
agentId: args.agentId,
|
|
1499
|
-
sessionId: request.sessionId,
|
|
1500
|
-
command: buildManagedCodexCommand({
|
|
1501
|
-
prompt: request.prompt,
|
|
1502
|
-
sessionId: request.sessionId,
|
|
1503
|
-
model: request.model,
|
|
1504
|
-
}),
|
|
1505
|
-
cwd: request.cwd,
|
|
1506
|
-
runtimeEnvPatch: request.runtimeEnvPatch,
|
|
1507
|
-
codexAuthBundle: request.codexAuthBundle,
|
|
1508
|
-
agentToken: args.agentToken,
|
|
1509
|
-
});
|
|
1510
|
-
publishCodexRpcResponse({
|
|
1511
|
-
nc: args.jetstream.nc,
|
|
1512
|
-
responseSubject,
|
|
1513
|
-
payload: { requestId, ok: true, task },
|
|
1514
|
-
});
|
|
1515
|
-
}
|
|
1516
|
-
catch (error) {
|
|
1517
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1518
|
-
if (responseSubject) {
|
|
1519
|
-
publishCodexRpcResponse({
|
|
1520
|
-
nc: args.jetstream.nc,
|
|
1521
|
-
responseSubject,
|
|
1522
|
-
payload: { requestId, ok: false, error: message },
|
|
1523
|
-
});
|
|
1524
|
-
}
|
|
1525
|
-
writeAgentError(`codex rpc failed requestId=${requestId} error=${message}`);
|
|
1526
|
-
}
|
|
1527
|
-
}
|
|
1528
|
-
function subscribeToCodexRpc(args) {
|
|
1529
|
-
const subject = buildAgentCodexRpcSubject(args.userId, args.agentId);
|
|
1530
|
-
args.jetstream.nc.subscribe(subject, {
|
|
1531
|
-
callback: (error, msg) => {
|
|
1532
|
-
if (error) {
|
|
1533
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1534
|
-
writeAgentError(`codex rpc subscription error: ${message}`);
|
|
1535
|
-
return;
|
|
1536
|
-
}
|
|
1537
|
-
void handleCodexRpcMessage({
|
|
1538
|
-
msg,
|
|
1539
|
-
jetstream: args.jetstream,
|
|
1540
|
-
serverBaseUrl: args.serverBaseUrl,
|
|
1541
|
-
userId: args.userId,
|
|
1542
|
-
agentId: args.agentId,
|
|
1543
|
-
agentToken: args.agentToken,
|
|
1544
|
-
});
|
|
1545
|
-
},
|
|
1546
|
-
});
|
|
1547
|
-
writeAgentInfo(`codex rpc subscribed subject=${subject}`);
|
|
1548
|
-
}
|
|
1549
1636
|
function runLocalCommand(command, args, cwd) {
|
|
1550
1637
|
return new Promise((resolve, reject) => {
|
|
1551
1638
|
const child = spawn(command, args, {
|
|
@@ -1870,39 +1957,66 @@ async function handleRunRpcMessage(args) {
|
|
|
1870
1957
|
requestId = request.requestId;
|
|
1871
1958
|
responseSubject = request.responseSubject;
|
|
1872
1959
|
if (request.action === "start") {
|
|
1873
|
-
const
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1960
|
+
const runId = request.runId ?? requestId;
|
|
1961
|
+
await claimRunStartSlot({ runId, sessionId: request.sessionId });
|
|
1962
|
+
try {
|
|
1963
|
+
const task = await startManagedRun({
|
|
1964
|
+
requestId,
|
|
1965
|
+
runId,
|
|
1966
|
+
serverBaseUrl: args.serverBaseUrl,
|
|
1967
|
+
userId: args.userId,
|
|
1968
|
+
agentId: args.agentId,
|
|
1969
|
+
sessionId: request.sessionId,
|
|
1970
|
+
codexArgs: buildManagedCodexArgs({
|
|
1971
|
+
prompt: request.prompt ?? "",
|
|
1972
|
+
sessionId: request.sessionId,
|
|
1973
|
+
model: request.model,
|
|
1974
|
+
}),
|
|
1975
|
+
cwd: request.cwd,
|
|
1976
|
+
runtimeEnvPatch: request.runtimeEnvPatch,
|
|
1977
|
+
codexAuthBundle: request.codexAuthBundle,
|
|
1978
|
+
agentToken: args.agentToken,
|
|
1979
|
+
});
|
|
1980
|
+
publishRunRpcResponse({ nc: args.jetstream.nc, responseSubject, payload: { requestId, ok: true, task } });
|
|
1981
|
+
}
|
|
1982
|
+
catch (error) {
|
|
1983
|
+
await releaseRunStartSlot({ runId, sessionId: request.sessionId }).catch(() => undefined);
|
|
1984
|
+
throw error;
|
|
1985
|
+
}
|
|
1887
1986
|
return;
|
|
1888
1987
|
}
|
|
1889
1988
|
if (request.action === "list") {
|
|
1890
|
-
const
|
|
1891
|
-
const
|
|
1892
|
-
const
|
|
1989
|
+
const persisted = await listPersistedRunTasks();
|
|
1990
|
+
const mergedById = new Map();
|
|
1991
|
+
for (const task of persisted) {
|
|
1992
|
+
mergedById.set(task.id, cloneRunTask(task));
|
|
1993
|
+
}
|
|
1994
|
+
for (const task of retainedRuns.values()) {
|
|
1995
|
+
if (!mergedById.has(task.id)) {
|
|
1996
|
+
mergedById.set(task.id, cloneRunTask(task));
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
const merged = [...mergedById.values()]
|
|
1893
2000
|
.sort((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt))
|
|
1894
2001
|
.slice(0, request.limit);
|
|
1895
2002
|
publishRunRpcResponse({ nc: args.jetstream.nc, responseSubject, payload: { requestId, ok: true, tasks: merged } });
|
|
1896
2003
|
return;
|
|
1897
2004
|
}
|
|
1898
|
-
const stored = request.runId ? getStoredRun(request.runId) : null;
|
|
2005
|
+
const stored = request.runId ? await getStoredRun(request.runId) : null;
|
|
1899
2006
|
if (!stored || stored.agentId !== args.agentId || stored.userId !== args.userId) {
|
|
1900
2007
|
throw new Error("Run not found");
|
|
1901
2008
|
}
|
|
1902
2009
|
if (request.action === "cancel") {
|
|
1903
|
-
const
|
|
1904
|
-
|
|
1905
|
-
|
|
2010
|
+
const target = stored;
|
|
2011
|
+
if (target.processPid === null) {
|
|
2012
|
+
throw new Error("Run pid not found");
|
|
2013
|
+
}
|
|
2014
|
+
target.cancelRequested = true;
|
|
2015
|
+
target.updatedAt = formatLocalTimestamp();
|
|
2016
|
+
await persistRunTask(target);
|
|
2017
|
+
writeRunStatus(target.id, `cancel requested pid=${target.processPid}`);
|
|
2018
|
+
sendSignalToPid(target.processPid, "SIGINT");
|
|
2019
|
+
const task = cloneRunTask(target);
|
|
1906
2020
|
publishRunRpcResponse({ nc: args.jetstream.nc, responseSubject, payload: { requestId, ok: true, task } });
|
|
1907
2021
|
return;
|
|
1908
2022
|
}
|
|
@@ -1978,21 +2092,17 @@ function sendSignalToTaskProcess(child, signal) {
|
|
|
1978
2092
|
// noop
|
|
1979
2093
|
}
|
|
1980
2094
|
}
|
|
1981
|
-
function
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
}
|
|
1991
|
-
catch (error) {
|
|
1992
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1993
|
-
writeAgentError(`task cancel request failed taskId=${taskId} via=${reason}: ${message}`);
|
|
1994
|
-
return false;
|
|
2095
|
+
function sendSignalToPid(pid, signal) {
|
|
2096
|
+
if (process.platform !== "win32") {
|
|
2097
|
+
try {
|
|
2098
|
+
process.kill(-pid, signal);
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
2101
|
+
catch {
|
|
2102
|
+
// Fall back to direct pid signaling.
|
|
2103
|
+
}
|
|
1995
2104
|
}
|
|
2105
|
+
process.kill(pid, signal);
|
|
1996
2106
|
}
|
|
1997
2107
|
function resolveLogTimeZone() {
|
|
1998
2108
|
const configured = process.env.DOER_AGENT_LOG_TIMEZONE?.trim() || process.env.TZ?.trim();
|
|
@@ -2124,9 +2234,6 @@ function resolveTaskWorkspace(rawCwd) {
|
|
|
2124
2234
|
function buildAgentFsRpcSubject(userId, agentId) {
|
|
2125
2235
|
return `doer.agent.fs.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
|
|
2126
2236
|
}
|
|
2127
|
-
function buildAgentShellRpcSubject(userId, agentId) {
|
|
2128
|
-
return `doer.agent.shell.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
|
|
2129
|
-
}
|
|
2130
2237
|
function normalizeFsRpcPath(rawPath) {
|
|
2131
2238
|
const root = workspaceRootOverride ?? (process.env.WORKSPACE?.trim() || process.cwd());
|
|
2132
2239
|
const raw = typeof rawPath === "string" && rawPath.trim() ? rawPath.trim() : ".";
|
|
@@ -2458,6 +2565,71 @@ function resolveSessionFilePath(filePath) {
|
|
|
2458
2565
|
function isObjectRecord(value) {
|
|
2459
2566
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
2460
2567
|
}
|
|
2568
|
+
const SESSION_RPC_BLOB_KEYS = new Set([
|
|
2569
|
+
"image_url",
|
|
2570
|
+
"image_base64",
|
|
2571
|
+
"content_base64",
|
|
2572
|
+
"file_data",
|
|
2573
|
+
"bytes",
|
|
2574
|
+
"data",
|
|
2575
|
+
]);
|
|
2576
|
+
function isInlineBlobString(value) {
|
|
2577
|
+
const trimmed = value.trim();
|
|
2578
|
+
if (!trimmed) {
|
|
2579
|
+
return false;
|
|
2580
|
+
}
|
|
2581
|
+
return trimmed.startsWith("data:") || trimmed.includes(";base64,");
|
|
2582
|
+
}
|
|
2583
|
+
function buildInlineBlobMarker(value) {
|
|
2584
|
+
const trimmed = value.trim();
|
|
2585
|
+
if (trimmed.startsWith("data:")) {
|
|
2586
|
+
const mimeEnd = trimmed.indexOf(";");
|
|
2587
|
+
const mimeType = mimeEnd > 5 ? trimmed.slice(5, mimeEnd) : "";
|
|
2588
|
+
if (mimeType) {
|
|
2589
|
+
return `[inline blob omitted: ${mimeType}]`;
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
return "[inline blob omitted]";
|
|
2593
|
+
}
|
|
2594
|
+
function sanitizeSessionRpcPayload(value) {
|
|
2595
|
+
if (typeof value === "string") {
|
|
2596
|
+
return value;
|
|
2597
|
+
}
|
|
2598
|
+
if (Array.isArray(value)) {
|
|
2599
|
+
return value.map((entry) => sanitizeSessionRpcPayload(entry));
|
|
2600
|
+
}
|
|
2601
|
+
if (!isObjectRecord(value)) {
|
|
2602
|
+
return value;
|
|
2603
|
+
}
|
|
2604
|
+
const sanitized = {};
|
|
2605
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
2606
|
+
if (SESSION_RPC_BLOB_KEYS.has(key) && typeof entry === "string" && isInlineBlobString(entry)) {
|
|
2607
|
+
sanitized[key] = buildInlineBlobMarker(entry);
|
|
2608
|
+
continue;
|
|
2609
|
+
}
|
|
2610
|
+
sanitized[key] = sanitizeSessionRpcPayload(entry);
|
|
2611
|
+
}
|
|
2612
|
+
return sanitized;
|
|
2613
|
+
}
|
|
2614
|
+
function sanitizeSessionRpcRawLine(line) {
|
|
2615
|
+
const trimmed = line.trim();
|
|
2616
|
+
if (!trimmed.startsWith("{")) {
|
|
2617
|
+
return line;
|
|
2618
|
+
}
|
|
2619
|
+
try {
|
|
2620
|
+
const parsed = JSON.parse(line);
|
|
2621
|
+
if (!isObjectRecord(parsed) || !isObjectRecord(parsed.payload) || parsed.type !== "response_item") {
|
|
2622
|
+
return line;
|
|
2623
|
+
}
|
|
2624
|
+
return JSON.stringify({
|
|
2625
|
+
...parsed,
|
|
2626
|
+
payload: sanitizeSessionRpcPayload(parsed.payload),
|
|
2627
|
+
});
|
|
2628
|
+
}
|
|
2629
|
+
catch {
|
|
2630
|
+
return line;
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2461
2633
|
function toTrimmedStringOrNull(value) {
|
|
2462
2634
|
if (typeof value !== "string") {
|
|
2463
2635
|
return null;
|
|
@@ -2734,7 +2906,9 @@ async function getAgentSessionRawRows(args) {
|
|
|
2734
2906
|
const sinceLine = Math.max(0, Math.floor(args.sinceLine));
|
|
2735
2907
|
const beforeRowId = args.beforeRowId && args.beforeRowId > 0 ? Math.floor(args.beforeRowId) : null;
|
|
2736
2908
|
const maxRawRows = 200;
|
|
2737
|
-
const
|
|
2909
|
+
const maxSelectionBytes = 120_000;
|
|
2910
|
+
const maxLineSelectionBytes = 4_096;
|
|
2911
|
+
const maxReadBytes = 2_000_000;
|
|
2738
2912
|
if (totalLines === 0) {
|
|
2739
2913
|
return {
|
|
2740
2914
|
rawRows: [],
|
|
@@ -2752,46 +2926,64 @@ async function getAgentSessionRawRows(args) {
|
|
|
2752
2926
|
endLineIndex = Math.max(0, Math.min(totalLines, beforeRowId - 1));
|
|
2753
2927
|
startLineIndex = endLineIndex;
|
|
2754
2928
|
let collectedRows = 0;
|
|
2755
|
-
let
|
|
2929
|
+
let collectedSelectionBytes = 0;
|
|
2930
|
+
let collectedReadBytes = 0;
|
|
2756
2931
|
while (startLineIndex > 0 && collectedRows < maxRawRows) {
|
|
2757
2932
|
const nextIndex = startLineIndex - 1;
|
|
2758
|
-
const
|
|
2759
|
-
|
|
2933
|
+
const nextReadBytes = getLineSpanBytes(nextIndex);
|
|
2934
|
+
const nextSelectionBytes = Math.min(nextReadBytes, maxLineSelectionBytes);
|
|
2935
|
+
if (collectedRows > 0 && collectedSelectionBytes + nextSelectionBytes > maxSelectionBytes) {
|
|
2936
|
+
break;
|
|
2937
|
+
}
|
|
2938
|
+
if (collectedRows > 0 && collectedReadBytes + nextReadBytes > maxReadBytes) {
|
|
2760
2939
|
break;
|
|
2761
2940
|
}
|
|
2762
2941
|
startLineIndex = nextIndex;
|
|
2763
2942
|
collectedRows += 1;
|
|
2764
|
-
|
|
2943
|
+
collectedSelectionBytes += nextSelectionBytes;
|
|
2944
|
+
collectedReadBytes += nextReadBytes;
|
|
2765
2945
|
}
|
|
2766
2946
|
}
|
|
2767
2947
|
else if (sinceLine > 0) {
|
|
2768
2948
|
startLineIndex = Math.min(totalLines, sinceLine);
|
|
2769
2949
|
endLineIndex = startLineIndex;
|
|
2770
2950
|
let collectedRows = 0;
|
|
2771
|
-
let
|
|
2951
|
+
let collectedSelectionBytes = 0;
|
|
2952
|
+
let collectedReadBytes = 0;
|
|
2772
2953
|
while (endLineIndex < totalLines && collectedRows < maxRawRows) {
|
|
2773
|
-
const
|
|
2774
|
-
|
|
2954
|
+
const nextReadBytes = getLineSpanBytes(endLineIndex);
|
|
2955
|
+
const nextSelectionBytes = Math.min(nextReadBytes, maxLineSelectionBytes);
|
|
2956
|
+
if (collectedRows > 0 && collectedSelectionBytes + nextSelectionBytes > maxSelectionBytes) {
|
|
2957
|
+
break;
|
|
2958
|
+
}
|
|
2959
|
+
if (collectedRows > 0 && collectedReadBytes + nextReadBytes > maxReadBytes) {
|
|
2775
2960
|
break;
|
|
2776
2961
|
}
|
|
2777
2962
|
endLineIndex += 1;
|
|
2778
2963
|
collectedRows += 1;
|
|
2779
|
-
|
|
2964
|
+
collectedSelectionBytes += nextSelectionBytes;
|
|
2965
|
+
collectedReadBytes += nextReadBytes;
|
|
2780
2966
|
}
|
|
2781
2967
|
}
|
|
2782
2968
|
else {
|
|
2783
2969
|
startLineIndex = totalLines;
|
|
2784
2970
|
let collectedRows = 0;
|
|
2785
|
-
let
|
|
2971
|
+
let collectedSelectionBytes = 0;
|
|
2972
|
+
let collectedReadBytes = 0;
|
|
2786
2973
|
while (startLineIndex > 0 && collectedRows < maxRawRows) {
|
|
2787
2974
|
const nextIndex = startLineIndex - 1;
|
|
2788
|
-
const
|
|
2789
|
-
|
|
2975
|
+
const nextReadBytes = getLineSpanBytes(nextIndex);
|
|
2976
|
+
const nextSelectionBytes = Math.min(nextReadBytes, maxLineSelectionBytes);
|
|
2977
|
+
if (collectedRows > 0 && collectedSelectionBytes + nextSelectionBytes > maxSelectionBytes) {
|
|
2978
|
+
break;
|
|
2979
|
+
}
|
|
2980
|
+
if (collectedRows > 0 && collectedReadBytes + nextReadBytes > maxReadBytes) {
|
|
2790
2981
|
break;
|
|
2791
2982
|
}
|
|
2792
2983
|
startLineIndex = nextIndex;
|
|
2793
2984
|
collectedRows += 1;
|
|
2794
|
-
|
|
2985
|
+
collectedSelectionBytes += nextSelectionBytes;
|
|
2986
|
+
collectedReadBytes += nextReadBytes;
|
|
2795
2987
|
}
|
|
2796
2988
|
}
|
|
2797
2989
|
if (startLineIndex >= endLineIndex) {
|
|
@@ -2824,7 +3016,7 @@ async function getAgentSessionRawRows(args) {
|
|
|
2824
3016
|
if (line.trim()) {
|
|
2825
3017
|
rawRows.push({
|
|
2826
3018
|
id: lineNumber,
|
|
2827
|
-
raw: line,
|
|
3019
|
+
raw: sanitizeSessionRpcRawLine(line),
|
|
2828
3020
|
});
|
|
2829
3021
|
}
|
|
2830
3022
|
lineNumber += 1;
|
|
@@ -3055,46 +3247,6 @@ function subscribeToFsRpc(args) {
|
|
|
3055
3247
|
});
|
|
3056
3248
|
writeAgentInfo(`fs rpc subscribed subject=${subject}`);
|
|
3057
3249
|
}
|
|
3058
|
-
function normalizeShellRpcRequest(args) {
|
|
3059
|
-
const requestId = typeof args.request.requestId === "string" ? args.request.requestId.trim() : "";
|
|
3060
|
-
if (!requestId) {
|
|
3061
|
-
throw new Error("missing requestId");
|
|
3062
|
-
}
|
|
3063
|
-
const requestAgentId = typeof args.request.agentId === "string" ? args.request.agentId.trim() : "";
|
|
3064
|
-
if (!requestAgentId) {
|
|
3065
|
-
throw new Error("missing agentId");
|
|
3066
|
-
}
|
|
3067
|
-
if (requestAgentId !== args.agentId) {
|
|
3068
|
-
throw new Error("agent id mismatch");
|
|
3069
|
-
}
|
|
3070
|
-
const kind = args.request.kind === "apply_patch" ? "apply_patch" : "shell";
|
|
3071
|
-
const command = typeof args.request.command === "string" ? args.request.command.trim() : "";
|
|
3072
|
-
const patch = typeof args.request.patch === "string" ? args.request.patch : "";
|
|
3073
|
-
if (kind === "shell" && !command) {
|
|
3074
|
-
throw new Error("missing command");
|
|
3075
|
-
}
|
|
3076
|
-
if (kind === "apply_patch" && !patch.trim()) {
|
|
3077
|
-
throw new Error("missing patch");
|
|
3078
|
-
}
|
|
3079
|
-
const responseSubject = typeof args.request.responseSubject === "string" ? args.request.responseSubject.trim() : "";
|
|
3080
|
-
if (!responseSubject) {
|
|
3081
|
-
throw new Error("missing responseSubject");
|
|
3082
|
-
}
|
|
3083
|
-
const cwd = typeof args.request.cwd === "string" && args.request.cwd.trim() ? args.request.cwd.trim() : null;
|
|
3084
|
-
const timeoutRaw = Number(args.request.timeoutMs);
|
|
3085
|
-
const timeoutMs = Number.isFinite(timeoutRaw) ? Math.max(1000, Math.min(Math.floor(timeoutRaw), 300000)) : 30000;
|
|
3086
|
-
return {
|
|
3087
|
-
kind,
|
|
3088
|
-
requestId,
|
|
3089
|
-
command: kind === "shell" ? command : null,
|
|
3090
|
-
patch: kind === "apply_patch" ? patch : null,
|
|
3091
|
-
cwd,
|
|
3092
|
-
timeoutMs,
|
|
3093
|
-
responseSubject,
|
|
3094
|
-
runtimeEnvPatch: normalizeEnvPatch(args.request.runtimeEnvPatch),
|
|
3095
|
-
codexAuthBundle: normalizeShellRpcCodexAuthBundle(args.request.codexAuth),
|
|
3096
|
-
};
|
|
3097
|
-
}
|
|
3098
3250
|
function normalizeShellRpcCodexAuthBundle(value) {
|
|
3099
3251
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3100
3252
|
return null;
|
|
@@ -3113,118 +3265,6 @@ function normalizeShellRpcCodexAuthBundle(value) {
|
|
|
3113
3265
|
apiKey: typeof row.apiKey === "string" || row.apiKey === null ? row.apiKey : undefined,
|
|
3114
3266
|
};
|
|
3115
3267
|
}
|
|
3116
|
-
function publishShellRpcResponse(args) {
|
|
3117
|
-
args.nc.publish(args.responseSubject, shellRpcCodec.encode(JSON.stringify(args.payload)));
|
|
3118
|
-
}
|
|
3119
|
-
async function handleShellRpcMessage(args) {
|
|
3120
|
-
let requestId = "unknown";
|
|
3121
|
-
let responseSubject = "";
|
|
3122
|
-
let stdout = "";
|
|
3123
|
-
let stderr = "";
|
|
3124
|
-
try {
|
|
3125
|
-
const payload = JSON.parse(shellRpcCodec.decode(args.msg.data));
|
|
3126
|
-
const request = normalizeShellRpcRequest({ request: payload, agentId: args.agentId });
|
|
3127
|
-
requestId = request.requestId;
|
|
3128
|
-
responseSubject = request.responseSubject;
|
|
3129
|
-
const startedAtMs = Date.now();
|
|
3130
|
-
const prepared = await prepareCommandExecution({
|
|
3131
|
-
cwd: request.cwd,
|
|
3132
|
-
userId: args.userId,
|
|
3133
|
-
taskId: request.requestId,
|
|
3134
|
-
codexAuthBundle: request.codexAuthBundle,
|
|
3135
|
-
});
|
|
3136
|
-
const child = spawnPreparedCommand({
|
|
3137
|
-
kind: request.kind,
|
|
3138
|
-
command: request.command,
|
|
3139
|
-
patch: request.patch,
|
|
3140
|
-
shellPath: prepared.shellPath,
|
|
3141
|
-
taskWorkspace: prepared.taskWorkspace,
|
|
3142
|
-
env: prepared.env,
|
|
3143
|
-
agentToken: args.agentToken,
|
|
3144
|
-
});
|
|
3145
|
-
writeRpcStatus(requestId, `started kind=${request.kind} cwd=${prepared.taskWorkspace} shell=${request.kind === "shell" ? prepared.shellPath : "apply_patch"}`);
|
|
3146
|
-
child.stdout.on("data", (chunk) => {
|
|
3147
|
-
stdout += chunk;
|
|
3148
|
-
writeRpcStream(requestId, "stdout", chunk);
|
|
3149
|
-
});
|
|
3150
|
-
child.stderr.on("data", (chunk) => {
|
|
3151
|
-
stderr += chunk;
|
|
3152
|
-
writeRpcStream(requestId, "stderr", chunk);
|
|
3153
|
-
});
|
|
3154
|
-
let timedOut = false;
|
|
3155
|
-
const timeout = setTimeout(() => {
|
|
3156
|
-
timedOut = true;
|
|
3157
|
-
sendSignalToTaskProcess(child, "SIGTERM");
|
|
3158
|
-
setTimeout(() => {
|
|
3159
|
-
sendSignalToTaskProcess(child, "SIGKILL");
|
|
3160
|
-
}, 1000).unref?.();
|
|
3161
|
-
}, request.timeoutMs);
|
|
3162
|
-
timeout.unref?.();
|
|
3163
|
-
const result = await new Promise((resolve, reject) => {
|
|
3164
|
-
child.once("error", reject);
|
|
3165
|
-
child.once("close", (code, signal) => {
|
|
3166
|
-
resolve({ exitCode: typeof code === "number" ? code : null, signal });
|
|
3167
|
-
});
|
|
3168
|
-
}).finally(() => {
|
|
3169
|
-
clearTimeout(timeout);
|
|
3170
|
-
});
|
|
3171
|
-
await prepared.codexAuthCleanup().catch(() => undefined);
|
|
3172
|
-
publishShellRpcResponse({
|
|
3173
|
-
nc: args.jetstream.nc,
|
|
3174
|
-
responseSubject,
|
|
3175
|
-
payload: {
|
|
3176
|
-
requestId,
|
|
3177
|
-
ok: !timedOut,
|
|
3178
|
-
exitCode: result.exitCode,
|
|
3179
|
-
signal: result.signal,
|
|
3180
|
-
stdout,
|
|
3181
|
-
stderr,
|
|
3182
|
-
...(timedOut ? { error: `Command timed out after ${request.timeoutMs}ms` } : {}),
|
|
3183
|
-
},
|
|
3184
|
-
});
|
|
3185
|
-
writeRpcStatus(requestId, `${timedOut ? "timed_out" : "completed"} exitCode=${result.exitCode ?? "null"} signal=${result.signal ?? "null"} durationMs=${Date.now() - startedAtMs}`);
|
|
3186
|
-
}
|
|
3187
|
-
catch (error) {
|
|
3188
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
3189
|
-
if (responseSubject) {
|
|
3190
|
-
publishShellRpcResponse({
|
|
3191
|
-
nc: args.jetstream.nc,
|
|
3192
|
-
responseSubject,
|
|
3193
|
-
payload: {
|
|
3194
|
-
requestId,
|
|
3195
|
-
ok: false,
|
|
3196
|
-
exitCode: null,
|
|
3197
|
-
signal: null,
|
|
3198
|
-
stdout,
|
|
3199
|
-
stderr,
|
|
3200
|
-
error: message,
|
|
3201
|
-
},
|
|
3202
|
-
});
|
|
3203
|
-
}
|
|
3204
|
-
writeRpcStatus(requestId, `failed error=${message}`);
|
|
3205
|
-
writeAgentError(`shell rpc failed requestId=${requestId} error=${message}`);
|
|
3206
|
-
}
|
|
3207
|
-
}
|
|
3208
|
-
function subscribeToShellRpc(args) {
|
|
3209
|
-
const subject = buildAgentShellRpcSubject(args.userId, args.agentId);
|
|
3210
|
-
args.jetstream.nc.subscribe(subject, {
|
|
3211
|
-
callback: (error, msg) => {
|
|
3212
|
-
if (error) {
|
|
3213
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
3214
|
-
writeAgentError(`shell rpc subscription error: ${message}`);
|
|
3215
|
-
return;
|
|
3216
|
-
}
|
|
3217
|
-
void handleShellRpcMessage({
|
|
3218
|
-
msg,
|
|
3219
|
-
jetstream: args.jetstream,
|
|
3220
|
-
userId: args.userId,
|
|
3221
|
-
agentId: args.agentId,
|
|
3222
|
-
agentToken: args.agentToken,
|
|
3223
|
-
});
|
|
3224
|
-
},
|
|
3225
|
-
});
|
|
3226
|
-
writeAgentInfo(`shell rpc subscribed subject=${subject}`);
|
|
3227
|
-
}
|
|
3228
3268
|
async function postJson(url, body) {
|
|
3229
3269
|
const res = await fetch(url, {
|
|
3230
3270
|
method: "POST",
|
|
@@ -3472,36 +3512,6 @@ function spawnPreparedCommand(args) {
|
|
|
3472
3512
|
child.stderr.setEncoding("utf8");
|
|
3473
3513
|
return child;
|
|
3474
3514
|
}
|
|
3475
|
-
function createManagedCancellation(child) {
|
|
3476
|
-
let cancelStage1Timer = null;
|
|
3477
|
-
let cancelStage2Timer = null;
|
|
3478
|
-
let cancelSignalSent = false;
|
|
3479
|
-
return {
|
|
3480
|
-
requestCancel: () => {
|
|
3481
|
-
if (cancelSignalSent) {
|
|
3482
|
-
return;
|
|
3483
|
-
}
|
|
3484
|
-
cancelSignalSent = true;
|
|
3485
|
-
sendSignalToTaskProcess(child, "SIGINT");
|
|
3486
|
-
cancelStage1Timer = setTimeout(() => {
|
|
3487
|
-
sendSignalToTaskProcess(child, "SIGTERM");
|
|
3488
|
-
}, 1200);
|
|
3489
|
-
cancelStage1Timer.unref?.();
|
|
3490
|
-
cancelStage2Timer = setTimeout(() => {
|
|
3491
|
-
sendSignalToTaskProcess(child, "SIGKILL");
|
|
3492
|
-
}, 3500);
|
|
3493
|
-
cancelStage2Timer.unref?.();
|
|
3494
|
-
},
|
|
3495
|
-
clear: () => {
|
|
3496
|
-
if (cancelStage1Timer) {
|
|
3497
|
-
clearTimeout(cancelStage1Timer);
|
|
3498
|
-
}
|
|
3499
|
-
if (cancelStage2Timer) {
|
|
3500
|
-
clearTimeout(cancelStage2Timer);
|
|
3501
|
-
}
|
|
3502
|
-
},
|
|
3503
|
-
};
|
|
3504
|
-
}
|
|
3505
3515
|
async function runTask(args) {
|
|
3506
3516
|
activeTaskLogContext = {
|
|
3507
3517
|
jetstream: args.jetstream,
|
|
@@ -3595,7 +3605,6 @@ async function runTask(args) {
|
|
|
3595
3605
|
}, 3500);
|
|
3596
3606
|
cancelStage2Timer.unref?.();
|
|
3597
3607
|
};
|
|
3598
|
-
activeTaskCancelRequests.set(args.taskId, requestCancel);
|
|
3599
3608
|
child.stdout.on("data", (chunk) => {
|
|
3600
3609
|
writeTaskStream(args.taskId, "stdout", chunk);
|
|
3601
3610
|
const seq = reserveNextEventSeq(args.taskId);
|
|
@@ -3690,7 +3699,6 @@ async function runTask(args) {
|
|
|
3690
3699
|
writeAgentInfo(`task=${args.taskId} status=${status} exitCode=${typeof result.code === "number" ? result.code : "null"} signal=${result.signal ?? "null"}`);
|
|
3691
3700
|
}
|
|
3692
3701
|
finally {
|
|
3693
|
-
activeTaskCancelRequests.delete(args.taskId);
|
|
3694
3702
|
activeTaskLogContext = null;
|
|
3695
3703
|
await codexAuth?.cleanup().catch(() => undefined);
|
|
3696
3704
|
}
|
|
@@ -3794,24 +3802,11 @@ async function main() {
|
|
|
3794
3802
|
agentId: initialAgentId,
|
|
3795
3803
|
agentToken,
|
|
3796
3804
|
});
|
|
3797
|
-
subscribeToShellRpc({
|
|
3798
|
-
jetstream,
|
|
3799
|
-
userId,
|
|
3800
|
-
agentId: initialAgentId,
|
|
3801
|
-
agentToken,
|
|
3802
|
-
});
|
|
3803
3805
|
subscribeToSessionRpc({
|
|
3804
3806
|
jetstream,
|
|
3805
3807
|
userId,
|
|
3806
3808
|
agentId: initialAgentId,
|
|
3807
3809
|
});
|
|
3808
|
-
subscribeToCodexRpc({
|
|
3809
|
-
jetstream,
|
|
3810
|
-
serverBaseUrl,
|
|
3811
|
-
userId,
|
|
3812
|
-
agentId: initialAgentId,
|
|
3813
|
-
agentToken,
|
|
3814
|
-
});
|
|
3815
3810
|
subscribeToCodexAuthRpc({
|
|
3816
3811
|
jetstream,
|
|
3817
3812
|
userId,
|