@dougbots/avenor-core 0.3.7 → 0.4.1

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 (3) hide show
  1. package/dist/index.d.ts +205 -5
  2. package/dist/index.js +1209 -146
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7,6 +7,22 @@ import * as os from "node:os";
7
7
  import * as path from "node:path";
8
8
  import * as crypto from "node:crypto";
9
9
  //#region src/client.ts
10
+ function normalizeSubscribeOptions(options) {
11
+ if (!options) return void 0;
12
+ const normalized = {};
13
+ if (options.runtime_id) normalized.runtime_id = options.runtime_id;
14
+ if (options.replay !== void 0) normalized.replay = options.replay;
15
+ if (options.limit !== void 0) normalized.limit = Math.max(1, Math.min(256, Math.trunc(options.limit)));
16
+ if (options.after_seq !== void 0) normalized.after_seq = Math.max(0, Math.trunc(options.after_seq));
17
+ return Object.keys(normalized).length > 0 ? normalized : void 0;
18
+ }
19
+ function subscribeOptionsKey(options) {
20
+ return JSON.stringify(normalizeSubscribeOptions(options) ?? {});
21
+ }
22
+ function removeResolver(resolvers, target) {
23
+ const index = resolvers.indexOf(target);
24
+ if (index >= 0) resolvers.splice(index, 1);
25
+ }
10
26
  var Client = class {
11
27
  socket;
12
28
  rl;
@@ -19,6 +35,7 @@ var Client = class {
19
35
  eventDone = false;
20
36
  dropped = 0;
21
37
  subscribed = false;
38
+ subscriptionKey = subscribeOptionsKey();
22
39
  constructor(socket, opts) {
23
40
  this.socket = socket;
24
41
  this.rl = readline.createInterface({
@@ -59,11 +76,10 @@ var Client = class {
59
76
  if (this.dropped > 0) {
60
77
  const lag = this.dropped;
61
78
  this.dropped = 0;
62
- const lagEvent = {
79
+ this.pushEvent({
63
80
  event: "client.lagged",
64
81
  dropped_count: lag
65
- };
66
- this.pushEvent(lagEvent);
82
+ });
67
83
  }
68
84
  this.pushEvent(ev);
69
85
  }
@@ -75,19 +91,28 @@ var Client = class {
75
91
  }
76
92
  this.pending.clear();
77
93
  this.eventDone = true;
78
- for (const resolve of this.eventResolvers) resolve({
79
- value: void 0,
80
- done: true
81
- });
94
+ for (const resolver of this.eventResolvers) {
95
+ resolver.active = false;
96
+ resolver.resolve({
97
+ value: void 0,
98
+ done: true
99
+ });
100
+ }
82
101
  this.eventResolvers = [];
83
102
  });
84
103
  }
85
104
  pushEvent(ev) {
86
- if (this.eventResolvers.length > 0) this.eventResolvers.shift()({
87
- value: ev,
88
- done: false
89
- });
90
- else if (this.eventQueue.length < 256) this.eventQueue.push(ev);
105
+ while (this.eventResolvers.length > 0) {
106
+ const resolver = this.eventResolvers.shift();
107
+ if (!resolver.active) continue;
108
+ resolver.active = false;
109
+ resolver.resolve({
110
+ value: ev,
111
+ done: false
112
+ });
113
+ return;
114
+ }
115
+ if (this.eventQueue.length < 256) this.eventQueue.push(ev);
91
116
  else this.dropped++;
92
117
  }
93
118
  async call(method, params) {
@@ -119,25 +144,90 @@ var Client = class {
119
144
  });
120
145
  });
121
146
  }
