@dougbots/avenor-core 0.3.6 → 0.4.0

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 +1222 -154
  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");
@@ -447,26 +1304,23 @@ function validateTimeout(value) {
447
1304
  return amount;
448
1305
  }
449
1306
  function validateSupervisorSocketPath(supervisorId) {
450
- const canonical = fs.realpathSync(supervisorId);
451
1307
  const root = fs.realpathSync(socketsRoot());
452
- const relative = path.relative(root, canonical);
453
- if (relative.startsWith("..") || path.isAbsolute(relative) || !/^avenor-mcp-[^/\\]+\.sock$/.test(path.basename(canonical))) throw new Error(`invalid supervisorId: ${supervisorId}`);
454
- return canonical;
1308
+ const parent = fs.realpathSync(path.dirname(supervisorId));
1309
+ const basename = path.basename(supervisorId);
1310
+ if (parent !== root || !/^avenor-mcp-[^/\\]+\.sock$/.test(basename)) throw new Error(`invalid supervisorId: ${supervisorId}`);
1311
+ return path.join(parent, basename);
455
1312
  }
456
1313
  //#endregion
457
1314
  //#region src/tools/get-supervisor-client.ts
458
1315
  async function getSupervisorClient(supervisorId) {
459
1316
  const id = validateSupervisorSocketPath(supervisorId);
460
- if (Supervisor.isCurrentInstance(id)) {
461
- const sup = Supervisor.currentInstance();
462
- if (!sup) throw new Error(`Supervisor.isCurrentInstance() returned true but currentInstance() is null (id: ${id})`);
463
- return {
464
- client: sup.getClient(),
465
- isSingleton: true,
466
- sup,
467
- supervisorId: id
468
- };
469
- }
1317
+ const current = Supervisor.currentInstance();
1318
+ if (current !== null && validateSupervisorSocketPath(current.supervisorId) === id) return {
1319
+ client: current.getClient(),
1320
+ isSingleton: true,
1321
+ sup: current,
1322
+ supervisorId: id
1323
+ };
470
1324
  return {
471
1325
  client: await dial(id),
472
1326
  isSingleton: false,
@@ -594,74 +1448,113 @@ function translateStatus(rawPhase, sentinelData) {
594
1448
  default: return "running";
595
1449
  }
596
1450
  }
597
- 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) {
598
1504
  let sentinel = null;
599
1505
  if (await sentinelExists(runInfo.sentinelPath)) sentinel = await parseSentinel$1(runInfo.sentinelPath);
600
1506
  const translated = translateStatus(liveStatus?.phase ?? liveStatus?.status ?? "running", sentinel);
601
- const result = {
1507
+ return buildBaseStatus(liveStatus ?? {}, {
602
1508
  run_id: runInfo.runId,
603
1509
  label: runInfo.label,
604
- status: translated,
605
- runtime_id: liveStatus?.runtime_id ?? runInfo.runtimeId,
606
- phase: liveStatus?.phase,
607
- phase_label: liveStatus?.phase_label,
608
- session_id: sentinel?.SESSION ?? liveStatus?.session_id ?? void 0,
609
- stop_reason: sentinel?._status ?? void 0
610
- };
611
- if (liveStatus?.pending_permission) {
612
- const pp = liveStatus.pending_permission;
613
- result.pending_permission = {
614
- request_id: pp.request_id ?? "",
615
- description: pp.description ?? "",
616
- options: Array.isArray(pp.options) ? pp.options.map((o) => ({
617
- option_id: o.option_id ?? "",
618
- label: o.label ?? "",
619
- kind: o.kind ?? ""
620
- })) : []
621
- };
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;
622
1521
  }
623
- return result;
624
1522
  }
625
1523
  async function statusTool(args) {
626
1524
  if (args.supervisorId) {
627
- const { client, isSingleton } = await getSupervisorClient(args.supervisorId);
1525
+ const { client, isSingleton, sup } = await getSupervisorClient(args.supervisorId);
628
1526
  try {
629
1527
  if (args.runId) {
630
1528
  validateRunId(args.runId);
631
- let liveStatus = null;
632
- try {
633
- liveStatus = await client.status(args.runId);
1529
+ const runInfo = isSingleton && sup ? findRunByLabel$3(sup, args.runId) : void 0;
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;
634
1540
  } catch {}
635
1541
  const sentinelPath = liveStatus?.sentinel_file ?? path.join(runsRoot(), args.runId, "sentinel.done");
636
1542
  let sentinel = null;
637
1543
  if (await sentinelExists(sentinelPath)) sentinel = await parseSentinel$1(sentinelPath);
638
- return {
1544
+ return buildBaseStatus(liveStatus ?? {}, {
639
1545
  run_id: args.runId,
640
1546
  label: args.runId,
641
- status: translateStatus(liveStatus?.phase ?? liveStatus?.status ?? "running", sentinel),
642
- runtime_id: liveStatus?.runtime_id,
643
- phase: liveStatus?.phase,
644
- phase_label: liveStatus?.phase_label,
645
- session_id: sentinel?.SESSION ?? liveStatus?.session_id ?? void 0,
646
- stop_reason: sentinel?._status ?? void 0,
647
- pending_permission: liveStatus?.pending_permission ? {
648
- request_id: liveStatus.pending_permission.request_id ?? "",
649
- description: liveStatus.pending_permission.description ?? "",
650
- options: Array.isArray(liveStatus.pending_permission.options) ? liveStatus.pending_permission.options.map((o) => ({
651
- option_id: o.option_id ?? "",
652
- label: o.label ?? "",
653
- kind: o.kind ?? ""
654
- })) : []
655
- } : void 0
656
- };
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));
657
1551
  }
658
- return (await client.list()).map((entry) => ({
659
- run_id: entry.runtime_id ?? entry.id ?? "",
660
- label: entry.label ?? "",
661
- status: translateStatus(entry.phase ?? entry.status ?? "running", null),
662
- 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 ?? ""),
663
1556
  session_id: entry.session_id
664
- }));
1557
+ }, translateStatus(entry.phase ?? entry.status ?? "running", null)));
665
1558
  } finally {
666
1559
  if (!isSingleton) client.close();
667
1560
  }
