@stjbrown/agent-knowledge 0.1.0-beta.3 → 0.1.0-beta.5

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.
@@ -3,17 +3,21 @@ import { createRequire as __janetCreateRequire } from "node:module";
3
3
  const require = __janetCreateRequire(import.meta.url);
4
4
  import {
5
5
  availableModels,
6
- completeOnboarding,
7
- loadSettings,
8
- normalizeModelSelection,
9
- rememberModel
10
- } from "./chunk-PBRTNSWS.js";
6
+ normalizeModelSelection
7
+ } from "./chunk-PUTDZMUI.js";
11
8
  import {
12
9
  GREETING,
13
10
  bootJanet,
11
+ completeOnboarding,
12
+ formatObservabilityStatus,
14
13
  getAuthStorage,
15
- messageText
16
- } from "./chunk-H4H6S4AV.js";
14
+ loadSettings,
15
+ messageText,
16
+ rememberModel,
17
+ rememberObservability,
18
+ resolveObservabilityConfig,
19
+ safeObservabilityEndpoint
20
+ } from "./chunk-EBMACGJV.js";
17
21
 
18
22
  // src/tui/index.ts
19
23
  import {
@@ -25,7 +29,8 @@ import {
25
29
  SelectList,
26
30
  Spacer,
27
31
  TUI,
28
- Text
32
+ Text,
33
+ matchesKey
29
34
  } from "@earendil-works/pi-tui";
30
35
 
31
36
  // src/tui/activity.ts
@@ -61,6 +66,90 @@ function toolActivityLabel(toolName) {
61
66
  }
62
67
  return "Janet is working\u2026";
63
68
  }
69
+ function toolErrorLabel(result) {
70
+ const detail = String(result);
71
+ const readRequired = detail.match(
72
+ /File "([^"]+)" (?:has not been read|was modified since last read)/
73
+ );
74
+ if (readRequired) {
75
+ return `Update paused: Janet needs to re-read "${readRequired[1]}" first.`;
76
+ }
77
+ return `Tool error: ${detail.slice(0, 140)}`;
78
+ }
79
+
80
+ // src/tui/interrupt.ts
81
+ function createInterruptController(actions, options = {}) {
82
+ const doublePressMs = options.doublePressMs ?? 800;
83
+ const now = options.now ?? Date.now;
84
+ let lastCtrlC;
85
+ const cancelRun = () => {
86
+ if (!actions.isRunning()) return "ignored";
87
+ actions.abortRun();
88
+ actions.notify("cancelled");
89
+ return "cancelled";
90
+ };
91
+ return {
92
+ handleCtrlC() {
93
+ const pressedAt = now();
94
+ if (lastCtrlC !== void 0 && pressedAt - lastCtrlC < doublePressMs) {
95
+ actions.notify("exit");
96
+ actions.exit();
97
+ return "exit";
98
+ }
99
+ lastCtrlC = pressedAt;
100
+ const cancelled = cancelRun();
101
+ if (cancelled !== "ignored") return cancelled;
102
+ if (actions.hasInput()) {
103
+ actions.clearInput();
104
+ actions.notify("cleared");
105
+ return "cleared";
106
+ }
107
+ actions.notify("exit-hint");
108
+ return "exit-hint";
109
+ },
110
+ handleEscape() {
111
+ return cancelRun();
112
+ }
113
+ };
114
+ }
115
+
116
+ // src/tui/traces.ts
117
+ function duration(span) {
118
+ if (!span.endedAt) return "running";
119
+ const elapsed = Math.max(0, span.endedAt.getTime() - span.startedAt.getTime());
120
+ return elapsed >= 1e3 ? `${(elapsed / 1e3).toFixed(1)}s` : `${elapsed}ms`;
121
+ }
122
+ function traceStatus(span) {
123
+ if (span.error) return "error";
124
+ return span.endedAt ? "ok" : "running";
125
+ }
126
+ function formatTraceTree(spans) {
127
+ const byParent = /* @__PURE__ */ new Map();
128
+ const ids = new Set(spans.map((span) => span.spanId));
129
+ for (const span of spans) {
130
+ const parent = span.parentSpanId && ids.has(span.parentSpanId) ? span.parentSpanId : null;
131
+ const children = byParent.get(parent) ?? [];
132
+ children.push(span);
133
+ byParent.set(parent, children);
134
+ }
135
+ for (const children of byParent.values()) {
136
+ children.sort((a, b) => a.startedAt.getTime() - b.startedAt.getTime());
137
+ }
138
+ const lines = [];
139
+ const visited = /* @__PURE__ */ new Set();
140
+ const visit = (span, depth) => {
141
+ if (visited.has(span.spanId)) return;
142
+ visited.add(span.spanId);
143
+ const status = traceStatus(span);
144
+ const marker = status === "error" ? "\u2717" : status === "running" ? "\u2026" : "\u2713";
145
+ lines.push(
146
+ `${" ".repeat(depth)}${marker} ${span.name} \xB7 ${span.spanType} \xB7 ${duration(span)}`
147
+ );
148
+ for (const child of byParent.get(span.spanId) ?? []) visit(child, depth + 1);
149
+ };
150
+ for (const root of byParent.get(null) ?? []) visit(root, 0);
151
+ return lines;
152
+ }
64
153
 