122
- async subscribe() {
123
- if (this.subscribed) return;
147
+ async subscribe(options) {
148
+ const key = subscribeOptionsKey(options);
149
+ if (this.subscribed) {
150
+ if (key !== subscribeOptionsKey() && key !== this.subscriptionKey) throw new Error("client already subscribed with different options");
151
+ return;
152
+ }
124
153
  this.subscribed = true;
125
- await this.call("subscribe");
126
- }
127
- async *events() {
128
- await this.subscribe();
129
- while (true) {
130
- if (this.eventDone) break;
131
- if (this.eventQueue.length > 0) {
132
- yield this.eventQueue.shift();
133
- continue;
154
+ this.subscriptionKey = key;
155
+ await this.call("subscribe", normalizeSubscribeOptions(options));
156
+ }
157
+ events(options) {
158
+ let active = true;
159
+ let pendingResolver = null;
160
+ let subscribedPromise = null;
161
+ const ensureSubscribed = async () => {
162
+ if (!subscribedPromise) subscribedPromise = this.subscribe(options);
163
+ await subscribedPromise;
164
+ };
165
+ const next = async () => {
166
+ if (!active || this.eventDone) return {
167
+ value: void 0,
168
+ done: true
169
+ };
170
+ try {
171
+ await ensureSubscribed();
172
+ } catch (error) {
173
+ active = false;
174
+ throw error;
134
175
  }
135
- const result = await new Promise((resolve) => {
136
- this.eventResolvers.push(resolve);
176
+ if (!active || this.eventDone) return {
177
+ value: void 0,
178
+ done: true
179
+ };
180
+ if (this.eventQueue.length > 0) return {
181
+ value: this.eventQueue.shift(),
182
+ done: false
183
+ };
184
+ return new Promise((resolve) => {
185
+ pendingResolver = {
186
+ resolve,
187
+ active: true
188
+ };
189
+ this.eventResolvers.push(pendingResolver);
137
190
  });
138
- if (result.done) break;
139
- yield result.value;
140
- }
191
+ };
192
+ const stop = async () => {
193
+ if (!active) return {
194
+ value: void 0,
195
+ done: true
196
+ };
197
+ active = false;
198
+ if (pendingResolver) {
199
+ pendingResolver.active = false;
200
+ removeResolver(this.eventResolvers, pendingResolver);
201
+ pendingResolver.resolve({
202
+ value: void 0,
203
+ done: true
204
+ });
205
+ pendingResolver = null;
206
+ }
207
+ return {
208
+ value: void 0,
209
+ done: true
210
+ };
211
+ };
212
+ return {
213
+ next,
214
+ return: stop,
215
+ async throw(error) {
216
+ await stop();
217
+ throw error;
218
+ },
219
+ [Symbol.asyncIterator]() {
220
+ return this;
221
+ }
222
+ };
223
+ }
224
+ async history(options) {
225
+ const params = {
226
+ runtime_id: options.runtime_id,
227
+ limit: options.limit !== void 0 ? Math.max(1, Math.min(256, Math.trunc(options.limit))) : void 0,
228
+ after_seq: options.after_seq !== void 0 ? Math.max(0, Math.trunc(options.after_seq)) : void 0
229
+ };
230
+ return this.call("history", params);
141
231
  }
142
232
  async status(runtimeId) {
143
233
  const params = runtimeId ? { runtime_id: runtimeId } : void 0;
@@ -190,6 +280,773 @@ function dial(socketPath, opts) {
190
280
  });
191
281
  }
192
282
  //#endregion
283
+ //#region src/value-fields.ts
284
+ function asRecord(value) {
285
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
286
+ return value;
287
+ }
288
+ function stringField(record, ...keys) {
289
+ if (!record) return void 0;
290
+ for (const key of keys) {
291
+ const value = record[key];
292
+ if (typeof value === "string" && value.length > 0) return value;
293
+ }
294
+ }
295
+ function numberField(record, ...keys) {
296
+ if (!record) return void 0;
297
+ for (const key of keys) {
298
+ const value = record[key];
299
+ if (typeof value === "number" && Number.isFinite(value)) return value;
300
+ if (typeof value === "string" && value.trim().length > 0) {
301
+ const parsed = Number(value);
302
+ if (Number.isFinite(parsed)) return parsed;
303
+ }
304
+ }
305
+ }
306
+ function stringArrayField(record, ...keys) {
307
+ if (!record) return void 0;
308
+ for (const key of keys) {
309
+ const value = record[key];
310
+ if (!Array.isArray(value)) continue;
311
+ const items = value.filter((item) => typeof item === "string" && item.length > 0);
312
+ if (items.length > 0) return items;
313
+ }
314
+ }
315
+ //#endregion
316
+ //#region src/run-events.ts
317
+ const MAX_TEXT_EXTRACTION_DEPTH = 12;
318
+ const MAX_TEXT_EXTRACTION_ITEMS = 64;
319
+ function extractEventText(value, depth = 0) {
320
+ if (depth >= MAX_TEXT_EXTRACTION_DEPTH) return void 0;
321
+ if (typeof value === "string") return value.length > 0 ? value : void 0;
322
+ if (Array.isArray(value)) {
323
+ const parts = [];
324
+ for (const item of value.slice(0, MAX_TEXT_EXTRACTION_ITEMS)) {
325
+ const text = extractEventText(item, depth + 1);
326
+ if (text) parts.push(text);
327
+ }
328
+ return parts.length > 0 ? parts.join("") : void 0;
329
+ }
330
+ const record = asRecord(value);
331
+ if (!record) return void 0;
332
+ for (const key of [
333
+ "delta",
334
+ "text",
335
+ "content",
336
+ "partial",
337
+ "message",
338
+ "assistantMessage",
339
+ "assistantMessageEvent",
340
+ "data",
341
+ "result",
342
+ "output"
343
+ ]) {
344
+ const text = extractEventText(record[key], depth + 1);
345
+ if (text) return text;
346
+ }
347
+ }
348
+ function normalizePermissionOptions(value) {
349
+ if (!Array.isArray(value)) return [];
350
+ const options = [];
351
+ for (const item of value) {
352
+ const record = asRecord(item);
353
+ if (!record) continue;
354
+ const option_id = stringField(record, "option_id", "optionId") ?? "";
355
+ const label = stringField(record, "label") ?? option_id;
356
+ const kind = stringField(record, "kind") ?? "";
357
+ if (!option_id && !label && !kind) continue;
358
+ options.push({
359
+ option_id,
360
+ label,
361
+ kind
362
+ });
363
+ }
364
+ return options;
365
+ }
366
+ function normalizePermission$1(record) {
367
+ const request_id = stringField(record, "request_id", "requestID", "id");
368
+ const description = stringField(record, "description", "title");
369
+ const question = stringField(record, "question", "message");
370
+ const tool = stringField(record, "tool");
371
+ const options = normalizePermissionOptions(record.options);
372
+ const kind = stringField(record, "kind", "status");
373
+ if (!request_id && !description && !question && !tool && options.length === 0 && !kind) return;
374
+ return {
375
+ request_id,
376
+ description,
377
+ question,
378
+ tool,
379
+ options,
380
+ kind
381
+ };
382
+ }
383
+ function normalizeTool(record, fallbackStatus) {
384
+ const title = stringField(record, "title", "toolName", "name");
385
+ const kind = stringField(record, "kind", "toolKind");
386
+ const id = stringField(record, "tool_call_id", "toolCallId", "call_id", "callId", "id", "message_id", "messageID", "part_id", "partID");
387
+ const status = stringField(record, "status", "state") ?? fallbackStatus;
388
+ const preview = extractEventText(record.preview) ?? extractEventText(record.arguments) ?? extractEventText(record.args) ?? extractEventText(record.input) ?? extractEventText(record.result) ?? extractEventText(record.output) ?? extractEventText(record.delta);
389
+ if (!id && !title && !kind && !status && !preview) return;
390
+ return {
391
+ key: id ?? title ?? kind ?? "tool",
392
+ id,
393
+ title,
394
+ kind,
395
+ status,
396
+ preview
397
+ };
398
+ }
399
+ function semanticFingerprint(event, delta, tool, permission, stopReason) {
400
+ if (delta) return `${event}:${delta.length}:${delta.slice(0, 512)}`;
401
+ if (tool) return `${event}:${tool.id ?? tool.title ?? tool.kind ?? tool.key}:${tool.status ?? ""}:${tool.preview ?? ""}`;
402
+ if (permission) return `${event}:${permission.request_id ?? permission.question ?? permission.description ?? permission.tool ?? ""}:${permission.kind ?? ""}`;
403
+ if (stopReason) return `${event}:${stopReason}`;
404
+ }
405
+ function normalizeRunEvent(input) {
406
+ const raw = asRecord(input);
407
+ if (!raw) return null;
408
+ const source_event = stringField(raw, "event", "type");
409
+ if (!source_event) return null;
410
+ const session_id = stringField(raw, "session_id", "sessionID", "sessionId");
411
+ const runtime_id = stringField(raw, "runtime_id", "runtimeID");
412
+ const run_id = stringField(raw, "run_id", "runId");
413
+ const run_label = stringField(raw, "run_label", "runLabel", "label");
414
+ const seq = numberField(raw, "seq");
415
+ const ts = numberField(raw, "ts", "timestamp");
416
+ const phase = stringField(raw, "phase", "status");
417
+ const phase_label = stringField(raw, "phase_label", "label");
418
+ const stop_reason = stringField(raw, "stop_reason", "stopReason");
419
+ const final_output = stringField(raw, "final_output", "finalOutput");
420
+ const backend = stringField(raw, "backend");
421
+ const agent = stringField(raw, "agent");
422
+ const model = stringField(raw, "model");
423
+ const dir = stringField(raw, "dir");
424
+ const parent_id = stringField(raw, "parent_id", "parentId");
425
+ const children = stringArrayField(raw, "children");
426
+ const event_path = stringField(raw, "event_path", "on_event");
427
+ const usage = asRecord(raw.usage) ?? void 0;
428
+ let event = source_event;
429
+ let alias = false;
430
+ let delta;
431
+ let chunk_role;
432
+ let tool;
433
+ let permission;
434
+ switch (source_event) {
435
+ case "agent.message_chunk":
436
+ chunk_role = "assistant";
437
+ delta = extractEventText(raw);
438
+ break;
439
+ case "agent.thought_chunk":
440
+ chunk_role = "thought";
441
+ delta = extractEventText(raw);
442
+ break;
443
+ case "user.message_chunk":
444
+ chunk_role = "user";
445
+ delta = extractEventText(raw);
446
+ break;
447
+ case "tool.call":
448
+ tool = normalizeTool(raw, "running");
449
+ break;
450
+ case "tool.call_update":
451
+ tool = normalizeTool(raw);
452
+ break;
453
+ case "permission.request":
454
+ case "permission.response":
455
+ permission = normalizePermission$1(raw);
456
+ break;
457
+ case "avenor.message.delta":
458
+ event = "agent.message_chunk";
459
+ alias = true;
460
+ chunk_role = "assistant";
461
+ delta = extractEventText(raw);
462
+ break;
463
+ case "avenor.message.update": {
464
+ alias = true;
465
+ const assistantMessageEvent = asRecord(raw.assistantMessageEvent) ?? raw;
466
+ const assistantType = stringField(assistantMessageEvent, "type");
467
+ if (assistantType === "thinking_delta") {
468
+ event = "agent.thought_chunk";
469
+ chunk_role = "thought";
470
+ delta = extractEventText(assistantMessageEvent);
471
+ } else if (assistantType === "toolcall_start") {
472
+ event = "tool.call";
473
+ tool = normalizeTool(assistantMessageEvent, "running");
474
+ } else if (assistantType === "toolcall_delta" || assistantType === "toolcall_update" || assistantType === "toolcall_end") {
475
+ event = "tool.call_update";
476
+ tool = normalizeTool(assistantMessageEvent, assistantType === "toolcall_end" ? "completed" : "running");
477
+ } else {
478
+ event = "agent.message_chunk";
479
+ chunk_role = "assistant";
480
+ delta = extractEventText(assistantMessageEvent);
481
+ }
482
+ break;
483
+ }
484
+ case "avenor.tool.start":
485
+ event = "tool.call";
486
+ alias = true;
487
+ tool = normalizeTool(raw, "running");
488
+ break;
489
+ case "avenor.tool.update":
490
+ event = "tool.call_update";
491
+ alias = true;
492
+ tool = normalizeTool(raw);
493
+ break;
494
+ case "avenor.tool.end":
495
+ event = "tool.call_update";
496
+ alias = true;
497
+ tool = normalizeTool(raw, "completed");
498
+ break;
499
+ }
500
+ return {
501
+ event,
502
+ source_event,
503
+ raw: { ...raw },
504
+ runtime_id,
505
+ session_id,
506
+ run_id,
507
+ run_label,
508
+ seq,
509
+ ts,
510
+ phase,
511
+ phase_label,
512
+ stop_reason,
513
+ final_output,
514
+ usage,
515
+ backend,
516
+ agent,
517
+ model,
518
+ dir,
519
+ parent_id,
520
+ children,
521
+ event_path,
522
+ delta,
523
+ chunk_role,
524
+ tool,
525
+ permission,
526
+ alias,
527
+ semantic_fingerprint: semanticFingerprint(event, delta, tool, permission, stop_reason)
528
+ };
529
+ }
530
+ //#endregion
531
+ //#region src/run-reducer.ts
532
+ const DEFAULT_RUN_REDUCER_LIMITS = {
533
+ transcript_entries: 64,
534
+ text_chars: 4e3,
535
+ tool_preview_chars: 1e3,
536
+ tools: 24,
537
+ permissions: 16,
538
+ legacy_keys: 128
539
+ };
540
+ function clipText(text, limit) {
541
+ if (!text) return void 0;
542
+ if (text.length <= limit) return text;
543
+ return text.slice(text.length - limit);
544
+ }
545
+ function appendText(current, delta, limit) {
546
+ if (!delta) return current;
547
+ return clipText(`${current}${delta}`, limit) ?? current;
548
+ }
549
+ function pushBounded(items, item, limit) {
550
+ const next = [...items, item];
551
+ if (next.length <= limit) return next;
552
+ return next.slice(next.length - limit);
553
+ }
554
+ function toLegacyKey(event) {
555
+ return JSON.stringify([
556
+ event.runtime_id ?? event.session_id ?? "",
557
+ event.event,
558
+ event.ts ?? "",
559
+ event.delta ?? "",
560
+ event.tool?.id ?? event.tool?.title ?? event.tool?.status ?? "",
561
+ event.permission?.request_id ?? event.permission?.question ?? "",
562
+ event.stop_reason ?? ""
563
+ ]);
564
+ }
565
+ function normalizeLimits(limits) {
566
+ return {
567
+ transcript_entries: Math.max(1, Math.trunc(limits?.transcript_entries ?? DEFAULT_RUN_REDUCER_LIMITS.transcript_entries)),
568
+ text_chars: Math.max(1, Math.trunc(limits?.text_chars ?? DEFAULT_RUN_REDUCER_LIMITS.text_chars)),
569
+ tool_preview_chars: Math.max(1, Math.trunc(limits?.tool_preview_chars ?? DEFAULT_RUN_REDUCER_LIMITS.tool_preview_chars)),
570
+ tools: Math.max(1, Math.trunc(limits?.tools ?? DEFAULT_RUN_REDUCER_LIMITS.tools)),
571
+ permissions: Math.max(1, Math.trunc(limits?.permissions ?? DEFAULT_RUN_REDUCER_LIMITS.permissions)),
572
+ legacy_keys: Math.max(1, Math.trunc(limits?.legacy_keys ?? DEFAULT_RUN_REDUCER_LIMITS.legacy_keys))
573
+ };
574
+ }
575
+ function createRunSnapshot(limits) {
576
+ return {
577
+ identity: {},
578
+ limits: normalizeLimits(limits),
579
+ ended: false,
580
+ assistant_text: "",
581
+ thought_text: "",
582
+ user_text: "",
583
+ transcript: [],
584
+ tools: [],
585
+ live_tools: [],
586
+ permissions: [],
587
+ _seen_seq_by_scope: {},
588
+ _recent_legacy_keys: []
589
+ };
590
+ }
591
+ function transcriptText(entry, limits) {
592
+ if (!entry.text) return entry;
593
+ return {
594
+ ...entry,
595
+ text: clipText(entry.text, limits.text_chars)
596
+ };
597
+ }
598
+ function appendTranscript(snapshot, entry) {
599
+ const nextEntry = transcriptText(entry, snapshot.limits);
600
+ const last = snapshot.transcript[snapshot.transcript.length - 1];
601
+ if (last && last.kind === nextEntry.kind && nextEntry.text && last.text && last.event === nextEntry.event) {
602
+ const merged = {
603
+ ...last,
604
+ text: clipText(`${last.text}${nextEntry.text}`, snapshot.limits.text_chars),
605
+ seq: nextEntry.seq ?? last.seq,
606
+ ts: nextEntry.ts ?? last.ts
607
+ };
608
+ return [...snapshot.transcript.slice(0, -1), merged];
609
+ }
610
+ return pushBounded(snapshot.transcript, nextEntry, snapshot.limits.transcript_entries);
611
+ }
612
+ function mergeIdentity(snapshot, event) {
613
+ return {
614
+ runtime_id: event.runtime_id ?? snapshot.identity.runtime_id,
615
+ session_id: event.session_id ?? snapshot.identity.session_id,
616
+ run_id: event.run_id ?? snapshot.identity.run_id,
617
+ run_label: event.run_label ?? snapshot.identity.run_label,
618
+ backend: event.backend ?? snapshot.identity.backend,
619
+ agent: event.agent ?? snapshot.identity.agent,
620
+ model: event.model ?? snapshot.identity.model,
621
+ dir: event.dir ?? snapshot.identity.dir,
622
+ parent_id: event.parent_id ?? snapshot.identity.parent_id,
623
+ children: event.children ?? snapshot.identity.children,
624
+ event_path: event.event_path ?? snapshot.identity.event_path
625
+ };
626
+ }
627
+ function withToolPreview(tool, limit) {
628
+ if (!tool) return tool;
629
+ return {
630
+ ...tool,
631
+ preview: clipText(tool.preview, limit)
632
+ };
633
+ }
634
+ function mergeToolState(snapshot, event, live) {
635
+ const tool = withToolPreview(event.tool, snapshot.limits.tool_preview_chars);
636
+ if (!tool) return snapshot.tools;
637
+ const tools = snapshot.tools.map((item) => ({ ...item }));
638
+ const index = tools.findIndex((item) => item.key === tool.key);
639
+ const existing = index >= 0 ? tools[index] : void 0;
640
+ const next = {
641
+ key: tool.key,
642
+ id: tool.id ?? existing?.id,
643
+ title: tool.title ?? existing?.title,
644
+ kind: tool.kind ?? existing?.kind,
645
+ status: tool.status ?? existing?.status,
646
+ preview: clipText(tool.preview ?? existing?.preview, snapshot.limits.tool_preview_chars),
647
+ started_at: existing?.started_at ?? event.ts,
648
+ updated_at: event.ts ?? existing?.updated_at,
649
+ ended_at: live ? existing?.ended_at : event.ts ?? existing?.ended_at,
650
+ live
651
+ };
652
+ if (index >= 0) tools[index] = next;
653
+ else tools.push(next);
654
+ return tools.length <= snapshot.limits.tools ? tools : tools.slice(tools.length - snapshot.limits.tools);
655
+ }
656
+ function liveTools(tools) {
657
+ return tools.filter((tool) => tool.live);
658
+ }
659
+ function mergePermissionHistory(snapshot, permission, resolved = false) {
660
+ if (!permission) return {
661
+ pending_permission: resolved ? void 0 : snapshot.pending_permission,
662
+ permissions: snapshot.permissions
663
+ };
664
+ const permissions = snapshot.permissions.map((item) => ({ ...item }));
665
+ const index = permission.request_id ? permissions.findIndex((item) => item.request_id === permission.request_id) : -1;
666
+ const entry = {
667
+ ...permission,
668
+ kind: resolved ? permission.kind ?? "resolved" : permission.kind,
669
+ options: permission.options.map((option) => ({ ...option }))
670
+ };
671
+ if (index >= 0) permissions[index] = {
672
+ ...permissions[index],
673
+ ...entry
674
+ };
675
+ else permissions.push(entry);
676
+ const bounded = permissions.length <= snapshot.limits.permissions ? permissions : permissions.slice(permissions.length - snapshot.limits.permissions);
677
+ return {
678
+ pending_permission: resolved ? void 0 : entry,
679
+ permissions: bounded
680
+ };
681
+ }
682
+ function shouldSkipSemanticUpdate(snapshot, event) {
683
+ if (!event.alias || !event.semantic_fingerprint) return false;
684
+ return snapshot._last_semantic_fingerprint === event.semantic_fingerprint && snapshot._last_source_event !== event.source_event;
685
+ }
686
+ function updateDedupeState(snapshot, event) {
687
+ const scope = event.runtime_id ?? event.session_id ?? "global";
688
+ if (event.seq !== void 0) {
689
+ const seen = snapshot._seen_seq_by_scope[scope] ?? 0;
690
+ if (event.seq <= seen) return null;
691
+ return {
692
+ _seen_seq_by_scope: {
693
+ ...snapshot._seen_seq_by_scope,
694
+ [scope]: event.seq
695
+ },
696
+ _recent_legacy_keys: snapshot._recent_legacy_keys
697
+ };
698
+ }
699
+ const key = toLegacyKey(event);
700
+ if (snapshot._recent_legacy_keys.includes(key)) return null;
701
+ const nextKeys = [...snapshot._recent_legacy_keys, key];
702
+ return {
703
+ _seen_seq_by_scope: snapshot._seen_seq_by_scope,
704
+ _recent_legacy_keys: nextKeys.length <= snapshot.limits.legacy_keys ? nextKeys : nextKeys.slice(nextKeys.length - snapshot.limits.legacy_keys)
705
+ };
706
+ }
707
+ function reduceRunSnapshot(current, input, limits) {
708
+ const snapshot = limits ? {
709
+ ...current,
710
+ limits: normalizeLimits({
711
+ ...current.limits,
712
+ ...limits
713
+ })
714
+ } : current;
715
+ const event = normalizeRunEvent(input);
716
+ if (!event) return snapshot;
717
+ const dedupe = updateDedupeState(snapshot, event);
718
+ if (!dedupe) return snapshot;
719
+ let next = {
720
+ ...snapshot,
721
+ identity: mergeIdentity(snapshot, event),
722
+ latest_seq: event.seq ?? snapshot.latest_seq,
723
+ last_event: event.event,
724
+ last_ts: event.ts ?? snapshot.last_ts,
725
+ phase: event.event === "session.end" ? "done" : event.phase ?? snapshot.phase,
726
+ phase_label: event.phase_label ?? snapshot.phase_label,
727
+ stop_reason: event.stop_reason ?? snapshot.stop_reason,
728
+ usage: event.usage ? {
729
+ ...snapshot.usage ?? {},
730
+ ...event.usage
731
+ } : snapshot.usage,
732
+ final_output: clipText(event.final_output ?? snapshot.final_output, snapshot.limits.text_chars),
733
+ _seen_seq_by_scope: dedupe._seen_seq_by_scope,
734
+ _recent_legacy_keys: dedupe._recent_legacy_keys
735
+ };
736
+ if (event.event === "session.end") next.ended = true;
737
+ if (shouldSkipSemanticUpdate(next, event)) return {
738
+ ...next,
739
+ _last_semantic_fingerprint: event.semantic_fingerprint ?? next._last_semantic_fingerprint,
740
+ _last_source_event: event.source_event
741
+ };
742
+ switch (event.event) {
743
+ case "agent.message_chunk": {
744
+ const text = clipText(event.delta, next.limits.text_chars);
745
+ next.assistant_text = appendText(next.assistant_text, text, next.limits.text_chars);
746
+ if (text) next.transcript = appendTranscript(next, {
747
+ kind: "assistant",
748
+ event: event.event,
749
+ source_event: event.source_event,
750
+ text,
751
+ seq: event.seq,
752
+ ts: event.ts
753
+ });
754
+ break;
755
+ }
756
+ case "agent.thought_chunk": {
757
+ const text = clipText(event.delta, next.limits.text_chars);
758
+ next.thought_text = appendText(next.thought_text, text, next.limits.text_chars);
759
+ if (text) next.transcript = appendTranscript(next, {
760
+ kind: "thought",
761
+ event: event.event,
762
+ source_event: event.source_event,
763
+ text,
764
+ seq: event.seq,
765
+ ts: event.ts
766
+ });
767
+ break;
768
+ }
769
+ case "user.message_chunk": {
770
+ const text = clipText(event.delta, next.limits.text_chars);
771
+ next.user_text = appendText(next.user_text, text, next.limits.text_chars);
772
+ if (text) next.transcript = appendTranscript(next, {
773
+ kind: "user",
774
+ event: event.event,
775
+ source_event: event.source_event,
776
+ text,
777
+ seq: event.seq,
778
+ ts: event.ts
779
+ });
780
+ break;
781
+ }
782
+ case "tool.call":
783
+ next.tools = mergeToolState(next, event, true);
784
+ next.live_tools = liveTools(next.tools);
785
+ next.transcript = appendTranscript(next, {
786
+ kind: "tool",
787
+ event: event.event,
788
+ source_event: event.source_event,
789
+ title: event.tool?.title ?? event.tool?.kind,
790
+ status: event.tool?.status,
791
+ text: clipText(event.tool?.preview, next.limits.tool_preview_chars),
792
+ seq: event.seq,
793
+ ts: event.ts
794
+ });
795
+ break;
796
+ case "tool.call_update":
797
+ next.tools = mergeToolState(next, event, ![
798
+ "completed",
799
+ "complete",
800
+ "done",
801
+ "failed",
802
+ "cancelled",
803
+ "canceled"
804
+ ].includes((event.tool?.status ?? "").toLowerCase()));
805
+ next.live_tools = liveTools(next.tools);
806
+ next.transcript = appendTranscript(next, {
807
+ kind: "tool",
808
+ event: event.event,
809
+ source_event: event.source_event,
810
+ title: event.tool?.title ?? event.tool?.kind,
811
+ status: event.tool?.status,
812
+ text: clipText(event.tool?.preview, next.limits.tool_preview_chars),
813
+ seq: event.seq,
814
+ ts: event.ts
815
+ });
816
+ break;
817
+ case "permission.request": {
818
+ const permission = mergePermissionHistory(next, event.permission);
819
+ next.pending_permission = permission.pending_permission;
820
+ next.permissions = permission.permissions;
821
+ next.phase = "waiting";
822
+ next.phase_label = event.permission?.question ?? event.permission?.description ?? event.permission?.tool ?? next.phase_label;
823
+ next.transcript = appendTranscript(next, {
824
+ kind: "permission",
825
+ event: event.event,
826
+ source_event: event.source_event,
827
+ request_id: event.permission?.request_id,
828
+ text: clipText(event.permission?.question ?? event.permission?.description ?? event.permission?.tool, next.limits.text_chars),
829
+ seq: event.seq,
830
+ ts: event.ts
831
+ });
832
+ break;
833
+ }
834
+ case "permission.response": {
835
+ const permission = mergePermissionHistory(next, event.permission ?? next.pending_permission, true);
836
+ next.pending_permission = permission.pending_permission;
837
+ next.permissions = permission.permissions;
838
+ next.transcript = appendTranscript(next, {
839
+ kind: "permission",
840
+ event: event.event,
841
+ source_event: event.source_event,
842
+ request_id: event.permission?.request_id ?? next.pending_permission?.request_id,
843
+ text: clipText(event.permission?.kind ?? event.permission?.description, next.limits.text_chars),
844
+ seq: event.seq,
845
+ ts: event.ts
846
+ });
847
+ break;
848
+ }
849
+ case "agent.status":
850
+ next.phase = event.phase ?? next.phase;
851
+ next.phase_label = event.phase_label ?? next.phase_label;
852
+ next.transcript = appendTranscript(next, {
853
+ kind: "status",
854
+ event: event.event,
855
+ source_event: event.source_event,
856
+ text: clipText(event.phase_label ?? event.phase, next.limits.text_chars),
857
+ seq: event.seq,
858
+ ts: event.ts
859
+ });
860
+ break;
861
+ case "session.end":
862
+ next.live_tools = [];
863
+ next.tools = next.tools.map((tool) => ({
864
+ ...tool,
865
+ live: false
866
+ }));
867
+ if (!next.final_output) next.final_output = clipText(next.assistant_text, next.limits.text_chars);
868
+ next.pending_permission = void 0;
869
+ next.transcript = appendTranscript(next, {
870
+ kind: "session",
871
+ event: event.event,
872
+ source_event: event.source_event,
873
+ text: clipText(event.final_output ?? event.stop_reason, next.limits.text_chars),
874
+ status: event.stop_reason,
875
+ seq: event.seq,
876
+ ts: event.ts
877
+ });
878
+ break;
879
+ case "session.start":
880
+ next.ended = false;
881
+ next.transcript = appendTranscript(next, {
882
+ kind: "session",
883
+ event: event.event,
884
+ source_event: event.source_event,
885
+ text: clipText(event.backend, next.limits.text_chars),
886
+ seq: event.seq,
887
+ ts: event.ts
888
+ });
889
+ break;
890
+ }
891
+ return {
892
+ ...next,
893
+ _last_semantic_fingerprint: event.semantic_fingerprint ?? next._last_semantic_fingerprint,
894
+ _last_source_event: event.source_event
895
+ };
896
+ }
897
+ var RunReducer = class {
898
+ state;
899
+ constructor(limits, initial) {
900
+ const base = createRunSnapshot(limits);
901
+ if (!initial) {
902
+ this.state = base;
903
+ return;
904
+ }
905
+ const identity = {
906
+ ...base.identity,
907
+ ...initial.identity ?? {}
908
+ };
909
+ const scope = identity.runtime_id ?? identity.session_id ?? "global";
910
+ const seenSeq = initial.latest_seq === void 0 ? {} : { [scope]: initial.latest_seq };
911
+ this.state = {
912
+ ...base,
913
+ ...initial,
914
+ identity,
915
+ limits: base.limits,
916
+ ended: initial.ended ?? base.ended,
917
+ assistant_text: initial.assistant_text ?? base.assistant_text,
918
+ thought_text: initial.thought_text ?? base.thought_text,
919
+ user_text: initial.user_text ?? base.user_text,
920
+ transcript: (initial.transcript ?? base.transcript).map((entry) => ({ ...entry })),
921
+ tools: (initial.tools ?? base.tools).map((tool) => ({ ...tool })),
922
+ live_tools: (initial.live_tools ?? base.live_tools).map((tool) => ({ ...tool })),
923
+ pending_permission: initial.pending_permission ? {
924
+ ...initial.pending_permission,
925
+ options: initial.pending_permission.options.map((option) => ({ ...option }))
926
+ } : void 0,
927
+ permissions: (initial.permissions ?? base.permissions).map((permission) => ({
928
+ ...permission,
929
+ options: permission.options.map((option) => ({ ...option }))
930
+ })),
931
+ _seen_seq_by_scope: seenSeq,
932
+ _recent_legacy_keys: []
933
+ };
934
+ }
935
+ apply(input) {
936
+ this.state = reduceRunSnapshot(this.state, input);
937
+ return this.snapshot();
938
+ }
939
+ snapshot() {
940
+ return {
941
+ ...this.state,
942
+ identity: { ...this.state.identity },
943
+ usage: this.state.usage ? { ...this.state.usage } : void 0,
944
+ transcript: this.state.transcript.map((entry) => ({ ...entry })),
945
+ tools: this.state.tools.map((tool) => ({ ...tool })),
946
+ live_tools: this.state.live_tools.map((tool) => ({ ...tool })),
947
+ pending_permission: this.state.pending_permission ? {
948
+ ...this.state.pending_permission,
949
+ options: this.state.pending_permission.options.map((option) => ({ ...option }))
950
+ } : void 0,
951
+ permissions: this.state.permissions.map((permission) => ({
952
+ ...permission,
953
+ options: permission.options.map((option) => ({ ...option }))
954
+ })),
955
+ _seen_seq_by_scope: { ...this.state._seen_seq_by_scope },
956
+ _recent_legacy_keys: [...this.state._recent_legacy_keys]
957
+ };
958
+ }
959
+ };
960
+ //#endregion
961
+ //#region src/run-observer.ts
962
+ function observeRun(client, options = {}) {
963
+ const reducer = new RunReducer(options.reducer_limits, options.initial_snapshot);
964
+ const listeners = /* @__PURE__ */ new Set();
965
+ const pendingNext = [];
966
+ const streamOptions = {};
967
+ if (options.runtime_id) {
968
+ streamOptions.runtime_id = options.runtime_id;
969
+ streamOptions.replay = options.replay ?? true;
970
+ } else if (options.replay !== void 0) streamOptions.replay = options.replay;
971
+ if (options.limit !== void 0) streamOptions.limit = options.limit;
972
+ if (options.after_seq !== void 0) streamOptions.after_seq = options.after_seq;
973
+ const iterator = client.events(streamOptions)[Symbol.asyncIterator]();
974
+ let closed = false;
975
+ let clientClosed = false;
976
+ let failure = null;
977
+ const closeClient = () => {
978
+ if (!options.close_client || clientClosed) return;
979
+ clientClosed = true;
980
+ client.close?.();
981
+ };
982
+ const flushPending = (snapshot) => {
983
+ const unresolved = pendingNext.splice(0, pendingNext.length);
984
+ for (const waiter of unresolved) {
985
+ if (failure) {
986
+ waiter.reject(failure);
987
+ continue;
988
+ }
989
+ if (snapshot && waiter.predicate && !waiter.predicate(snapshot)) {
990
+ pendingNext.push(waiter);
991
+ continue;
992
+ }
993
+ waiter.resolve(snapshot);
994
+ }
995
+ };
996
+ const loop = (async () => {
997
+ try {
998
+ while (!closed) {
999
+ const result = await iterator.next();
1000
+ if (result.done || closed) break;
1001
+ const snapshot = reducer.apply(result.value);
1002
+ for (const listener of listeners) listener(snapshot, result.value);
1003
+ flushPending(snapshot);
1004
+ }
1005
+ } catch (error) {
1006
+ failure = error instanceof Error ? error : new Error(String(error));
1007
+ } finally {
1008
+ closed = true;
1009
+ flushPending(null);
1010
+ }
1011
+ })();
1012
+ return {
1013
+ get closed() {
1014
+ return closed;
1015
+ },
1016
+ snapshot() {
1017
+ return reducer.snapshot();
1018
+ },
1019
+ subscribe(listener) {
1020
+ listeners.add(listener);
1021
+ return () => {
1022
+ listeners.delete(listener);
1023
+ };
1024
+ },
1025
+ next(predicate) {
1026
+ if (failure) return Promise.reject(failure);
1027
+ if (closed) return Promise.resolve(null);
1028
+ return new Promise((resolve, reject) => {
1029
+ pendingNext.push({
1030
+ resolve,
1031
+ reject,
1032
+ predicate
1033
+ });
1034
+ });
1035
+ },
1036
+ async close() {
1037
+ try {
1038
+ if (!closed) {
1039
+ closed = true;
1040
+ if (typeof iterator.return === "function") await iterator.return();
1041
+ }
1042
+ await loop.catch(() => {});
1043
+ } finally {
1044
+ closeClient();
1045
+ }
1046
+ }
1047
+ };
1048
+ }
1049
+ //#endregion
193
1050
  //#region src/paths.ts
194
1051
  function avenorHome() {
195
1052
  return process.env.AVENOR_HOME || path.join(os.homedir(), ".avenor");
@@ -591,33 +1448,77 @@ function translateStatus(rawPhase, sentinelData) {
591
1448
  default: return "running";
592
1449
  }
593
1450
  }
594
- async function buildRunStatus(client, runInfo, liveStatus) {
1451
+ function normalizePermission(raw) {
1452
+ if (!raw) return void 0;
1453
+ const request_id = stringField(raw, "request_id", "requestID") ?? "";
1454
+ const description = stringField(raw, "description", "question", "tool", "title") ?? "";
1455
+ const optionsRaw = raw.options;
1456
+ const options = Array.isArray(optionsRaw) ? optionsRaw.map((item) => {
1457
+ const record = asRecord(item);
1458
+ if (!record) return null;
1459
+ return {
1460
+ option_id: stringField(record, "option_id", "optionId") ?? "",
1461
+ label: stringField(record, "label") ?? "",
1462
+ kind: stringField(record, "kind") ?? ""
1463
+ };
1464
+ }).filter((item) => item !== null) : [];
1465
+ if (!request_id && !description && options.length === 0) return;
1466
+ return {
1467
+ request_id,
1468
+ description,
1469
+ options
1470
+ };
1471
+ }
1472
+ function extractPendingPermission(liveStatus) {
1473
+ if (!liveStatus) return void 0;
1474
+ const permission = normalizePermission(asRecord(liveStatus.permission));
1475
+ if (permission) return permission;
1476
+ const pendingPermission = liveStatus.pending_permission;
1477
+ if (typeof pendingPermission === "boolean") return;
1478
+ return normalizePermission(asRecord(pendingPermission));
1479
+ }
1480
+ function buildBaseStatus(source, fallback, translatedStatus) {
1481
+ return {
1482
+ run_id: stringField(source, "run_id") ?? fallback.run_id,
1483
+ label: stringField(source, "label") ?? fallback.label,
1484
+ status: translatedStatus,
1485
+ runtime_id: stringField(source, "runtime_id", "id") ?? fallback.runtime_id,
1486
+ phase: stringField(source, "phase"),
1487
+ phase_label: stringField(source, "phase_label"),
1488
+ pending_permission: extractPendingPermission(source),
1489
+ session_id: stringField(source, "session_id") ?? fallback.session_id,
1490
+ stop_reason: stringField(source, "stop_reason") ?? fallback.stop_reason,
1491
+ backend: stringField(source, "backend"),
1492
+ agent: stringField(source, "agent"),
1493
+ model: stringField(source, "model"),
1494
+ dir: stringField(source, "dir"),
1495
+ parent_id: stringField(source, "parent_id", "parentId"),
1496
+ children: stringArrayField(source, "children"),
1497
+ event_path: stringField(source, "event_path", "on_event"),
1498
+ usage: asRecord(source.usage) ?? void 0,
1499
+ latest_seq: numberField(source, "latest_seq"),
1500
+ final_output: stringField(source, "final_output", "finalOutput")
1501
+ };
1502
+ }
1503
+ async function buildRunStatus(runInfo, liveStatus) {
595
1504
  let sentinel = null;
596
1505
  if (await sentinelExists(runInfo.sentinelPath)) sentinel = await parseSentinel$1(runInfo.sentinelPath);
597
1506
  const translated = translateStatus(liveStatus?.phase ?? liveStatus?.status ?? "running", sentinel);
598
- const result = {
1507
+ return buildBaseStatus(liveStatus ?? {}, {
599
1508
  run_id: runInfo.runId,
600
1509
  label: runInfo.label,
601
- status: translated,
602
- runtime_id: liveStatus?.runtime_id ?? runInfo.runtimeId,
603
- phase: liveStatus?.phase,
604
- phase_label: liveStatus?.phase_label,
605
- session_id: sentinel?.SESSION ?? liveStatus?.session_id ?? void 0,
606
- stop_reason: sentinel?._status ?? void 0
607
- };
608
- if (liveStatus?.pending_permission) {
609
- const pp = liveStatus.pending_permission;
610
- result.pending_permission = {
611
- request_id: pp.request_id ?? "",
612
- description: pp.description ?? "",
613
- options: Array.isArray(pp.options) ? pp.options.map((o) => ({
614
- option_id: o.option_id ?? "",
615
- label: o.label ?? "",
616
- kind: o.kind ?? ""
617
- })) : []
618
- };
1510
+ runtime_id: runInfo.runtimeId,
1511
+ session_id: sentinel?.SESSION ?? liveStatus?.session_id ?? runInfo.sessionId,
1512
+ stop_reason: sentinel?._status
1513
+ }, translated);
1514
+ }
1515
+ async function queryLiveStatus(client, runtimeId) {
1516
+ if (!runtimeId) return null;
1517
+ try {
1518
+ return await client.status(runtimeId);
1519
+ } catch {
1520
+ return null;
619
1521
  }
620
- return result;
621
1522
  }
622
1523
  async function statusTool(args) {
623
1524
  if (args.supervisorId) {
@@ -626,47 +1527,34 @@ async function statusTool(args) {
626
1527
  if (args.runId) {
627
1528
  validateRunId(args.runId);
628
1529
  const runInfo = isSingleton && sup ? findRunByLabel$3(sup, args.runId) : void 0;
629
- if (runInfo) {
630
- let liveStatus = null;
631
- if (runInfo.runtimeId) try {
632
- liveStatus = await client.status(runInfo.runtimeId);
633
- } catch {}
634
- return buildRunStatus(client, runInfo, liveStatus);
635
- }
636
- let liveStatus = null;
637
- try {
638
- liveStatus = await client.status(args.runId);
1530
+ if (runInfo) return buildRunStatus(runInfo, await queryLiveStatus(client, runInfo.runtimeId));
1531
+ let liveStatus = await queryLiveStatus(client, args.runId);
1532
+ if (!liveStatus) try {
1533
+ const entry = (await client.list()).find((item) => {
1534
+ const runtimeId = String(item.runtime_id ?? item.id ?? "");
1535
+ const label = String(item.label ?? "");
1536
+ const runId = String(item.run_id ?? "");
1537
+ return runtimeId === args.runId || label === args.runId || runId === args.runId;
1538
+ });
1539
+ if (entry) liveStatus = await queryLiveStatus(client, String(entry.runtime_id ?? entry.id ?? "")) ?? entry;
639
1540
  } catch {}
640
1541
  const sentinelPath = liveStatus?.sentinel_file ?? path.join(runsRoot(), args.runId, "sentinel.done");
641
1542
  let sentinel = null;
642
1543
  if (await sentinelExists(sentinelPath)) sentinel = await parseSentinel$1(sentinelPath);
643
- return {
1544
+ return buildBaseStatus(liveStatus ?? {}, {
644
1545
  run_id: args.runId,
645
1546
  label: args.runId,
646
- status: translateStatus(liveStatus?.phase ?? liveStatus?.status ?? "running", sentinel),
647
- runtime_id: liveStatus?.runtime_id,
648
- phase: liveStatus?.phase,
649
- phase_label: liveStatus?.phase_label,
650
- session_id: sentinel?.SESSION ?? liveStatus?.session_id ?? void 0,
651
- stop_reason: sentinel?._status ?? void 0,
652
- pending_permission: liveStatus?.pending_permission ? {
653
- request_id: liveStatus.pending_permission.request_id ?? "",
654
- description: liveStatus.pending_permission.description ?? "",
655
- options: Array.isArray(liveStatus.pending_permission.options) ? liveStatus.pending_permission.options.map((o) => ({
656
- option_id: o.option_id ?? "",
657
- label: o.label ?? "",
658
- kind: o.kind ?? ""
659
- })) : []
660
- } : void 0
661
- };
1547
+ runtime_id: stringField(liveStatus ?? {}, "runtime_id", "id"),
1548
+ session_id: sentinel?.SESSION ?? liveStatus?.session_id,
1549
+ stop_reason: sentinel?._status
1550
+ }, translateStatus(liveStatus?.phase ?? liveStatus?.status ?? "running", sentinel));
662
1551
  }
663
- return (await client.list()).map((entry) => ({
664
- run_id: entry.runtime_id ?? entry.id ?? "",
665
- label: entry.label ?? "",
666
- status: translateStatus(entry.phase ?? entry.status ?? "running", null),
667
- phase: entry.phase,
1552
+ return (await client.list()).map((entry) => buildBaseStatus(entry, {
1553
+ run_id: String(entry.run_id ?? entry.runtime_id ?? entry.id ?? ""),
1554
+ label: String(entry.label ?? entry.runtime_id ?? entry.id ?? ""),
1555
+ runtime_id: String(entry.runtime_id ?? entry.id ?? ""),
668
1556
  session_id: entry.session_id
669
- }));
1557
+ }, translateStatus(entry.phase ?? entry.status ?? "running", null)));
670
1558
  } finally {
671
1559
  if (!isSingleton) client.close();
672
1560
  }
@@ -675,58 +1563,34 @@ async function statusTool(args) {
675
1563
  const client = sup.getClient();
676
1564
  if (args.runId) {
677
1565
  const runInfo = findRunByLabel$3(sup, args.runId);
678
- if (runInfo) {
679
- let liveStatus = null;
680
- if (runInfo.runtimeId) try {
681
- liveStatus = await client.status(runInfo.runtimeId);
682
- } catch {}
683
- return buildRunStatus(client, runInfo, liveStatus);
684
- }
1566
+ if (runInfo) return buildRunStatus(runInfo, await queryLiveStatus(client, runInfo.runtimeId));
685
1567
  validateRunId(args.runId);
686
- let liveStatus = null;
687
- try {
688
- liveStatus = await client.status(args.runId);
689
- } catch {}
1568
+ const liveStatus = await queryLiveStatus(client, args.runId);
690
1569
  const sentinelPath = path.join(runsRoot(), args.runId, "sentinel.done");
691
1570
  let sentinel = null;
692
1571
  if (await sentinelExists(sentinelPath)) sentinel = await parseSentinel$1(sentinelPath);
693
- return {
1572
+ return buildBaseStatus(liveStatus ?? {}, {
694
1573
  run_id: args.runId,
695
1574
  label: args.runId,
696
- status: translateStatus(liveStatus?.phase ?? liveStatus?.status ?? "running", sentinel),
697
- phase: liveStatus?.phase,
698
- phase_label: liveStatus?.phase_label,
699
- session_id: sentinel?.SESSION ?? liveStatus?.session_id ?? void 0,
700
- stop_reason: sentinel?._status ?? void 0,
701
- pending_permission: liveStatus?.pending_permission ? {
702
- request_id: liveStatus.pending_permission.request_id ?? "",
703
- description: liveStatus.pending_permission.description ?? "",
704
- options: Array.isArray(liveStatus.pending_permission.options) ? liveStatus.pending_permission.options.map((o) => ({
705
- option_id: o.option_id ?? "",
706
- label: o.label ?? "",
707
- kind: o.kind ?? ""
708
- })) : []
709
- } : void 0
710
- };
1575
+ runtime_id: stringField(liveStatus ?? {}, "runtime_id", "id"),
1576
+ session_id: sentinel?.SESSION ?? liveStatus?.session_id,
1577
+ stop_reason: sentinel?._status
1578
+ }, translateStatus(liveStatus?.phase ?? liveStatus?.status ?? "running", sentinel));
711
1579
  }
712
1580
  const list = await client.list();
713
1581
  const results = [];
714
1582
  for (const entry of list) {
715
- const entryId = entry.runtime_id ?? entry.id ?? "";
716
- const entryLabel = entry.label ?? entryId;
1583
+ const entryId = String(entry.runtime_id ?? entry.id ?? "");
1584
+ const entryLabel = String(entry.label ?? entryId);
717
1585
  const runInfo = findRunByLabel$3(sup, entryId) ?? findRunByLabel$3(sup, entryLabel);
718
- let liveStatus = null;
719
- if (runInfo?.runtimeId) try {
720
- liveStatus = await client.status(runInfo.runtimeId);
721
- } catch {}
722
- if (runInfo && liveStatus) results.push(await buildRunStatus(client, runInfo, liveStatus));
723
- else results.push({
724
- run_id: entryId,
1586
+ const liveStatus = runInfo ? await queryLiveStatus(client, runInfo.runtimeId) : null;
1587
+ if (runInfo && liveStatus) results.push(await buildRunStatus(runInfo, liveStatus));
1588
+ else results.push(buildBaseStatus(entry, {
1589
+ run_id: String(entry.run_id ?? entryId),
725
1590
  label: entryLabel,
726
- status: translateStatus(entry.phase ?? entry.status ?? "running", null),
727
- phase: entry.phase,
1591
+ runtime_id: entryId,
728
1592
  session_id: entry.session_id
729
- });
1593
+ }, translateStatus(entry.phase ?? entry.status ?? "running", null)));
730
1594
  }
731
1595
  return results;
732
1596
  }
@@ -843,41 +1707,240 @@ async function followUpTool(args) {
843
1707
  }
844
1708
  //#endregion
845
1709
  //#region src/tools/events.ts
1710
+ const MAX_EVENT_LIMIT = 1e3;
1711
+ const MAX_STRING_CHARS = 4e3;
1712
+ const MAX_ARRAY_ITEMS = 32;
1713
+ const MAX_OBJECT_KEYS = 64;
846
1714
  function findRunByLabel(sup, runId) {
847
1715
  const runs = sup.runs;
848
1716
  const byKey = runs.get(runId);
849
1717
  if (byKey) return byKey;
850
1718
  for (const info of runs.values()) if (info.label === runId) return info;
851
1719
  }
852
- async function eventsTool(args) {
853
- if (args.supervisorId) throw new Error("eventsTool with explicit supervisorId not supported — event log path must be tracked by the singleton supervisor");
854
- const runInfo = findRunByLabel(await Supervisor.get(), args.runId);
855
- if (!runInfo) throw new Error(`run not found: ${args.runId}`);
856
- const limit = Math.max(1, Math.min(1e3, Math.trunc(args.limit ?? 50)));
857
- let raw;
1720
+ function normalizeLimit(limit) {
1721
+ return Math.max(1, Math.min(MAX_EVENT_LIMIT, Math.trunc(limit ?? 50)));
1722
+ }
1723
+ function contextualError(context, error) {
1724
+ const message = error instanceof Error ? error.message : String(error);
1725
+ return /* @__PURE__ */ new Error(`${context}: ${message}`);
1726
+ }
1727
+ function eventName(record) {
1728
+ const event = record.event;
1729
+ if (typeof event === "string" && event.length > 0) return event;
1730
+ const type = record.type;
1731
+ return typeof type === "string" ? type : "";
1732
+ }
1733
+ function eventSeq(record) {
1734
+ const value = record.seq;
1735
+ if (typeof value === "number" && Number.isFinite(value)) return value;
1736
+ if (typeof value === "string" && value.trim().length > 0) {
1737
+ const parsed = Number(value);
1738
+ if (Number.isFinite(parsed)) return parsed;
1739
+ }
1740
+ }
1741
+ function clipValue(value) {
1742
+ if (typeof value === "string") {
1743
+ if (value.length <= MAX_STRING_CHARS) return value;
1744
+ return value.slice(value.length - MAX_STRING_CHARS);
1745
+ }
1746
+ if (Array.isArray(value)) return (value.length <= MAX_ARRAY_ITEMS ? value : value.slice(value.length - MAX_ARRAY_ITEMS)).map((item) => clipValue(item));
1747
+ const record = asRecord(value);
1748
+ if (!record) return value;
1749
+ const result = {};
1750
+ const entries = Object.entries(record).slice(0, MAX_OBJECT_KEYS);
1751
+ for (const [key, item] of entries) result[key] = clipValue(item);
1752
+ return result;
1753
+ }
1754
+ function clipEvent(record) {
1755
+ return clipValue(record);
1756
+ }
1757
+ function matchesFilters(record, args) {
1758
+ const name = eventName(record);
1759
+ if (args.types && args.types.length > 0 && !args.types.includes(name)) return false;
1760
+ const seq = eventSeq(record);
1761
+ if (args.after_seq !== void 0 && seq !== void 0 && seq <= args.after_seq) return false;
1762
+ return true;
1763
+ }
1764
+ async function readNdjsonEvents(filePath, args) {
1765
+ const stream = fs.createReadStream(filePath, { encoding: "utf-8" });
1766
+ const rl = readline.createInterface({
1767
+ input: stream,
1768
+ crlfDelay: Infinity
1769
+ });
1770
+ const events = [];
858
1771
  try {
859
- const stat = await fs.promises.stat(runInfo.eventLogPath);
860
- const tailBytes = Math.min(stat.size, 256 * 1024);
861
- const handle = await fs.promises.open(runInfo.eventLogPath, "r");
1772
+ for await (const line of rl) {
1773
+ if (!line.trim()) continue;
1774
+ try {
1775
+ const record = asRecord(JSON.parse(line));
1776
+ if (!record || !matchesFilters(record, args)) continue;
1777
+ events.push(clipEvent(record));
1778
+ if (events.length > args.limit) events.splice(0, events.length - args.limit);
1779
+ } catch {}
1780
+ }
1781
+ } finally {
1782
+ rl.close();
1783
+ stream.close();
1784
+ }
1785
+ return events;
1786
+ }
1787
+ async function resolveRun(args) {
1788
+ if (args.supervisorId) {
1789
+ const { client, isSingleton, sup } = await getSupervisorClient(args.supervisorId);
1790
+ const runInfo = isSingleton && sup ? findRunByLabel(sup, args.runId) : void 0;
1791
+ if (runInfo) return {
1792
+ client,
1793
+ isSingleton,
1794
+ runInfo,
1795
+ runtimeId: runInfo.runtimeId,
1796
+ eventLogPath: runInfo.eventLogPath
1797
+ };
1798
+ let runtimeId;
1799
+ let eventLogPath;
862
1800
  try {
863
- const buffer = Buffer.alloc(tailBytes);
864
- const start = Math.max(0, stat.size - tailBytes);
865
- const { bytesRead } = await handle.read(buffer, 0, tailBytes, start);
866
- raw = buffer.subarray(0, bytesRead).toString("utf-8");
867
- } finally {
868
- await handle.close();
1801
+ const direct = await client.status(args.runId);
1802
+ runtimeId = direct.runtime_id ?? args.runId;
1803
+ eventLogPath = direct.event_path ?? direct.on_event;
1804
+ } catch (statusError) {
1805
+ let list;
1806
+ try {
1807
+ list = await client.list();
1808
+ } catch (listError) {
1809
+ if (!isSingleton) client.close();
1810
+ throw new AggregateError([contextualError(`status lookup for ${args.runId}`, statusError), contextualError("runtime list fallback", listError)], `unable to resolve run ${args.runId}`);
1811
+ }
1812
+ const match = list.find((entry) => {
1813
+ const runtime = String(entry.runtime_id ?? entry.id ?? "");
1814
+ const label = String(entry.label ?? "");
1815
+ const runId = String(entry.run_id ?? "");
1816
+ return runtime === args.runId || label === args.runId || runId === args.runId;
1817
+ });
1818
+ if (!match) {
1819
+ if (!isSingleton) client.close();
1820
+ throw contextualError(`unable to resolve run ${args.runId}`, statusError);
1821
+ }
1822
+ runtimeId = String(match.runtime_id ?? match.id ?? "") || void 0;
1823
+ eventLogPath = match.event_path ?? match.on_event;
869
1824
  }
870
- } catch {
1825
+ return {
1826
+ client,
1827
+ isSingleton,
1828
+ runtimeId,
1829
+ eventLogPath
1830
+ };
1831
+ }
1832
+ const sup = await Supervisor.get();
1833
+ const runInfo = findRunByLabel(sup, args.runId);
1834
+ if (runInfo) return {
1835
+ client: sup.getClient(),
1836
+ isSingleton: true,
1837
+ runInfo,
1838
+ runtimeId: runInfo.runtimeId,
1839
+ eventLogPath: runInfo.eventLogPath
1840
+ };
1841
+ validateRunId(args.runId);
1842
+ let runtimeId;
1843
+ let eventLogPath;
1844
+ try {
1845
+ const direct = await sup.getClient().status(args.runId);
1846
+ runtimeId = direct.runtime_id ?? args.runId;
1847
+ eventLogPath = direct.event_path ?? direct.on_event;
1848
+ } catch (error) {
1849
+ throw contextualError(`unable to resolve run ${args.runId}`, error);
1850
+ }
1851
+ return {
1852
+ client: sup.getClient(),
1853
+ isSingleton: true,
1854
+ runtimeId,
1855
+ eventLogPath
1856
+ };
1857
+ }
1858
+ async function eventsTool(args) {
1859
+ const limit = normalizeLimit(args.limit);
1860
+ const resolved = await resolveRun(args);
1861
+ try {
1862
+ const failures = [];
1863
+ if (resolved.eventLogPath) try {
1864
+ return { events: await readNdjsonEvents(resolved.eventLogPath, {
1865
+ types: args.types,
1866
+ limit,
1867
+ after_seq: args.after_seq
1868
+ }) };
1869
+ } catch (error) {
1870
+ failures.push(contextualError(`read event log ${resolved.eventLogPath}`, error));
1871
+ }
1872
+ if (resolved.runtimeId) try {
1873
+ const history = await resolved.client.history({
1874
+ runtime_id: resolved.runtimeId,
1875
+ limit,
1876
+ after_seq: args.after_seq
1877
+ });
1878
+ return { events: Array.isArray(history.events) ? history.events.map((event) => asRecord(event)).filter((event) => event !== null).filter((event) => matchesFilters(event, args)).map((event) => clipEvent(event)) : [] };
1879
+ } catch (error) {
1880
+ failures.push(contextualError(`read history for ${resolved.runtimeId}`, error));
1881
+ }
1882
+ if (failures.length > 0) throw new AggregateError(failures, `unable to read events for run ${args.runId}`);
871
1883
  return { events: [] };
1884
+ } finally {
1885
+ if (!resolved.isSingleton) resolved.client.close();
872
1886
  }
873
- const lines = raw.split("\n").filter((line) => line.trim());
874
- let events = [];
875
- for (const line of lines) try {
876
- events.push(JSON.parse(line));
877
- } catch {}
878
- if (args.types && args.types.length > 0) events = events.filter((e) => args.types.includes(e.type ?? e.event ?? ""));
879
- if (events.length > limit) events = events.slice(events.length - limit);
880
- return { events };
1887
+ }
1888
+ //#endregion
1889
+ //#region src/tools/inspect.ts
1890
+ function publicSnapshot(snapshot) {
1891
+ const { _seen_seq_by_scope: _seen, _recent_legacy_keys: _recent, _last_semantic_fingerprint: _fingerprint, _last_source_event: _source, ...rest } = snapshot;
1892
+ return rest;
1893
+ }
1894
+ async function inspectTool(args) {
1895
+ const rawStatus = await statusTool({
1896
+ runId: args.runId,
1897
+ supervisorId: args.supervisorId
1898
+ });
1899
+ const status = Array.isArray(rawStatus) ? rawStatus[0] : rawStatus;
1900
+ if (!status) throw new Error(`run not found: ${args.runId}`);
1901
+ const reducer = new RunReducer();
1902
+ const { events } = await eventsTool({
1903
+ runId: args.runId,
1904
+ supervisorId: args.supervisorId,
1905
+ limit: args.limit,
1906
+ after_seq: args.after_seq
1907
+ });
1908
+ for (const event of events) reducer.apply(event);
1909
+ const snapshot = reducer.snapshot();
1910
+ snapshot.identity = {
1911
+ ...snapshot.identity,
1912
+ runtime_id: status.runtime_id ?? snapshot.identity.runtime_id,
1913
+ session_id: status.session_id ?? snapshot.identity.session_id,
1914
+ run_id: status.run_id ?? snapshot.identity.run_id,
1915
+ run_label: status.label ?? snapshot.identity.run_label,
1916
+ backend: status.backend ?? snapshot.identity.backend,
1917
+ agent: status.agent ?? snapshot.identity.agent,
1918
+ model: status.model ?? snapshot.identity.model,
1919
+ dir: status.dir ?? snapshot.identity.dir,
1920
+ parent_id: status.parent_id ?? snapshot.identity.parent_id,
1921
+ children: status.children ?? snapshot.identity.children,
1922
+ event_path: status.event_path ?? snapshot.identity.event_path
1923
+ };
1924
+ snapshot.phase = status.phase ?? snapshot.phase;
1925
+ snapshot.phase_label = status.phase_label ?? snapshot.phase_label;
1926
+ snapshot.latest_seq = status.latest_seq ?? snapshot.latest_seq;
1927
+ snapshot.usage = status.usage ?? snapshot.usage;
1928
+ snapshot.stop_reason = status.stop_reason ?? snapshot.stop_reason;
1929
+ snapshot.final_output = status.final_output ?? snapshot.final_output ?? snapshot.assistant_text;
1930
+ snapshot.pending_permission = status.pending_permission ?? snapshot.pending_permission;
1931
+ const clean = publicSnapshot(snapshot);
1932
+ return {
1933
+ run_id: status.run_id,
1934
+ label: status.label,
1935
+ status,
1936
+ snapshot: clean,
1937
+ transcript: clean.transcript,
1938
+ tools: clean.tools,
1939
+ live_tools: clean.live_tools,
1940
+ permissions: clean.permissions,
1941
+ pending_permission: clean.pending_permission,
1942
+ final_output: clean.final_output
1943
+ };
881
1944
  }
882
1945
  //#endregion
883
1946
  //#region src/tools/shutdown.ts
@@ -936,4 +1999,4 @@ async function shutdownTool(args) {
936
1999
  };
937
2000
  }
938
2001
  //#endregion
939
- export { Supervisor, answerPermissionTool, dial, eventsTool, findAvenorBinary, followUpTool, getInstallDir, getVersion, installerBinaryPath, shutdownTool, spawnTool, statusTool };
2002
+ export { DEFAULT_RUN_REDUCER_LIMITS, RunReducer, Supervisor, answerPermissionTool, createRunSnapshot, dial, eventsTool, extractEventText, findAvenorBinary, followUpTool, getInstallDir, getVersion, inspectTool, installerBinaryPath, normalizeRunEvent, observeRun, reduceRunSnapshot, shutdownTool, spawnTool, statusTool };