@@ -670,58 +1563,34 @@ async function statusTool(args) {
670
1563
  const client = sup.getClient();
671
1564
  if (args.runId) {
672
1565
  const runInfo = findRunByLabel$3(sup, args.runId);
673
- if (runInfo) {
674
- let liveStatus = null;
675
- if (runInfo.runtimeId) try {
676
- liveStatus = await client.status(runInfo.runtimeId);
677
- } catch {}
678
- return buildRunStatus(client, runInfo, liveStatus);
679
- }
1566
+ if (runInfo) return buildRunStatus(runInfo, await queryLiveStatus(client, runInfo.runtimeId));
680
1567
  validateRunId(args.runId);
681
- let liveStatus = null;
682
- try {
683
- liveStatus = await client.status(args.runId);
684
- } catch {}
1568
+ const liveStatus = await queryLiveStatus(client, args.runId);
685
1569
  const sentinelPath = path.join(runsRoot(), args.runId, "sentinel.done");
686
1570
  let sentinel = null;
687
1571
  if (await sentinelExists(sentinelPath)) sentinel = await parseSentinel$1(sentinelPath);
688
- return {
1572
+ return buildBaseStatus(liveStatus ?? {}, {
689
1573
  run_id: args.runId,
690
1574
  label: args.runId,
691
- status: translateStatus(liveStatus?.phase ?? liveStatus?.status ?? "running", sentinel),
692
- phase: liveStatus?.phase,
693
- phase_label: liveStatus?.phase_label,
694
- session_id: sentinel?.SESSION ?? liveStatus?.session_id ?? void 0,
695
- stop_reason: sentinel?._status ?? void 0,
696
- pending_permission: liveStatus?.pending_permission ? {
697
- request_id: liveStatus.pending_permission.request_id ?? "",
698
- description: liveStatus.pending_permission.description ?? "",
699
- options: Array.isArray(liveStatus.pending_permission.options) ? liveStatus.pending_permission.options.map((o) => ({
700
- option_id: o.option_id ?? "",
701
- label: o.label ?? "",
702
- kind: o.kind ?? ""
703
- })) : []
704
- } : void 0
705
- };
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));
706
1579
  }
707
1580
  const list = await client.list();
708
1581
  const results = [];
709
1582
  for (const entry of list) {
710
- const entryId = entry.runtime_id ?? entry.id ?? "";
711
- const entryLabel = entry.label ?? entryId;
1583
+ const entryId = String(entry.runtime_id ?? entry.id ?? "");
1584
+ const entryLabel = String(entry.label ?? entryId);
712
1585
  const runInfo = findRunByLabel$3(sup, entryId) ?? findRunByLabel$3(sup, entryLabel);
713
- let liveStatus = null;
714
- if (runInfo?.runtimeId) try {
715
- liveStatus = await client.status(runInfo.runtimeId);
716
- } catch {}
717
- if (runInfo && liveStatus) results.push(await buildRunStatus(client, runInfo, liveStatus));
718
- else results.push({
719
- 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),
720
1590
  label: entryLabel,
721
- status: translateStatus(entry.phase ?? entry.status ?? "running", null),
722
- phase: entry.phase,
1591
+ runtime_id: entryId,
723
1592
  session_id: entry.session_id
724
- });
1593
+ }, translateStatus(entry.phase ?? entry.status ?? "running", null)));
725
1594
  }