65
154
  // src/tui/theme.ts
66
155
  import chalk from "chalk";
@@ -103,16 +192,6 @@ var markdownTheme = {
103
192
 
104
193
  // src/tui/index.ts
105
194
  var OAUTH_PROVIDERS = ["anthropic", "openai-codex"];
106
- var JanetEditor = class extends Editor {
107
- onCtrlC;
108
- handleInput(data) {
109
- if (data === "") {
110
- this.onCtrlC?.();
111
- return;
112
- }
113
- super.handleInput(data);
114
- }
115
- };
116
195
  var HELP_TEXT = `Commands:
117
196
  /models Pick a model from a list (arrow keys)
118
197
  /model [provider/id] Open the picker, or switch directly by id
@@ -120,9 +199,13 @@ var HELP_TEXT = `Commands:
120
199
  Log in; OpenAI mode is browser or device
121
200
  /logout <provider> Remove stored credentials for a provider
122
201
  /auth Show which providers are authenticated
202
+ /observability Configure opt-in tracing
203
+ /traces Browse recent local traces
204
+ /cancel Cancel the active run
123
205
  /help This help
124
206
  /quit Exit (double Ctrl+C also works)
125
207
 
208
+ While Janet is working, Esc or Ctrl+C cancels the active run.
126
209
  Anything else is a message to Janet.`;
127
210
  function resolveAnswer(q, text) {
128
211
  if (!q.options?.length) return text;
@@ -143,7 +226,10 @@ function resolveAnswer(q, text) {
143
226
  return pick(text);
144
227
  }
145
228
  async function runTui(opts) {
146
- const { controller, session, paths, herdrDetach } = await bootJanet({ ...opts, interactive: true });
229
+ const { controller, session, paths, herdrDetach, observability } = await bootJanet({
230
+ ...opts,
231
+ interactive: true
232
+ });
147
233
  const persistedModel = process.env["JANET_MODEL"] || loadSettings().defaultModelId;
148
234
  const presetModel = persistedModel ? normalizeModelSelection(persistedModel, availableModels()) : void 0;
149
235
  if (!session.model.hasSelection() && presetModel) {
@@ -153,7 +239,7 @@ async function runTui(opts) {
153
239
  const ui = new TUI(terminal);
154
240
  const chat = new Container();
155
241
  const status = new Text("", 1, 0);
156
- const editor = new JanetEditor(ui, editorTheme);
242
+ const editor = new Editor(ui, editorTheme);
157
243
  const loader = new Loader(ui, c.accent, c.dim, "Janet is thinking\u2026");
158
244
  ui.addChild(chat);
159
245
  ui.addChild(new Spacer(1));
@@ -166,11 +252,15 @@ async function runTui(opts) {
166
252
  let pendingInput = null;
167
253
  let activeSelect = null;
168
254
  let active = null;
255
+ let cancelRequested = false;
169
256
  const activeTools = /* @__PURE__ */ new Map();
170
257
  const updateStatus = () => {
171
258
  const model = session.model.hasSelection() ? session.model.get() : "no model \u2014 /model <id>";
172
- const state = pendingInput ? "enter the requested value" : pendingQuestion || activeSelect ? "answer Janet's question" : pendingApproval ? "awaiting approval" : running ? "working" : "idle";
173
- status.setText(c.dim(`${paths.projectPath} \xB7 `) + c.accent(model) + c.dim(` \xB7 ${state}`));
259
+ const tracing = observability.status.enabled ? c.dim(` \xB7 trace:${observability.status.capture}`) : "";
260
+ const state = pendingInput ? "enter the requested value" : pendingQuestion || activeSelect ? "answer Janet's question" : pendingApproval ? "awaiting approval" : cancelRequested ? "cancelling" : running ? "working \xB7 Esc/Ctrl+C cancels" : "idle";
261
+ status.setText(
262
+ c.dim(`${paths.projectPath} \xB7 `) + c.accent(model) + c.dim(` \xB7 ${state}`) + tracing
263
+ );
174
264
  ui.requestRender();
175
265
  };
176
266
  const appendToChat = (comp) => {
@@ -217,6 +307,7 @@ async function runTui(opts) {
217
307
  switch (event.type) {
218
308
  case "agent_start":
219
309
  running = true;
310
+ cancelRequested = false;
220
311
  active = null;
221
312
  activeTools.clear();
222
313
  loader.setMessage("Janet is thinking\u2026");
@@ -257,7 +348,7 @@ async function runTui(opts) {
257
348
  );
258
349
  if (event.isError) {
259
350
  closeSegment();
260
- addLine(c.warn(` Tool error: ${String(event.result).slice(0, 140)}`));
351
+ addLine(c.warn(` ${toolErrorLabel(event.result)}`));
261
352
  }
262
353
  break;
263
354
  case "tool_suspended": {
@@ -318,6 +409,7 @@ async function runTui(opts) {
318
409
  break;
319
410
  case "agent_end":
320
411
  running = false;
412
+ cancelRequested = false;
321
413
  activeTools.clear();
322
414
  loader.setMessage("Janet is thinking\u2026");
323
415
  if (event.reason !== "suspended") pendingQuestion = null;
@@ -327,20 +419,84 @@ async function runTui(opts) {
327
419
  }
328
420
  };
329
421
  const unsubscribe = session.subscribe(onEvent);
422
+ let removeInputListener = () => {
423
+ };
424
+ let sigintHandler;
330
425
  const shutdown = async (code) => {
426
+ removeInputListener();
427
+ if (sigintHandler) process.off("SIGINT", sigintHandler);
331
428
  unsubscribe();
332
429
  herdrDetach();
333
430
  ui.stop();
431
+ await observability.flush().catch(() => {
432
+ });
334
433
  await controller.destroy().catch(() => {
335
434
  });
336
435
  process.exit(code);
337
436
  };
437
+ const notifyInterrupt = (result) => {
438
+ switch (result) {
439
+ case "cancelled":
440
+ addLine(c.dim(" Cancelling the active run\u2026"));
441
+ break;
442
+ case "cleared":
443
+ break;
444
+ case "exit":
445
+ break;
446
+ case "exit-hint":
447
+ addLine(c.dim(" Press Ctrl+C again to quit."));
448
+ break;
449
+ }
450
+ updateStatus();
451
+ };
452
+ const abortActiveRun = () => {
453
+ if (cancelRequested) return;
454
+ cancelRequested = true;
455
+ pendingApproval = null;
456
+ pendingQuestion = null;
457
+ activeTools.clear();
458
+ if (activeSelect) {
459
+ chat.removeChild(activeSelect);
460
+ activeSelect = null;
461
+ }
462
+ ui.setFocus(editor);
463
+ loader.setMessage("Cancelling\u2026");
464
+ session.abort();
465
+ };
466
+ const interrupts = createInterruptController({
467
+ isRunning: () => running,
468
+ hasInput: () => editor.getText().length > 0,
469
+ abortRun: abortActiveRun,
470
+ clearInput: () => {
471
+ editor.setText("");
472
+ ui.requestRender();
473
+ },
474
+ exit: () => {
475
+ void shutdown(0);
476
+ },
477
+ notify: notifyInterrupt
478
+ });
479
+ removeInputListener = ui.addInputListener((data) => {
480
+ if (matchesKey(data, "ctrl+c")) {
481
+ interrupts.handleCtrlC();
482
+ return { consume: true };
483
+ }
484
+ if (matchesKey(data, "escape") && running) {
485
+ interrupts.handleEscape();
486
+ return { consume: true };
487
+ }
488
+ return void 0;
489
+ });
490
+ sigintHandler = () => {
491
+ interrupts.handleCtrlC();
492
+ };
493
+ process.on("SIGINT", sigintHandler);
338
494
  const promptInput = (message, placeholder) => {
339
495
  addLine(c.accentBold(` ${message}`));
340
496
  if (placeholder) addLine(c.dim(` (${placeholder})`));
341
- updateStatus();
342
497
  return new Promise((resolve) => {
343
498
  pendingInput = resolve;
499
+ updateStatus();
344
500
  });
345
501
  };
346
502
  const showModelPicker = (intro) => {
@@ -380,6 +536,254 @@ async function runTui(opts) {
380
536
  ui.setFocus(select);
381
537
  updateStatus();
382
538
  };
539
+ const savedObservabilitySummary = () => {
540
+ const saved = loadSettings().observability;
541
+ const resolved = resolveObservabilityConfig(saved, {});
542
+ return formatObservabilityStatus({
543
+ enabled: resolved.enabled,
544
+ capture: resolved.capture,
545
+ sampleRate: resolved.sampleRate,
546
+ destinations: [
547
+ ...resolved.local.enabled ? ["local"] : [],
548
+ ...resolved.remote ? [
549
+ resolved.remote.kind === "phoenix" ? `phoenix (${safeObservabilityEndpoint(resolved.remote.endpoint)})` : `otlp (${safeObservabilityEndpoint(resolved.remote.endpoint)})`
550
+ ] : []
551
+ ],
552
+ warnings: resolved.warnings
553
+ });
554
+ };
555
+ const persistObservability = (settings) => {
556
+ rememberObservability(settings);
557
+ addLine(c.accentBold(" \u2713 Observability settings saved."));
558
+ addLine(c.dim(` Saved: ${savedObservabilitySummary()}`));
559
+ addLine(c.dim(" Restart Janet to apply the new setting."));
560
+ updateStatus();
561
+ };
562
+ const closeActiveSelect = (select) => {
563
+ chat.removeChild(select);
564
+ if (activeSelect === select) activeSelect = null;
565
+ ui.setFocus(editor);
566
+ };
567
+ const confirmFullCapture = (base) => {
568
+ addLine(
569
+ c.warn(
570
+ " Full capture includes prompts, responses, and tool payloads. Do not use it with sensitive material."
571
+ )
572
+ );
573
+ const select = new SelectList(
574
+ [
575
+ {
576
+ value: "no",
577
+ label: "Keep metadata-only capture",
578
+ description: "Recommended. Content stays out of traces."
579
+ },
580
+ {
581
+ value: "yes",
582
+ label: "Enable full capture",
583
+ description: "I understand trace content may contain sensitive data."
584
+ }
585
+ ],
586
+ 2,
587
+ editorTheme.selectList
588
+ );
589
+ select.onSelect = (item) => {
590
+ closeActiveSelect(select);
591
+ persistObservability({
592
+ ...base,
593
+ capture: item.value === "yes" ? "full" : "metadata"
594
+ });
595
+ };
596
+ activeSelect = select;
597
+ chat.addChild(select);
598
+ ui.setFocus(select);
599
+ updateStatus();
600
+ };
601
+ const chooseCaptureMode = (base) => {
602
+ addLine(c.accentBold(" What may Janet include in traces?"));
603
+ const select = new SelectList(
604
+ [
605
+ {
606
+ value: "metadata",
607
+ label: "Metadata only",
608
+ description: "Timing, tool names, model, tokens, status, and errors."
609
+ },
610
+ {
611
+ value: "full",
612
+ label: "Full content",
613
+ description: "Also includes prompts, responses, and tool payloads."
614
+ }
615
+ ],
616
+ 2,
617
+ editorTheme.selectList
618
+ );
619
+ select.onSelect = (item) => {
620
+ closeActiveSelect(select);
621
+ const capture = item.value;
622
+ if (capture === "full") {
623
+ confirmFullCapture(base);
624
+ } else {
625
+ persistObservability({ ...base, capture });
626
+ }
627
+ };
628
+ activeSelect = select;
629
+ chat.addChild(select);
630
+ ui.setFocus(select);
631
+ updateStatus();
632
+ };
633
+ const showObservabilityPicker = () => {
634
+ if (running) {
635
+ addLine(c.dim(" Cancel the active run before changing observability settings."));
636
+ return;
637
+ }
638
+ addLine(c.accentBold(" Configure observability"));
639
+ addLine(c.dim(` Active now: ${formatObservabilityStatus(observability.status)}`));
640
+ addLine(c.dim(" Tracing is opt-in and changes apply after restart."));
641
+ const select = new SelectList(
642
+ [
643
+ {
644
+ value: "off",
645
+ label: "Off",
646
+ description: "No spans, trace database, or network export."
647
+ },
648
+ {
649
+ value: "local",
650
+ label: "Local trace history",
651
+ description: "Store traces in ~/.agent-knowledge/observability.db."
652
+ },
653
+ {
654
+ value: "phoenix",
655
+ label: "Phoenix",
656
+ description: "Send OTLP traces to http://localhost:6006."
657
+ },
658
+ {
659
+ value: "otlp",
660
+ label: "Custom OTLP",
661
+ description: "Send OTLP/HTTP protobuf traces to your endpoint."
662
+ }
663
+ ],
664
+ 4,
665
+ editorTheme.selectList
666
+ );
667
+ select.onSelect = (item) => {
668
+ closeActiveSelect(select);
669
+ if (item.value === "off") {
670
+ persistObservability({
671
+ capture: "off",
672
+ sampleRate: 1,
673
+ local: { enabled: false, retentionDays: 7 }
674
+ });
675
+ return;
676
+ }
677
+ if (item.value === "local") {
678
+ chooseCaptureMode({
679
+ sampleRate: 1,
680
+ local: { enabled: true, retentionDays: 7 }
681
+ });
682
+ return;
683
+ }
684
+ if (item.value === "phoenix") {
685
+ chooseCaptureMode({
686
+ sampleRate: 1,
687
+ local: { enabled: false, retentionDays: 7 },
688
+ remote: {
689
+ kind: "phoenix",
690
+ endpoint: "http://localhost:6006",
691
+ projectName: "janet"
692
+ }
693
+ });
694
+ return;
695
+ }
696
+ void promptInput(
697
+ "OTLP endpoint (for example, http://localhost:4318):"
698
+ ).then((endpoint) => {
699
+ try {
700
+ const parsed = new URL(endpoint);
701
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error();
702
+ if (parsed.username || parsed.password || parsed.search || parsed.hash) {
703
+ addLine(
704
+ c.error(
705
+ " Do not put credentials or query parameters in the saved endpoint. Use OTEL_EXPORTER_OTLP_HEADERS."
706
+ )
707
+ );
708
+ return;
709
+ }
710
+ } catch {
711
+ addLine(c.error(" Endpoint must be a valid HTTP or HTTPS URL."));
712
+ return;
713
+ }
714
+ chooseCaptureMode({
715
+ sampleRate: 1,
716
+ local: { enabled: false, retentionDays: 7 },
717
+ remote: {
718
+ kind: "otlp",
719
+ endpoint
720
+ }
721
+ });
722
+ });
723
+ };
724
+ activeSelect = select;
725
+ chat.addChild(select);
726
+ ui.setFocus(select);
727
+ updateStatus();
728
+ };
729
+ const showLocalTraces = async () => {
730
+ if (running) {
731
+ addLine(c.dim(" Cancel the active run before browsing traces."));
732
+ return;
733
+ }
734
+ if (!observability.config.local.enabled) {
735
+ addLine(c.dim(" Local trace history is not active. Use /observability to enable it."));
736
+ return;
737
+ }
738
+ await observability.flush().catch(() => {
739
+ });
740
+ const store = await observability.storage.getStore("observability");
741
+ if (!store) {
742
+ addLine(c.error(" Local trace storage is unavailable."));
743
+ return;
744
+ }
745
+ const recent = await store.listTraces({
746
+ pagination: { page: 0, perPage: 10 },
747
+ orderBy: { field: "startedAt", direction: "DESC" }
748
+ });
749
+ if (!recent.spans.length) {
750
+ addLine(c.dim(" No local traces yet."));
751
+ return;
752
+ }
753
+ addLine(c.accentBold(" Recent local traces"));
754
+ const select = new SelectList(
755
+ recent.spans.map((span) => {
756
+ const state = traceStatus(span);
757
+ const marker = state === "error" ? "\u2717" : state === "running" ? "\u2026" : "\u2713";
758
+ return {
759
+ value: span.traceId,
760
+ label: `${marker} ${span.name}`,
761
+ description: `${span.startedAt.toLocaleString()} \xB7 ${span.traceId}`
762
+ };
763
+ }),
764
+ Math.min(recent.spans.length, 10),
765
+ editorTheme.selectList
766
+ );
767
+ select.onSelect = (item) => {
768
+ closeActiveSelect(select);
769
+ void store.getTrace({ traceId: item.value }).then((trace) => {
770
+ if (!trace) {
771
+ addLine(c.error(` Trace not found: ${item.value}`));
772
+ return;
773
+ }
774
+ addLine(c.accentBold(` Trace ${trace.traceId}`));
775
+ for (const line of formatTraceTree(trace.spans)) {
776
+ addLine(c.dim(` ${line}`));
777
+ }
778
+ }).catch((error) => {
779
+ addLine(c.error(` Could not read trace: ${error.message}`));
780
+ });
781
+ };
782
+ activeSelect = select;
783
+ chat.addChild(select);
784
+ ui.setFocus(select);
785
+ updateStatus();
786
+ };
383
787
  const handleCommand = async (text) => {
384
788
  const [cmd, ...rest] = text.slice(1).split(/\s+/);
385
789
  switch (cmd) {
@@ -390,6 +794,32 @@ async function runTui(opts) {
390
794
  case "help":
391
795
  addLine(c.dim(HELP_TEXT));
392
796
  break;
797
+ case "cancel":
798
+ if (interrupts.handleEscape() === "ignored") {
799
+ addLine(c.dim(" No active run to cancel."));
800
+ }
801
+ break;
802
+ case "observability": {
803
+ const action = rest[0]?.trim().toLowerCase();
804
+ if (action === "status") {
805
+ addLine(c.dim(` Active: ${formatObservabilityStatus(observability.status)}`));
806
+ addLine(c.dim(` Saved: ${savedObservabilitySummary()}`));
807
+ } else if (action === "off") {
808
+ persistObservability({
809
+ capture: "off",
810
+ sampleRate: 1,
811
+ local: { enabled: false, retentionDays: 7 }
812
+ });
813
+ } else if (!action) {
814
+ showObservabilityPicker();
815
+ } else {
816
+ addLine(c.dim("Usage: /observability [status | off]"));
817
+ }
818
+ break;
819
+ }
820
+ case "traces":
821
+ await showLocalTraces();
822
+ break;
393
823
  case "login": {
394
824
  const providerId = (rest[0] || "anthropic").trim();
395
825
  if (!OAUTH_PROVIDERS.includes(providerId)) {
@@ -523,31 +953,21 @@ async function runTui(opts) {
523
953
  showModelPicker(" Pick a model first:");
524
954
  return;
525
955
  }
526
- void session.sendMessage({ content: text }).catch((err) => {
956
+ void session.sendMessage({
957
+ content: text,
958
+ tracingOptions: observability.tracingOptionsForTurn({
959
+ interactive: true,
960
+ operation: "chat",
961
+ resourceId: paths.resourceId,
962
+ threadId: session.thread.getId() ?? void 0
963
+ })
964
+ }).catch((err) => {
527
965
  running = false;
528
966
  setLoader(false);
529
967
  addLine(c.error(` \u2717 ${err.message}`));
530
968
  updateStatus();
531
969
  });
532
970
  };
533
- let lastCtrlC = 0;
534
- editor.onCtrlC = () => {
535
- const now = Date.now();
536
- if (now - lastCtrlC < 800) {
537
- void shutdown(0);
538
- return;
539
- }
540
- lastCtrlC = now;
541
- if (running) {
542
- void session.abort();
543
- addLine(c.dim(" (aborted \u2014 Ctrl+C again to quit)"));
544
- } else if (editor.getText()) {
545
- editor.setText("");
546
- ui.requestRender();
547
- } else {
548
- addLine(c.dim(" (Ctrl+C again to quit)"));
549
- }
550
- };
551
971
  addLine(c.accentBold(GREETING));
552
972
  addLine(
553
973
  c.dim(
@@ -555,6 +975,9 @@ async function runTui(opts) {
555
975
  Ask me anything in the bundle, or say what to ingest. /help for commands.`
556
976
  )
557
977
  );
978
+ for (const warning of observability.status.warnings) {
979
+ addLine(c.warn(`Observability: ${warning}`));
980
+ }
558
981
  updateStatus();
559
982
  ui.start();
560
983
  ui.setFocus(editor);
@@ -566,4 +989,4 @@ Ask me anything in the bundle, or say what to ingest. /help for commands.`
566
989
  export {
567
990
  runTui
568
991
  };
569
- //# sourceMappingURL=tui-GCLV5CZQ.js.map
992
+ //# sourceMappingURL=tui-6F3IBI3J.js.map