@yeaft/webchat-agent 1.0.622 → 1.0.623

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.
@@ -1,4 +1,4 @@
1
- import { mkdirSync, realpathSync, readFileSync, writeFileSync, renameSync, readdirSync, existsSync, lstatSync } from 'node:fs';
1
+ import { mkdirSync, realpathSync, readFileSync, writeFileSync, renameSync, readdirSync, opendirSync, existsSync, lstatSync } from 'node:fs';
2
2
  import { resolve, join, relative, isAbsolute, sep } from 'node:path';
3
3
  import { randomUUID } from 'node:crypto';
4
4
  import { TaskManager } from '../tasks/manager.js';
@@ -96,8 +96,23 @@ function atomicJSON(path, value) {
96
96
  renameSync(temporary, path);
97
97
  }
98
98
 
99
- function jsonFiles(directory) {
99
+ function jsonFiles(directory, { limit } = {}) {
100
100
  if (!existsSync(directory)) return [];
101
+ if (limit != null) {
102
+ // Main cognition reads a bounded latest window, not every durable result at once.
103
+ // Iterate directory entries without assembling an unbounded filename array.
104
+ const handle = opendirSync(directory), records = [];
105
+ const time = record => new Date(record.updatedAt ?? record.task?.updatedAt ?? record.createdAt ?? 0).getTime() || 0;
106
+ try {
107
+ for (let entry; (entry = handle.readSync());) {
108
+ if (!entry.isFile() || !entry.name.endsWith('.json')) continue;
109
+ try { records.push(JSON.parse(readFileSync(join(directory, entry.name), 'utf8'))); } catch { continue; }
110
+ records.sort((a, b) => time(b) - time(a));
111
+ if (records.length > limit) records.pop();
112
+ }
113
+ } finally { handle.closeSync(); }
114
+ return records;
115
+ }
101
116
  return readdirSync(directory).filter(name => name.endsWith('.json')).flatMap(name => {
102
117
  try { return [JSON.parse(readFileSync(join(directory, name), 'utf8'))]; } catch { return []; }
103
118
  });
@@ -562,9 +577,9 @@ class ScopedPersonTaskHost {
562
577
  this.#persistAgents();
563
578
  const recent = items => items.sort((a, b) => String(b.updatedAt ?? b.task?.updatedAt ?? '').localeCompare(String(a.updatedAt ?? a.task?.updatedAt ?? ''))).slice(0, limit);
564
579
  return { namespace: this.scope.key, sessionId: this.sessionId,
565
- tasks: recent(jsonFiles(this.#manager.store.sessionDir(this.sessionId))),
566
- agents: clone([...this.#agents.values()].slice(-limit)), completions: recent(jsonFiles(join(this.dataRoot, 'completions'))),
567
- toolResults: recent(jsonFiles(join(this.dataRoot, 'tool-results'))).map(result => ({ ...result,
580
+ tasks: recent(jsonFiles(this.#manager.store.sessionDir(this.sessionId), { limit })),
581
+ agents: clone([...this.#agents.values()].slice(-limit)), completions: recent(jsonFiles(join(this.dataRoot, 'completions'), { limit })),
582
+ toolResults: recent(jsonFiles(join(this.dataRoot, 'tool-results'), { limit })).map(result => ({ ...result,
568
583
  output: result.output.slice(0, 8192), rawPath: join(this.dataRoot, 'tool-results', `${result.id}.json`) })) };
569
584
  }
570
585
 
@@ -51,7 +51,7 @@ export function callUsage(raw, completed) {
51
51
  return { ...usage, reportedCalls: Number(reported), missingCalls: Number(!reported), complete: completed && usage.totalTokens !== null };
52
52
  }
53
53
 
54
- function sumUsage(calls, terminal) {
54
+ export function sumUsage(calls, terminal) {
55
55
  const fields = [...TOKEN_FIELDS, 'inputTotalTokens', 'totalTokens'];
56
56
  const usage = Object.fromEntries(fields.map(key => {
57
57
  const known = calls.map(call => call.usage[key]).filter(value => value !== null);
@@ -84,7 +84,11 @@ export function turnView(episode, events) {
84
84
  if (!call) continue;
85
85
  if (event.kind === 'call_output' || event.kind === 'call_failed') {
86
86
  call.status = event.kind === 'call_output' ? 'completed' : ['CANCELLED', 'INTERRUPTED'].includes(event.code) ? event.code.toLowerCase() : 'failed';
87
- call.endedAt = event.createdAt; call.code = string(event.code);
87
+ call.endedAt = event.createdAt;
88
+ // Legacy call lifecycle timestamps include queue/pause time. Do not label
89
+ // them as provider duration when no actual dispatch measurement was stored.
90
+ call.elapsedMs = token(event.requestElapsedMs);
91
+ call.code = string(event.code);
88
92
  call.effective.effort = event.effortObserved === true ? string(event.effectiveEffort) : null;
89
93
  call.usage = callUsage(event.usage, event.kind === 'call_output');
90
94
  } else if (event.kind === 'proposal_rejected') {
@@ -97,7 +101,7 @@ export function turnView(episode, events) {
97
101
  }
98
102
  }
99
103
  const items = [...calls.values()].sort((a, b) => a.index - b.index);
100
- return { id: episode.id, seq: episode.seq, kind: episode.kind, status: episode.status, createdAt: episode.createdAt,
104
+ return { id: episode.id, seq: episode.seq, kind: episode.kind, status: episode.status, controlStatus: episode.status === 'running' ? string(episode.controlStatus) : null, createdAt: episode.createdAt,
101
105
  endedAt: episode.endedAt, terminalCode: episode.terminalCode, budget: { calls: token(episode.calls), timeoutMs: token(episode.timeoutMs) },
102
106
  models: [...new Set(items.map(call => call.dispatched.model).filter(Boolean))], usage: sumUsage(items, episode.status !== 'running'), calls: items };
103
107
  }