doer-agent 0.5.7 → 0.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -279,33 +279,57 @@ function extractLastSessionMessage(candidateLines) {
279
279
  }
280
280
  return null;
281
281
  }
282
- async function readLastSessionMessage(fileHandle, fileSize) {
283
- const chunkBytes = 16_384;
284
- const maxScanBytes = 131_072;
285
- if (fileSize <= 0) {
286
- return null;
287
- }
288
- let position = fileSize;
289
- let scanned = 0;
290
- let carry = "";
291
- while (position > 0 && scanned < maxScanBytes) {
292
- const readSize = Math.min(chunkBytes, position, maxScanBytes - scanned);
293
- position -= readSize;
294
- scanned += readSize;
295
- const buffer = Buffer.alloc(readSize);
296
- const { bytesRead } = await fileHandle.read(buffer, 0, readSize, position);
282
+ function linePrefixContainsSessionMessageCandidate(prefix) {
283
+ return (/"type"\s*:\s*"event_msg"/.test(prefix) &&
284
+ /"type"\s*:\s*"(agent_message|user_message)"/.test(prefix));
285
+ }
286
+ async function readLineSpan(fileHandle, start, end) {
287
+ const readSize = Math.max(0, end - start);
288
+ if (readSize <= 0) {
289
+ return "";
290
+ }
291
+ const buffer = Buffer.alloc(readSize);
292
+ let totalBytesRead = 0;
293
+ while (totalBytesRead < readSize) {
294
+ const { bytesRead } = await fileHandle.read(buffer, totalBytesRead, readSize - totalBytesRead, start + totalBytesRead);
297
295
  if (bytesRead <= 0) {
298
296
  break;
299
297
  }
300
- const merged = buffer.toString("utf8", 0, bytesRead) + carry;
301
- const lines = merged.split(/\r?\n/);
302
- carry = lines.shift() || "";
303
- const found = extractLastSessionMessage(lines.reverse());
304
- if (found) {
305
- return found;
298
+ totalBytesRead += bytesRead;
299
+ }
300
+ return buffer.toString("utf8", 0, totalBytesRead).trim();
301
+ }
302
+ async function readLastSessionMessage(workspaceRoot, filePath) {
303
+ const index = await readSessionLineIndex(workspaceRoot, filePath);
304
+ const totalLines = index.lineStartOffsets.length;
305
+ if (totalLines <= 0 || index.size <= 0) {
306
+ return null;
307
+ }
308
+ const resolvedFile = resolveSessionFilePath(workspaceRoot, filePath);
309
+ const fileHandle = await open(resolvedFile, "r");
310
+ try {
311
+ for (let lineIndex = totalLines - 1; lineIndex >= 0; lineIndex -= 1) {
312
+ const start = index.lineStartOffsets[lineIndex] ?? index.size;
313
+ const end = lineIndex + 1 < totalLines ? (index.lineStartOffsets[lineIndex + 1] ?? index.size) : index.size;
314
+ const spanBytes = Math.max(0, end - start);
315
+ if (spanBytes <= 0) {
316
+ continue;
317
+ }
318
+ const prefixBytes = Math.min(spanBytes, 1024);
319
+ const prefix = await readLineSpan(fileHandle, start, start + prefixBytes);
320
+ if (!linePrefixContainsSessionMessageCandidate(prefix)) {
321
+ continue;
322
+ }
323
+ const found = extractLastSessionMessage([await readLineSpan(fileHandle, start, end)]);
324
+ if (found) {
325
+ return found;
326
+ }
306
327
  }
328
+ return null;
329
+ }
330
+ finally {
331
+ await fileHandle.close().catch(() => undefined);
307
332
  }
308
- return extractLastSessionMessage([carry]);
309
333
  }
310
334
  function normalizeSessionMeta(rawMeta, filePath, mtimeMs) {
311
335
  const baseName = path.basename(filePath, path.extname(filePath));
@@ -321,13 +345,13 @@ function normalizeSessionMeta(rawMeta, filePath, mtimeMs) {
321
345
  filePath,
322
346
  };
323
347
  }
324
- async function readSessionSummary(filePath, mtimeMs) {
348
+ async function readSessionSummary(workspaceRoot, filePath, mtimeMs) {
325
349
  let fileHandle = null;
326
350
  try {
327
351
  fileHandle = await open(filePath, "r");
328
352
  const entryStat = await fileHandle.stat();
329
353
  const firstLine = await readFirstLine(fileHandle, entryStat.size);
330
- const tailSummary = await readLastSessionMessage(fileHandle, entryStat.size);
354
+ const tailSummary = await readLastSessionMessage(workspaceRoot, filePath);
331
355
  let normalized = normalizeSessionMeta({}, filePath, mtimeMs);
332
356
  if (firstLine) {
333
357
  try {
@@ -363,6 +387,7 @@ async function readSessionSummary(filePath, mtimeMs) {
363
387
  }
364
388
  }
365
389
  async function listAgentSessions(workspaceRoot) {
390
+ const maxSessionSummaries = 10;
366
391
  const sessionsRoot = getSessionsRootPath(workspaceRoot);
367
392
  let sessionsRootStat;
368
393
  try {
@@ -376,8 +401,23 @@ async function listAgentSessions(workspaceRoot) {
376
401
  }
377
402
  const files = await collectSessionJsonlFiles(workspaceRoot);
378
403
  files.sort((a, b) => b.mtimeMs - a.mtimeMs || a.filePath.localeCompare(b.filePath));
379
- const sessions = await Promise.all(files.slice(0, 10).map((file) => readSessionSummary(file.filePath, file.mtimeMs)));
380
- return sessions.sort((a, b) => toSortableTimestampMs(b.updatedAt) - toSortableTimestampMs(a.updatedAt) || b.filePath.localeCompare(a.filePath));
404
+ const sessions = [];
405
+ for (let index = 0; index < files.length; index += 1) {
406
+ const file = files[index];
407
+ sessions.push(await readSessionSummary(workspaceRoot, file.filePath, file.mtimeMs));
408
+ sessions.sort((a, b) => toSortableTimestampMs(b.updatedAt) - toSortableTimestampMs(a.updatedAt) || b.filePath.localeCompare(a.filePath));
409
+ if (sessions.length > maxSessionSummaries) {
410
+ sessions.length = maxSessionSummaries;
411
+ }
412
+ const nextFile = files[index + 1] ?? null;
413
+ const oldestSelectedSession = sessions[maxSessionSummaries - 1] ?? null;
414
+ if (nextFile &&
415
+ oldestSelectedSession &&
416
+ toSortableTimestampMs(oldestSelectedSession.updatedAt) >= nextFile.mtimeMs) {
417
+ break;
418
+ }
419
+ }
420
+ return sessions;
381
421
  }
382
422
  async function readSessionLineIndex(workspaceRoot, filePath) {
383
423
  const resolvedFile = resolveSessionFilePath(workspaceRoot, filePath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doer-agent",
3
- "version": "0.5.7",
3
+ "version": "0.5.8",
4
4
  "description": "Reverse-polling agent runtime for doer",
5
5
  "type": "module",
6
6
  "main": "dist/agent.js",