doer-agent 0.2.5 → 0.2.6

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.
Files changed (2) hide show
  1. package/dist/agent.js +277 -151
  2. 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";
@@ -15,7 +15,6 @@ const fsRpcCodec = StringCodec();
15
15
  const shellRpcCodec = StringCodec();
16
16
  const runRpcCodec = StringCodec();
17
17
  const sessionRpcCodec = StringCodec();
18
- const codexRpcCodec = StringCodec();
19
18
  const codexAuthRpcCodec = StringCodec();
20
19
  const settingsRpcCodec = StringCodec();
21
20
  const gitRpcCodec = StringCodec();
@@ -35,9 +34,6 @@ function buildAgentRunRpcSubject(userId, agentId) {
35
34
  function buildAgentSessionRpcSubject(userId, agentId) {
36
35
  return `doer.agent.session.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
37
36
  }
38
- function buildAgentCodexRpcSubject(userId, agentId) {
39
- return `doer.agent.codex.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
40
- }
41
37
  function buildAgentCodexAuthRpcSubject(userId, agentId) {
42
38
  return `doer.agent.codex.auth.rpc.${sanitizeUserId(userId)}.${agentId.trim()}`;
43
39
  }
@@ -421,9 +417,11 @@ function normalizeRunRpcRequest(args) {
421
417
  throw new Error("missing responseSubject");
422
418
  }
423
419
  const runId = typeof args.request.runId === "string" && args.request.runId.trim() ? args.request.runId.trim() : null;
424
- const command = typeof args.request.command === "string" && args.request.command.trim() ? args.request.command.trim() : null;
425
- if (action === "start" && !command) {
426
- throw new Error("missing command");
420
+ const prompt = typeof args.request.prompt === "string" && args.request.prompt.trim() ? args.request.prompt.trim() : null;
421
+ const sessionId = typeof args.request.sessionId === "string" && args.request.sessionId.trim() ? args.request.sessionId.trim() : null;
422
+ const model = normalizeCodexModel(args.request.model);
423
+ if (action === "start" && !prompt) {
424
+ throw new Error("missing prompt");
427
425
  }
428
426
  if ((action === "get" || action === "cancel") && !runId) {
429
427
  throw new Error("missing runId");
@@ -437,7 +435,9 @@ function normalizeRunRpcRequest(args) {
437
435
  requestId,
438
436
  action,
439
437
  runId,
440
- command,
438
+ prompt,
439
+ sessionId,
440
+ model,
441
441
  cwd,
442
442
  responseSubject,
443
443
  sinceSeq,
@@ -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,6 +1009,13 @@ 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));
@@ -933,11 +1034,8 @@ async function startManagedRun(args) {
933
1034
  taskId: args.runId,
934
1035
  codexAuthBundle: args.codexAuthBundle,
935
1036
  });
936
- const child = spawnPreparedCommand({
937
- kind: "shell",
938
- command: args.command,
939
- patch: null,
940
- shellPath: prepared.shellPath,
1037
+ const child = spawnManagedCodexCommand({
1038
+ codexArgs: args.codexArgs,
941
1039
  taskWorkspace: prepared.taskWorkspace,
942
1040
  env: prepared.env,
943
1041
  agentToken: args.agentToken,
@@ -1000,6 +1098,7 @@ async function startManagedRun(args) {
1000
1098
  persistRetainedRun(task);
1001
1099
  activeRuns.delete(task.id);
1002
1100
  void removeRunTask(task.id).catch(() => undefined);
1101
+ void releaseRunStartSlot({ runId: task.id, sessionId: task.sessionId }).catch(() => undefined);
1003
1102
  void prepared.codexAuthCleanup().catch(() => undefined);
1004
1103
  writeRunStatus(task.id, `failed error=${message}`);
1005
1104
  });
@@ -1019,6 +1118,7 @@ async function startManagedRun(args) {
1019
1118
  persistRetainedRun(task);
1020
1119
  activeRuns.delete(task.id);
1021
1120
  void removeRunTask(task.id).catch(() => undefined);
1121
+ void releaseRunStartSlot({ runId: task.id, sessionId: task.sessionId }).catch(() => undefined);
1022
1122
  void prepared.codexAuthCleanup().catch(() => undefined);
1023
1123
  writeRunStatus(task.id, `completed status=${task.status} exitCode=${task.resultExitCode ?? "null"} signal=${task.resultSignal ?? "null"}`);
1024
1124
  });
@@ -1038,45 +1138,17 @@ function normalizeCodexModel(value) {
1038
1138
  const normalized = typeof value === "string" ? value.trim() : "";
1039
1139
  return normalized || "gpt-5.4";
1040
1140
  }
1041
- function normalizeCodexRpcRequest(args) {
1042
- const requestId = typeof args.request.requestId === "string" ? args.request.requestId.trim() : "";
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) {
1141
+ function buildManagedCodexArgs(args) {
1142
+ const promptArgs = ["--", args.prompt];
1063
1143
  const fixedArgs = ["--dangerously-bypass-approvals-and-sandbox"];
1064
- const codexArgs = args.sessionId
1065
- ? ["exec", "resume", "--json", args.sessionId, args.prompt]
1066
- : ["exec", "--json", args.prompt];
1067
- const commandArgs = [...fixedArgs, "--model", args.model, ...codexArgs].map(shellSingleQuote).join(" ");
1068
- const direct = `exec codex ${commandArgs}`;
1069
- const fallback = `exec npm exec --yes --package doer-agent -- codex ${commandArgs}`;
1070
- const script = [
1071
- "if command -v codex >/dev/null 2>&1; then",
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)));
1144
+ return [
1145
+ ...fixedArgs,
1146
+ "--model",
1147
+ args.model,
1148
+ ...(args.sessionId
1149
+ ? ["exec", "resume", "--json", args.sessionId, ...promptArgs]
1150
+ : ["exec", "--json", ...promptArgs]),
1151
+ ];
1080
1152
  }
1081
1153
  function buildLocalCodexCliCommand(args) {
1082
1154
  const quotedArgs = args.map(shellSingleQuote).join(" ");
@@ -1090,6 +1162,34 @@ function buildLocalCodexCliCommand(args) {
1090
1162
  ].join("\n");
1091
1163
  return `bash -lc ${shellSingleQuote(script)}`;
1092
1164
  }
1165
+ function hasDirectCodexBinary() {
1166
+ const result = spawnSync("bash", ["-lc", "command -v codex >/dev/null 2>&1"], {
1167
+ stdio: "ignore",
1168
+ });
1169
+ return result.status === 0;
1170
+ }
1171
+ function spawnManagedCodexCommand(args) {
1172
+ const env = {
1173
+ ...args.env,
1174
+ DOER_AGENT_TOKEN: args.agentToken,
1175
+ };
1176
+ const child = hasDirectCodexBinary()
1177
+ ? spawn("codex", args.codexArgs, {
1178
+ cwd: args.taskWorkspace,
1179
+ detached: process.platform !== "win32",
1180
+ env,
1181
+ stdio: ["ignore", "pipe", "pipe"],
1182
+ })
1183
+ : spawn("npm", ["exec", "--yes", "--package", "doer-agent", "--", "codex", ...args.codexArgs], {
1184
+ cwd: args.taskWorkspace,
1185
+ detached: process.platform !== "win32",
1186
+ env,
1187
+ stdio: ["ignore", "pipe", "pipe"],
1188
+ });
1189
+ child.stdout?.setEncoding("utf8");
1190
+ child.stderr?.setEncoding("utf8");
1191
+ return child;
1192
+ }
1093
1193
  async function runLocalCodexCli(args, timeoutMs) {
1094
1194
  const command = buildLocalCodexCliCommand(args);
1095
1195
  const workspaceRoot = workspaceRootOverride ?? (process.env.WORKSPACE?.trim() || process.cwd());
@@ -1482,70 +1582,6 @@ function subscribeToCodexAuthRpc(args) {
1482
1582
  });
1483
1583
  writeAgentInfo(`codex auth rpc subscribed subject=${subject}`);
1484
1584
  }
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
1585
  function runLocalCommand(command, args, cwd) {
1550
1586
  return new Promise((resolve, reject) => {
1551
1587
  const child = spawn(command, args, {
@@ -1870,20 +1906,32 @@ async function handleRunRpcMessage(args) {
1870
1906
  requestId = request.requestId;
1871
1907
  responseSubject = request.responseSubject;
1872
1908
  if (request.action === "start") {
1873
- const task = await startManagedRun({
1874
- requestId,
1875
- runId: request.runId ?? requestId,
1876
- serverBaseUrl: args.serverBaseUrl,
1877
- userId: args.userId,
1878
- agentId: args.agentId,
1879
- sessionId: null,
1880
- command: request.command ?? "",
1881
- cwd: request.cwd,
1882
- runtimeEnvPatch: request.runtimeEnvPatch,
1883
- codexAuthBundle: request.codexAuthBundle,
1884
- agentToken: args.agentToken,
1885
- });
1886
- publishRunRpcResponse({ nc: args.jetstream.nc, responseSubject, payload: { requestId, ok: true, task } });
1909
+ const runId = request.runId ?? requestId;
1910
+ await claimRunStartSlot({ runId, sessionId: request.sessionId });
1911
+ try {
1912
+ const task = await startManagedRun({
1913
+ requestId,
1914
+ runId,
1915
+ serverBaseUrl: args.serverBaseUrl,
1916
+ userId: args.userId,
1917
+ agentId: args.agentId,
1918
+ sessionId: request.sessionId,
1919
+ codexArgs: buildManagedCodexArgs({
1920
+ prompt: request.prompt ?? "",
1921
+ sessionId: request.sessionId,
1922
+ model: request.model,
1923
+ }),
1924
+ cwd: request.cwd,
1925
+ runtimeEnvPatch: request.runtimeEnvPatch,
1926
+ codexAuthBundle: request.codexAuthBundle,
1927
+ agentToken: args.agentToken,
1928
+ });
1929
+ publishRunRpcResponse({ nc: args.jetstream.nc, responseSubject, payload: { requestId, ok: true, task } });
1930
+ }
1931
+ catch (error) {
1932
+ await releaseRunStartSlot({ runId, sessionId: request.sessionId }).catch(() => undefined);
1933
+ throw error;
1934
+ }
1887
1935
  return;
1888
1936
  }
1889
1937
  if (request.action === "list") {
@@ -2458,6 +2506,71 @@ function resolveSessionFilePath(filePath) {
2458
2506
  function isObjectRecord(value) {
2459
2507
  return !!value && typeof value === "object" && !Array.isArray(value);
2460
2508
  }
2509
+ const SESSION_RPC_BLOB_KEYS = new Set([
2510
+ "image_url",
2511
+ "image_base64",
2512
+ "content_base64",
2513
+ "file_data",
2514
+ "bytes",
2515
+ "data",
2516
+ ]);
2517
+ function isInlineBlobString(value) {
2518
+ const trimmed = value.trim();
2519
+ if (!trimmed) {
2520
+ return false;
2521
+ }
2522
+ return trimmed.startsWith("data:") || trimmed.includes(";base64,");
2523
+ }
2524
+ function buildInlineBlobMarker(value) {
2525
+ const trimmed = value.trim();
2526
+ if (trimmed.startsWith("data:")) {
2527
+ const mimeEnd = trimmed.indexOf(";");
2528
+ const mimeType = mimeEnd > 5 ? trimmed.slice(5, mimeEnd) : "";
2529
+ if (mimeType) {
2530
+ return `[inline blob omitted: ${mimeType}]`;
2531
+ }
2532
+ }
2533
+ return "[inline blob omitted]";
2534
+ }
2535
+ function sanitizeSessionRpcPayload(value) {
2536
+ if (typeof value === "string") {
2537
+ return value;
2538
+ }
2539
+ if (Array.isArray(value)) {
2540
+ return value.map((entry) => sanitizeSessionRpcPayload(entry));
2541
+ }
2542
+ if (!isObjectRecord(value)) {
2543
+ return value;
2544
+ }
2545
+ const sanitized = {};
2546
+ for (const [key, entry] of Object.entries(value)) {
2547
+ if (SESSION_RPC_BLOB_KEYS.has(key) && typeof entry === "string" && isInlineBlobString(entry)) {
2548
+ sanitized[key] = buildInlineBlobMarker(entry);
2549
+ continue;
2550
+ }
2551
+ sanitized[key] = sanitizeSessionRpcPayload(entry);
2552
+ }
2553
+ return sanitized;
2554
+ }
2555
+ function sanitizeSessionRpcRawLine(line) {
2556
+ const trimmed = line.trim();
2557
+ if (!trimmed.startsWith("{")) {
2558
+ return line;
2559
+ }
2560
+ try {
2561
+ const parsed = JSON.parse(line);
2562
+ if (!isObjectRecord(parsed) || !isObjectRecord(parsed.payload) || parsed.type !== "response_item") {
2563
+ return line;
2564
+ }
2565
+ return JSON.stringify({
2566
+ ...parsed,
2567
+ payload: sanitizeSessionRpcPayload(parsed.payload),
2568
+ });
2569
+ }
2570
+ catch {
2571
+ return line;
2572
+ }
2573
+ }
2461
2574
  function toTrimmedStringOrNull(value) {
2462
2575
  if (typeof value !== "string") {
2463
2576
  return null;
@@ -2734,7 +2847,9 @@ async function getAgentSessionRawRows(args) {
2734
2847
  const sinceLine = Math.max(0, Math.floor(args.sinceLine));
2735
2848
  const beforeRowId = args.beforeRowId && args.beforeRowId > 0 ? Math.floor(args.beforeRowId) : null;
2736
2849
  const maxRawRows = 200;
2737
- const maxRawBytes = 120_000;
2850
+ const maxSelectionBytes = 120_000;
2851
+ const maxLineSelectionBytes = 4_096;
2852
+ const maxReadBytes = 2_000_000;
2738
2853
  if (totalLines === 0) {
2739
2854
  return {
2740
2855
  rawRows: [],
@@ -2752,46 +2867,64 @@ async function getAgentSessionRawRows(args) {
2752
2867
  endLineIndex = Math.max(0, Math.min(totalLines, beforeRowId - 1));
2753
2868
  startLineIndex = endLineIndex;
2754
2869
  let collectedRows = 0;
2755
- let collectedBytes = 0;
2870
+ let collectedSelectionBytes = 0;
2871
+ let collectedReadBytes = 0;
2756
2872
  while (startLineIndex > 0 && collectedRows < maxRawRows) {
2757
2873
  const nextIndex = startLineIndex - 1;
2758
- const nextBytes = getLineSpanBytes(nextIndex);
2759
- if (collectedRows > 0 && collectedBytes + nextBytes > maxRawBytes) {
2874
+ const nextReadBytes = getLineSpanBytes(nextIndex);
2875
+ const nextSelectionBytes = Math.min(nextReadBytes, maxLineSelectionBytes);
2876
+ if (collectedRows > 0 && collectedSelectionBytes + nextSelectionBytes > maxSelectionBytes) {
2877
+ break;
2878
+ }
2879
+ if (collectedRows > 0 && collectedReadBytes + nextReadBytes > maxReadBytes) {
2760
2880
  break;
2761
2881
  }
2762
2882
  startLineIndex = nextIndex;
2763
2883
  collectedRows += 1;
2764
- collectedBytes += nextBytes;
2884
+ collectedSelectionBytes += nextSelectionBytes;
2885
+ collectedReadBytes += nextReadBytes;
2765
2886
  }
2766
2887
  }
2767
2888
  else if (sinceLine > 0) {
2768
2889
  startLineIndex = Math.min(totalLines, sinceLine);
2769
2890
  endLineIndex = startLineIndex;
2770
2891
  let collectedRows = 0;
2771
- let collectedBytes = 0;
2892
+ let collectedSelectionBytes = 0;
2893
+ let collectedReadBytes = 0;
2772
2894
  while (endLineIndex < totalLines && collectedRows < maxRawRows) {
2773
- const nextBytes = getLineSpanBytes(endLineIndex);
2774
- if (collectedRows > 0 && collectedBytes + nextBytes > maxRawBytes) {
2895
+ const nextReadBytes = getLineSpanBytes(endLineIndex);
2896
+ const nextSelectionBytes = Math.min(nextReadBytes, maxLineSelectionBytes);
2897
+ if (collectedRows > 0 && collectedSelectionBytes + nextSelectionBytes > maxSelectionBytes) {
2898
+ break;
2899
+ }
2900
+ if (collectedRows > 0 && collectedReadBytes + nextReadBytes > maxReadBytes) {
2775
2901
  break;
2776
2902
  }
2777
2903
  endLineIndex += 1;
2778
2904
  collectedRows += 1;
2779
- collectedBytes += nextBytes;
2905
+ collectedSelectionBytes += nextSelectionBytes;
2906
+ collectedReadBytes += nextReadBytes;
2780
2907
  }
2781
2908
  }
2782
2909
  else {
2783
2910
  startLineIndex = totalLines;
2784
2911
  let collectedRows = 0;
2785
- let collectedBytes = 0;
2912
+ let collectedSelectionBytes = 0;
2913
+ let collectedReadBytes = 0;
2786
2914
  while (startLineIndex > 0 && collectedRows < maxRawRows) {
2787
2915
  const nextIndex = startLineIndex - 1;
2788
- const nextBytes = getLineSpanBytes(nextIndex);
2789
- if (collectedRows > 0 && collectedBytes + nextBytes > maxRawBytes) {
2916
+ const nextReadBytes = getLineSpanBytes(nextIndex);
2917
+ const nextSelectionBytes = Math.min(nextReadBytes, maxLineSelectionBytes);
2918
+ if (collectedRows > 0 && collectedSelectionBytes + nextSelectionBytes > maxSelectionBytes) {
2919
+ break;
2920
+ }
2921
+ if (collectedRows > 0 && collectedReadBytes + nextReadBytes > maxReadBytes) {
2790
2922
  break;
2791
2923
  }
2792
2924
  startLineIndex = nextIndex;
2793
2925
  collectedRows += 1;
2794
- collectedBytes += nextBytes;
2926
+ collectedSelectionBytes += nextSelectionBytes;
2927
+ collectedReadBytes += nextReadBytes;
2795
2928
  }
2796
2929
  }
2797
2930
  if (startLineIndex >= endLineIndex) {
@@ -2824,7 +2957,7 @@ async function getAgentSessionRawRows(args) {
2824
2957
  if (line.trim()) {
2825
2958
  rawRows.push({
2826
2959
  id: lineNumber,
2827
- raw: line,
2960
+ raw: sanitizeSessionRpcRawLine(line),
2828
2961
  });
2829
2962
  }
2830
2963
  lineNumber += 1;
@@ -3805,13 +3938,6 @@ async function main() {
3805
3938
  userId,
3806
3939
  agentId: initialAgentId,
3807
3940
  });
3808
- subscribeToCodexRpc({
3809
- jetstream,
3810
- serverBaseUrl,
3811
- userId,
3812
- agentId: initialAgentId,
3813
- agentToken,
3814
- });
3815
3941
  subscribeToCodexAuthRpc({
3816
3942
  jetstream,
3817
3943
  userId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doer-agent",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Reverse-polling agent runtime for doer",
5
5
  "type": "module",
6
6
  "main": "dist/agent.js",