doer-agent 0.5.4 → 0.5.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.
@@ -13,7 +13,6 @@ const SESSION_RPC_BLOB_KEYS = new Set([
13
13
  "bytes",
14
14
  "data",
15
15
  ]);
16
- const SESSION_RPC_MAX_STRING_CHARS = 512;
17
16
  function getSessionsRootPath(workspaceRoot) {
18
17
  return path.join(workspaceRoot, ".codex", "sessions");
19
18
  }
@@ -90,19 +89,27 @@ function buildInlineBlobMarker(value) {
90
89
  }
91
90
  return "[inline blob omitted]";
92
91
  }
93
- function truncateSessionRpcString(value) {
94
- if (value.length <= SESSION_RPC_MAX_STRING_CHARS) {
95
- return value;
92
+ function getSessionRpcPayloadByteLength(value) {
93
+ try {
94
+ const serialized = typeof value === "string" ? value : JSON.stringify(value);
95
+ return typeof serialized === "string" ? Buffer.byteLength(serialized, "utf8") : null;
96
+ }
97
+ catch {
98
+ return null;
96
99
  }
97
- const omittedChars = value.length - SESSION_RPC_MAX_STRING_CHARS;
98
- return `${value.slice(0, SESSION_RPC_MAX_STRING_CHARS)}\n[truncated ${omittedChars} chars for session RPC]`;
99
100
  }
100
- function sanitizeSessionRpcPayload(value, options = {}) {
101
+ function buildSessionRpcTruncatedMarker(label, value) {
102
+ const byteLength = getSessionRpcPayloadByteLength(value);
103
+ return byteLength === null
104
+ ? `[${label} truncated for session RPC pagination]`
105
+ : `[${label} truncated for session RPC pagination: ${byteLength} bytes omitted]`;
106
+ }
107
+ function sanitizeSessionRpcPayload(value) {
101
108
  if (typeof value === "string") {
102
- return options.truncateStrings ? truncateSessionRpcString(value) : value;
109
+ return value;
103
110
  }
104
111
  if (Array.isArray(value)) {
105
- return value.map((entry) => sanitizeSessionRpcPayload(entry, options));
112
+ return value.map((entry) => sanitizeSessionRpcPayload(entry));
106
113
  }
107
114
  if (!isObjectRecord(value)) {
108
115
  return value;
@@ -113,7 +120,7 @@ function sanitizeSessionRpcPayload(value, options = {}) {
113
120
  sanitized[key] = buildInlineBlobMarker(entry);
114
121
  continue;
115
122
  }
116
- sanitized[key] = sanitizeSessionRpcPayload(entry, options);
123
+ sanitized[key] = sanitizeSessionRpcPayload(entry);
117
124
  }
118
125
  return sanitized;
119
126
  }
@@ -130,7 +137,7 @@ function sanitizeSessionRpcRawLine(line) {
130
137
  if (parsed.type === "compacted" || parsed.type === "turn_context" || parsed.type === "session_meta") {
131
138
  return JSON.stringify({
132
139
  ...parsed,
133
- payload: sanitizeSessionRpcPayload(parsed.payload, { truncateStrings: true }),
140
+ payload: buildSessionRpcTruncatedMarker("payload", parsed.payload),
134
141
  });
135
142
  }
136
143
  if (!isObjectRecord(parsed.payload)) {
@@ -146,7 +153,7 @@ function sanitizeSessionRpcRawLine(line) {
146
153
  payload: {
147
154
  type: payloadType,
148
155
  status: typeof parsed.payload.status === "string" ? parsed.payload.status : "completed",
149
- message: `[${payloadType} payload omitted for session RPC pagination]`,
156
+ message: buildSessionRpcTruncatedMarker(`${payloadType} payload`, parsed.payload),
150
157
  },
151
158
  });
152
159
  }
@@ -175,6 +182,10 @@ function pickSessionString(...values) {
175
182
  }
176
183
  return null;
177
184
  }
185
+ function toSortableTimestampMs(value) {
186
+ const parsed = Date.parse(value);
187
+ return Number.isFinite(parsed) ? parsed : 0;
188
+ }
178
189
  export async function collectSessionJsonlFiles(workspaceRoot) {
179
190
  const out = [];
180
191
  const stack = [getSessionsRootPath(workspaceRoot)];
@@ -233,8 +244,14 @@ async function readFirstLine(fileHandle, fileSize) {
233
244
  }
234
245
  return raw.trim();
235
246
  }
236
- function extractLastAgentMessage(candidateLines) {
237
- let fallback = null;
247
+ function normalizeSessionSummaryMessage(value) {
248
+ const message = toTrimmedStringOrNull(value);
249
+ if (!message || message.toLowerCase() === "empty") {
250
+ return null;
251
+ }
252
+ return message;
253
+ }
254
+ function extractLastSessionMessage(candidateLines) {
238
255
  for (const line of candidateLines) {
239
256
  const trimmed = line.trim();
240
257
  if (!trimmed) {
@@ -245,18 +262,13 @@ function extractLastAgentMessage(candidateLines) {
245
262
  if (parsed.type !== "event_msg" || !isObjectRecord(parsed.payload)) {
246
263
  continue;
247
264
  }
248
- if (parsed.payload.type === "agent_message" && typeof parsed.payload.message === "string" && parsed.payload.message.trim()) {
249
- return {
250
- message: parsed.payload.message.trim(),
251
- updatedAt: toTrimmedStringOrNull(parsed.timestamp),
252
- };
265
+ if (parsed.payload.type !== "agent_message" && parsed.payload.type !== "user_message") {
266
+ continue;
253
267
  }
254
- if (!fallback &&
255
- parsed.payload.type === "task_complete" &&
256
- typeof parsed.payload.last_agent_message === "string" &&
257
- parsed.payload.last_agent_message.trim()) {
258
- fallback = {
259
- message: parsed.payload.last_agent_message.trim(),
268
+ const message = normalizeSessionSummaryMessage(parsed.payload.message);
269
+ if (message) {
270
+ return {
271
+ message,
260
272
  updatedAt: toTrimmedStringOrNull(parsed.timestamp),
261
273
  };
262
274
  }
@@ -265,9 +277,9 @@ function extractLastAgentMessage(candidateLines) {
265
277
  // ignore malformed lines
266
278
  }
267
279
  }
268
- return fallback;
280
+ return null;
269
281
  }
270
- async function readLastAgentMessage(fileHandle, fileSize) {
282
+ async function readLastSessionMessage(fileHandle, fileSize) {
271
283
  const chunkBytes = 16_384;
272
284
  const maxScanBytes = 131_072;
273
285
  if (fileSize <= 0) {
@@ -288,12 +300,12 @@ async function readLastAgentMessage(fileHandle, fileSize) {
288
300
  const merged = buffer.toString("utf8", 0, bytesRead) + carry;
289
301
  const lines = merged.split(/\r?\n/);
290
302
  carry = lines.shift() || "";
291
- const found = extractLastAgentMessage(lines.reverse());
303
+ const found = extractLastSessionMessage(lines.reverse());
292
304
  if (found) {
293
305
  return found;
294
306
  }
295
307
  }
296
- return extractLastAgentMessage([carry]);
308
+ return extractLastSessionMessage([carry]);
297
309
  }
298
310
  function normalizeSessionMeta(rawMeta, filePath, mtimeMs) {
299
311
  const baseName = path.basename(filePath, path.extname(filePath));
@@ -315,7 +327,7 @@ async function readSessionSummary(filePath, mtimeMs) {
315
327
  fileHandle = await open(filePath, "r");
316
328
  const entryStat = await fileHandle.stat();
317
329
  const firstLine = await readFirstLine(fileHandle, entryStat.size);
318
- const tailSummary = await readLastAgentMessage(fileHandle, entryStat.size);
330
+ const tailSummary = await readLastSessionMessage(fileHandle, entryStat.size);
319
331
  let normalized = normalizeSessionMeta({}, filePath, mtimeMs);
320
332
  if (firstLine) {
321
333
  try {
@@ -365,7 +377,7 @@ async function listAgentSessions(workspaceRoot) {
365
377
  const files = await collectSessionJsonlFiles(workspaceRoot);
366
378
  files.sort((a, b) => b.mtimeMs - a.mtimeMs || a.filePath.localeCompare(b.filePath));
367
379
  const sessions = await Promise.all(files.slice(0, 10).map((file) => readSessionSummary(file.filePath, file.mtimeMs)));
368
- return sessions.sort((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt) || b.filePath.localeCompare(a.filePath));
380
+ return sessions.sort((a, b) => toSortableTimestampMs(b.updatedAt) - toSortableTimestampMs(a.updatedAt) || b.filePath.localeCompare(a.filePath));
369
381
  }
370
382
  async function readSessionLineIndex(workspaceRoot, filePath) {
371
383
  const resolvedFile = resolveSessionFilePath(workspaceRoot, filePath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doer-agent",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
4
4
  "description": "Reverse-polling agent runtime for doer",
5
5
  "type": "module",
6
6
  "main": "dist/agent.js",