726
1595
  return results;
727
1596
  }
@@ -838,41 +1707,240 @@ async function followUpTool(args) {
838
1707
  }
839
1708
  //#endregion
840
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;
841
1714
  function findRunByLabel(sup, runId) {
842
1715
  const runs = sup.runs;
843
1716
  const byKey = runs.get(runId);
844
1717
  if (byKey) return byKey;
845
1718
  for (const info of runs.values()) if (info.label === runId) return info;
846
1719
  }
847
- async function eventsTool(args) {
848
- if (args.supervisorId) throw new Error("eventsTool with explicit supervisorId not supported — event log path must be tracked by the singleton supervisor");
849
- const runInfo = findRunByLabel(await Supervisor.get(), args.runId);
850
- if (!runInfo) throw new Error(`run not found: ${args.runId}`);
851
- const limit = Math.max(1, Math.min(1e3, Math.trunc(args.limit ?? 50)));
852
- 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 = [];
853
1771
  try {
854
- const stat = await fs.promises.stat(runInfo.eventLogPath);
855
- const tailBytes = Math.min(stat.size, 256 * 1024);
856
- 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;
857
1800
  try {
858
- const buffer = Buffer.alloc(tailBytes);
859
- const start = Math.max(0, stat.size - tailBytes);
860
- const { bytesRead } = await handle.read(buffer, 0, tailBytes, start);
861
- raw = buffer.subarray(0, bytesRead).toString("utf-8");
862
- } finally {
863
- 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;
864
1824
  }
865
- } 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}`);
866
1883
  return { events: [] };
1884
+ } finally {
1885
+ if (!resolved.isSingleton) resolved.client.close();
867
1886
  }
868
- const lines = raw.split("\n").filter((line) => line.trim());
869
- let events = [];
870
- for (const line of lines) try {
871
- events.push(JSON.parse(line));
872
- } catch {}
873
- if (args.types && args.types.length > 0) events = events.filter((e) => args.types.includes(e.type ?? e.event ?? ""));
874
- if (events.length > limit) events = events.slice(events.length - limit);
875
- 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
+ };
876
1944
  }
877
1945
  //#endregion
878
1946
  //#region src/tools/shutdown.ts
@@ -931,4 +1999,4 @@ async function shutdownTool(args) {
931
1999
  };
932
2000
  }
933
2001
  //#endregion
934
- 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 };