@dipertq/dsh-openviking-status 0.1.7 → 0.2.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.
package/lib/client.cjs CHANGED
@@ -4,9 +4,11 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  "use strict";
7
+ var __create = Object.create;
7
8
  var __defProp = Object.defineProperty;
8
9
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
9
10
  var __getOwnPropNames = Object.getOwnPropertyNames;
11
+ var __getProtoOf = Object.getPrototypeOf;
10
12
  var __hasOwnProp = Object.prototype.hasOwnProperty;
11
13
  var __export = (target, all) => {
12
14
  for (var name2 in all)
@@ -20,6 +22,14 @@ var __copyProps = (to, from, except, desc) => {
20
22
  }
21
23
  return to;
22
24
  };
25
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
26
+ // If the importer is in node compatibility mode or this is not an ESM
27
+ // file that has been converted to a CommonJS file using a Babel-
28
+ // compatible transform (i.e. "__esModule" has not been set), then set
29
+ // "default" to the CommonJS "module.exports" for node compatibility.
30
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
31
+ mod
32
+ ));
23
33
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
24
34
 
25
35
  // src/client/index.tsx
@@ -28,13 +38,17 @@ __export(client_exports, {
28
38
  COMMIT_THRESHOLD: () => COMMIT_THRESHOLD,
29
39
  DEFAULT_OPENVIKING_ENDPOINT: () => DEFAULT_OPENVIKING_ENDPOINT,
30
40
  OpenVikingClient: () => OpenVikingClient,
41
+ OpenVikingSettingsSection: () => OpenVikingSettingsSection,
31
42
  OpenVikingStatusChip: () => OpenVikingStatusChip,
32
43
  OpenVikingStatusPopover: () => OpenVikingStatusPopover,
44
+ THEME: () => THEME,
33
45
  apply: () => apply,
46
+ chatNodesToText: () => chatNodesToText,
34
47
  checkHealth: () => checkHealth,
35
48
  commitSession: () => commitSession,
36
49
  defaultOpenVikingClient: () => defaultOpenVikingClient,
37
50
  fetchSession: () => fetchSession,
51
+ formatDaemonVersion: () => formatDaemonVersion,
38
52
  formatEndpoint: () => formatEndpoint,
39
53
  formatMemoryLeafName: () => formatMemoryLeafName,
40
54
  formatPendingTokens: () => formatPendingTokens,
@@ -42,11 +56,9 @@ __export(client_exports, {
42
56
  formatStatusLabel: () => formatStatusLabel,
43
57
  formatTooltipTitle: () => formatTooltipTitle,
44
58
  getCategoryBadgeStyle: () => getCategoryBadgeStyle,
45
- getFallbackSessionMessages: () => getFallbackSessionMessages,
46
59
  getProgressBarColor: () => getProgressBarColor,
47
60
  getProgressBarPercent: () => getProgressBarPercent,
48
61
  getSession: () => getSession,
49
- getStatusGlow: () => getStatusGlow,
50
62
  getStatusIndicatorColor: () => getStatusIndicatorColor,
51
63
  handleEscapeKey: () => handleEscapeKey,
52
64
  inferCategory: () => inferCategory,
@@ -55,12 +67,14 @@ __export(client_exports, {
55
67
  parseRecalledMemories: () => parseRecalledMemories,
56
68
  resolveApiKey: () => resolveApiKey,
57
69
  resolveEndpoint: () => resolveEndpoint,
70
+ themeVar: () => themeVar,
58
71
  truncateSessionId: () => truncateSessionId
59
72
  });
60
73
  module.exports = __toCommonJS(client_exports);
61
74
 
62
75
  // src/client/OpenVikingStatusChip.tsx
63
- var import_react2 = require("react");
76
+ var import_react2 = __toESM(require("react"), 1);
77
+ var import_react_dom = __toESM(require("react-dom"), 1);
64
78
 
65
79
  // src/client/api.ts
66
80
  var DEFAULT_OPENVIKING_ENDPOINT = "http://127.0.0.1:1933";
@@ -114,6 +128,30 @@ var OpenVikingClient = class {
114
128
  this.endpoint = resolveEndpoint(endpoint);
115
129
  this.apiKey = resolveApiKey(apiKey);
116
130
  }
131
+ /**
132
+ * Обновление конфигурации клиента на лету (например, после сохранения настроек в UI).
133
+ */
134
+ updateConfig(config) {
135
+ if (config.endpoint && config.endpoint.trim()) {
136
+ this.endpoint = resolveEndpoint(config.endpoint);
137
+ }
138
+ if (config.apiKey !== void 0) {
139
+ this.apiKey = resolveApiKey(config.apiKey);
140
+ }
141
+ this.resolvedSessionIds.clear();
142
+ }
143
+ /**
144
+ * Очистить кэш разрешенных идентификаторов сессий.
145
+ */
146
+ clearResolvedSessions() {
147
+ this.resolvedSessionIds.clear();
148
+ }
149
+ /**
150
+ * Проверка, работает ли клиент через DSH Web Server proxy.
151
+ */
152
+ isProxy() {
153
+ return this.endpoint.startsWith("/") || this.endpoint.includes("/openviking-status/api");
154
+ }
117
155
  /**
118
156
  * Формирование заголовков запроса, включая опциональный заголовок авторизации
119
157
  */
@@ -144,6 +182,8 @@ var OpenVikingClient = class {
144
182
  } else if (raw.startsWith("dsh-")) {
145
183
  const suffix = raw.slice("dsh-".length);
146
184
  candidates.push(raw, `dsh-session-${suffix}`);
185
+ } else if (raw.startsWith("session-")) {
186
+ candidates.push(`dsh-${raw}`, raw);
147
187
  } else {
148
188
  candidates.push(`dsh-session-${raw}`, `dsh-${raw}`, raw);
149
189
  }
@@ -153,6 +193,32 @@ var OpenVikingClient = class {
153
193
  * Проверка доступности и состояния сервиса OpenViking
154
194
  */
155
195
  async checkHealth() {
196
+ if (this.isProxy()) {
197
+ try {
198
+ const res = await fetch(`${this.endpoint}/health`, {
199
+ method: "GET",
200
+ headers: this.getHeaders()
201
+ });
202
+ if (!res.ok) {
203
+ return {
204
+ ok: false,
205
+ error: `HTTP ${res.status}: ${res.statusText}`
206
+ };
207
+ }
208
+ const body = await res.json().catch(() => ({}));
209
+ const isOk = body.ok !== false && body.status !== "error" && (body.ok === true || body.status === "ok" || body.status === "healthy" || res.ok);
210
+ return {
211
+ ok: isOk,
212
+ version: typeof body.version === "string" ? body.version : void 0,
213
+ storage: typeof body.storage === "string" ? body.storage : void 0
214
+ };
215
+ } catch (err) {
216
+ return {
217
+ ok: false,
218
+ error: err instanceof Error ? err.message : String(err)
219
+ };
220
+ }
221
+ }
156
222
  try {
157
223
  const res = await fetch(`${this.endpoint}/health`, {
158
224
  method: "GET",
@@ -179,12 +245,58 @@ var OpenVikingClient = class {
179
245
  }
180
246
  }
181
247
  /**
182
- * Получение метаданных сессии по идентификатору с автоматическим разрешением префикса.
183
- * При сетевых сбоях или ошибках авторизации возвращает null, не выбрасывая исключений.
248
+ * Чтение метаданных сессии с явной причиной неудачи.
249
+ *
250
+ * Демон может работать с `auth_mode: api_key`: тогда `/health` остаётся
251
+ * открытым, а сессия отвечает 401. Схлопывать это в «нет данных» нельзя —
252
+ * иначе интерфейс покажет живой индикатор рядом с нулями и умолчит о том,
253
+ * что счётчики просто недоступны.
184
254
  */
185
- async fetchSession(sessionId) {
255
+ async readSession(sessionId) {
186
256
  if (!sessionId || !sessionId.trim()) {
187
- return null;
257
+ return { status: "missing" };
258
+ }
259
+ if (this.isProxy()) {
260
+ try {
261
+ const res = await fetch(
262
+ `${this.endpoint}/session?id=${encodeURIComponent(sessionId.trim())}`,
263
+ {
264
+ method: "GET",
265
+ headers: this.getHeaders()
266
+ }
267
+ );
268
+ if (res.status === 401 || res.status === 403) {
269
+ return { status: "unauthorized" };
270
+ }
271
+ if (!res.ok) {
272
+ return { status: "error", detail: `HTTP ${res.status}` };
273
+ }
274
+ const data = await res.json();
275
+ if (data.status === "unauthorized") return { status: "unauthorized" };
276
+ if (data.status === "missing") return { status: "missing" };
277
+ if (data.status === "unreachable")
278
+ return {
279
+ status: "unreachable",
280
+ detail: typeof data.detail === "string" ? data.detail : void 0
281
+ };
282
+ if (data.status === "error")
283
+ return {
284
+ status: "error",
285
+ detail: typeof data.detail === "string" ? data.detail : void 0
286
+ };
287
+ if (data.status === "ok" && data.session) {
288
+ return {
289
+ status: "ok",
290
+ session: data.session
291
+ };
292
+ }
293
+ return { status: "error", detail: "malformed response from proxy" };
294
+ } catch (err) {
295
+ return {
296
+ status: "unreachable",
297
+ detail: err instanceof Error ? err.message : String(err)
298
+ };
299
+ }
188
300
  }
189
301
  const candidates = this.getCandidateSessionIds(sessionId);
190
302
  for (const candidateId of candidates) {
@@ -199,30 +311,48 @@ var OpenVikingClient = class {
199
311
  if (res.status === 404) {
200
312
  continue;
201
313
  }
314
+ if (res.status === 401 || res.status === 403) {
315
+ return { status: "unauthorized" };
316
+ }
202
317
  if (!res.ok) {
203
- return null;
318
+ return { status: "error", detail: `HTTP ${res.status}` };
204
319
  }
205
320
  const data = await res.json();
206
321
  const raw = data?.result ?? data?.data ?? data;
207
322
  if (!raw || typeof raw !== "object") {
208
- return null;
323
+ return { status: "error", detail: "malformed response body" };
209
324
  }
210
325
  this.resolvedSessionIds.set(sessionId.trim(), candidateId);
211
326
  return {
212
- session_id: typeof raw.session_id === "string" ? raw.session_id : candidateId,
213
- peer_id: typeof raw.peer_id === "string" ? raw.peer_id : void 0,
214
- pending_tokens: typeof raw.pending_tokens === "number" ? raw.pending_tokens : 0,
215
- message_count: typeof raw.message_count === "number" ? raw.message_count : void 0,
216
- commit_count: typeof raw.commit_count === "number" ? raw.commit_count : void 0,
217
- last_commit_at: typeof raw.last_commit_at === "string" ? raw.last_commit_at : typeof raw.last_commit === "string" ? raw.last_commit : void 0,
218
- created_at: typeof raw.created_at === "string" ? raw.created_at : void 0,
219
- updated_at: typeof raw.updated_at === "string" ? raw.updated_at : void 0
327
+ status: "ok",
328
+ session: {
329
+ session_id: typeof raw.session_id === "string" ? raw.session_id : candidateId,
330
+ peer_id: typeof raw.peer_id === "string" ? raw.peer_id : void 0,
331
+ pending_tokens: typeof raw.pending_tokens === "number" ? raw.pending_tokens : 0,
332
+ message_count: typeof raw.message_count === "number" ? raw.message_count : void 0,
333
+ commit_count: typeof raw.commit_count === "number" ? raw.commit_count : void 0,
334
+ last_commit_at: typeof raw.last_commit_at === "string" ? raw.last_commit_at : typeof raw.last_commit === "string" ? raw.last_commit : void 0,
335
+ created_at: typeof raw.created_at === "string" ? raw.created_at : void 0,
336
+ updated_at: typeof raw.updated_at === "string" ? raw.updated_at : void 0
337
+ }
338
+ };
339
+ } catch (err) {
340
+ return {
341
+ status: "unreachable",
342
+ detail: err instanceof Error ? err.message : String(err)
220
343
  };
221
- } catch {
222
- return null;
223
344
  }
224
345
  }
225
- return null;
346
+ return { status: "missing" };
347
+ }
348
+ /**
349
+ * Получение метаданных сессии по идентификатору.
350
+ * Обёртка над {@link readSession} для вызывающих, которым причина неудачи
351
+ * не нужна: любая неудача сводится к null.
352
+ */
353
+ async fetchSession(sessionId) {
354
+ const result = await this.readSession(sessionId);
355
+ return result.status === "ok" ? result.session : null;
226
356
  }
227
357
  /**
228
358
  * Алиас для fetchSession
@@ -237,6 +367,31 @@ var OpenVikingClient = class {
237
367
  if (!sessionId || !sessionId.trim()) {
238
368
  return { ok: false, error: "Missing sessionId" };
239
369
  }
370
+ if (this.isProxy()) {
371
+ try {
372
+ const res = await fetch(`${this.endpoint}/session/commit`, {
373
+ method: "POST",
374
+ headers: this.getHeaders(),
375
+ body: JSON.stringify({
376
+ sessionId: sessionId.trim(),
377
+ ...options ?? { keep_recent_count: 10 }
378
+ })
379
+ });
380
+ if (!res.ok) {
381
+ return { ok: false, error: `HTTP ${res.status}` };
382
+ }
383
+ const data = await res.json().catch(() => ({}));
384
+ return {
385
+ ok: data.ok === true,
386
+ error: typeof data.error === "string" ? data.error : void 0
387
+ };
388
+ } catch (err) {
389
+ return {
390
+ ok: false,
391
+ error: err instanceof Error ? err.message : String(err)
392
+ };
393
+ }
394
+ }
240
395
  const candidates = this.getCandidateSessionIds(sessionId);
241
396
  const bodyPayload = JSON.stringify(options ?? { keep_recent_count: 10 });
242
397
  let lastError = "Session commit failed";
@@ -288,6 +443,45 @@ function commitSession(sessionId, options, endpoint, apiKey) {
288
443
  return client.commitSession(sessionId, options);
289
444
  }
290
445
 
446
+ // src/client/theme.ts
447
+ var THEME = {
448
+ /** Фон всплывающей панели — тот же, что у меню и диалогов DSH. */
449
+ panelSurface: "--dsw-specific-menu",
450
+ /** Тень панели. */
451
+ panelElevation: "--dsw-elevation-prominent",
452
+ /** Контур панели; задаётся через переменную тени, а не через border. */
453
+ panelStroke: "--dsw-alias-border-l1",
454
+ /** Заголовки и акцентный текст. */
455
+ labelPrimary: "--dsw-alias-label-primary",
456
+ /** Основной текст панели. */
457
+ labelSecondary: "--dsw-alias-label-secondary",
458
+ /** Приглушённый текст: подписи, значения, текст чипа в покое. */
459
+ labelTertiary: "--dsw-alias-label-tertiary",
460
+ /** Разделительная линия внутри панели. */
461
+ hairline: "--dsw-alias-border-l2",
462
+ /** Подсветка интерактивного элемента под курсором. */
463
+ hoverBackground: "--dsw-alias-interactive-bg-hover",
464
+ /** Подсветка нажатого элемента. */
465
+ activeBackground: "--dsw-alias-interactive-bg-active",
466
+ /** Демон доступен. */
467
+ stateSuccess: "--dsw-alias-state-success-primary",
468
+ /** Демон недоступен или сессия нечитаема. */
469
+ stateError: "--dsw-alias-state-error-primary",
470
+ /** Идёт коммит, либо накоплено близко к порогу. */
471
+ stateWarning: "--dsw-alias-state-warn-primary",
472
+ /** Утопленная поверхность: дорожка прогресс-бара. */
473
+ insetSurface: "--dsw-alias-bg-layer-2",
474
+ /** Заливка основной кнопки действий. */
475
+ buttonPrimaryFill: "--dsw-alias-button-primary-fill",
476
+ /** Цвет текста на основной кнопке действий. */
477
+ buttonPrimaryText: "--dsw-alias-label-primary-inverted",
478
+ /** Моноширинный шрифт для идентификаторов и путей. */
479
+ fontMono: "--dsw-font-markdown-code-font-family"
480
+ };
481
+ function themeVar(role) {
482
+ return `var(${THEME[role]})`;
483
+ }
484
+
291
485
  // src/client/recallParser.ts
292
486
  var KNOWN_CATEGORIES = /* @__PURE__ */ new Set([
293
487
  "preferences",
@@ -596,6 +790,15 @@ function parseRecalledMemories(input) {
596
790
  // src/client/OpenVikingStatusPopover.tsx
597
791
  var import_react = require("react");
598
792
  var import_jsx_runtime = require("react/jsx-runtime");
793
+ function dshIcon(name2) {
794
+ try {
795
+ const primitives = typeof require === "function" ? require("@deepseek-ai/dsh-client-ui-primitives") : null;
796
+ const icon = primitives?.[name2];
797
+ return typeof icon === "function" ? icon : null;
798
+ } catch {
799
+ return null;
800
+ }
801
+ }
599
802
  function getProgressBarPercent(pendingTokens, threshold = COMMIT_THRESHOLD) {
600
803
  if (!threshold || threshold <= 0) return 0;
601
804
  const ratio = (pendingTokens || 0) / threshold;
@@ -603,9 +806,14 @@ function getProgressBarPercent(pendingTokens, threshold = COMMIT_THRESHOLD) {
603
806
  }
604
807
  function getProgressBarColor(percent) {
605
808
  if (percent >= 80) {
606
- return "var(--dsw-status-warning, #fbbf24)";
809
+ return themeVar("stateWarning");
607
810
  }
608
- return "var(--dsw-status-success, #34d399)";
811
+ return themeVar("stateSuccess");
812
+ }
813
+ function formatDaemonVersion(version) {
814
+ const value = version?.trim();
815
+ if (!value) return void 0;
816
+ return /^v/i.test(value) ? value : `v${value}`;
609
817
  }
610
818
  function formatRelativeTime(isoOrTimestamp, now = Date.now()) {
611
819
  if (!isoOrTimestamp) return void 0;
@@ -647,39 +855,8 @@ function truncateSessionId(id, maxLen = 16) {
647
855
  if (id.length <= maxLen) return id;
648
856
  return `${id.slice(0, maxLen)}...`;
649
857
  }
650
- function getCategoryBadgeStyle(category) {
651
- switch (category?.toLowerCase()) {
652
- case "preferences":
653
- return {
654
- backgroundColor: "rgba(168, 85, 247, 0.15)",
655
- color: "var(--dsw-status-purple, #c084fc)"
656
- };
657
- case "entities":
658
- return {
659
- backgroundColor: "rgba(59, 130, 246, 0.15)",
660
- color: "var(--dsw-status-info, #60a5fa)"
661
- };
662
- case "skills":
663
- return {
664
- backgroundColor: "rgba(236, 72, 153, 0.15)",
665
- color: "var(--dsw-status-pink, #f472b6)"
666
- };
667
- case "events":
668
- return {
669
- backgroundColor: "rgba(245, 158, 11, 0.15)",
670
- color: "var(--dsw-status-warning, #fbbf24)"
671
- };
672
- case "resources":
673
- return {
674
- backgroundColor: "rgba(20, 184, 166, 0.15)",
675
- color: "var(--dsw-status-teal, #2dd4bf)"
676
- };
677
- default:
678
- return {
679
- backgroundColor: "rgba(148, 163, 184, 0.15)",
680
- color: "var(--dsw-text-muted, #94a3b8)"
681
- };
682
- }
858
+ function getCategoryBadgeStyle(_category) {
859
+ return { color: themeVar("labelTertiary") };
683
860
  }
684
861
  function handleEscapeKey(event, onClose) {
685
862
  if (event.key === "Escape") {
@@ -692,6 +869,7 @@ function OpenVikingStatusPopover({
692
869
  sessionId,
693
870
  health,
694
871
  sessionData,
872
+ sessionRead,
695
873
  recalledResult,
696
874
  endpoint,
697
875
  isCommitting = false,
@@ -711,13 +889,17 @@ function OpenVikingStatusPopover({
711
889
  };
712
890
  }, []);
713
891
  const isOnline = health?.ok === true;
714
- const statusColor = isOnline ? "var(--dsw-status-success, #34d399)" : "var(--dsw-status-error, #f87171)";
892
+ const sessionUnreadable = isOnline && sessionRead != null && sessionRead.status !== "ok";
893
+ const unauthorized = sessionRead?.status === "unauthorized";
894
+ const statusColor = isOnline ? sessionUnreadable ? themeVar("stateWarning") : themeVar("stateSuccess") : themeVar("stateError");
715
895
  const displaySessionId = sessionData?.session_id || sessionId || "";
716
896
  const pendingTokens = sessionData?.pending_tokens ?? 0;
717
897
  const progressPercent = getProgressBarPercent(pendingTokens);
718
898
  const progressBarColor = getProgressBarColor(progressPercent);
719
899
  const memoryItems = recalledResult?.items || [];
720
900
  const recalledCount = recalledResult?.recalledCount ?? memoryItems.length;
901
+ const CopyIcon = dshIcon("IconCopyOutline16");
902
+ const CheckIcon = dshIcon("IconCheckOutline16");
721
903
  const handleCopySessionId = (0, import_react.useCallback)(() => {
722
904
  if (!displaySessionId) return;
723
905
  if (typeof navigator !== "undefined" && navigator.clipboard) {
@@ -733,7 +915,15 @@ function OpenVikingStatusPopover({
733
915
  }, 1500);
734
916
  }
735
917
  }, [displaySessionId]);
736
- const isCommitDisabled = isCommitting || !isOnline || pendingTokens === 0;
918
+ const isCommitDisabled = isCommitting || !isOnline || sessionUnreadable || pendingTokens === 0;
919
+ const pendingLabel = sessionUnreadable ? "unavailable" : `${pendingTokens.toLocaleString()} / ${COMMIT_THRESHOLD.toLocaleString()}`;
920
+ const rowStyle = {
921
+ display: "flex",
922
+ justifyContent: "space-between",
923
+ alignItems: "center"
924
+ };
925
+ const mutedStyle = { color: themeVar("labelTertiary") };
926
+ const monoStyle = { fontFamily: themeVar("fontMono") };
737
927
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
738
928
  "div",
739
929
  {
@@ -744,17 +934,23 @@ function OpenVikingStatusPopover({
744
934
  position: "absolute",
745
935
  bottom: "calc(100% + 8px)",
746
936
  right: 0,
747
- width: "320px",
748
- backgroundColor: "var(--dsw-surface-overlay, #1e293b)",
749
- border: "1px solid var(--dsw-border-default, #334155)",
750
- borderRadius: "8px",
751
- padding: "12px",
752
- boxShadow: "0 10px 25px -5px rgba(0, 0, 0, 0.5)",
753
- zIndex: 1e3,
754
- fontSize: "12px",
755
- color: "var(--dsw-text-default, #f1f5f9)",
756
- fontFamily: "var(--dsw-font-sans, system-ui, sans-serif)",
937
+ zIndex: 1100,
757
938
  boxSizing: "border-box",
939
+ width: "max-content",
940
+ minWidth: "min(300px, 100vw - 24px)",
941
+ maxWidth: "min(440px, 100vw - 24px)",
942
+ background: themeVar("panelSurface"),
943
+ boxShadow: themeVar("panelElevation"),
944
+ color: themeVar("labelSecondary"),
945
+ cursor: "default",
946
+ border: 0,
947
+ borderRadius: "12px",
948
+ padding: "16px",
949
+ fontSize: "12px",
950
+ lineHeight: "18px",
951
+ ...{
952
+ "--dsw-elevation-stroke-color": themeVar("panelStroke")
953
+ },
758
954
  ...style
759
955
  },
760
956
  children: [
@@ -765,58 +961,41 @@ function OpenVikingStatusPopover({
765
961
  style: {
766
962
  display: "flex",
767
963
  justifyContent: "space-between",
768
- alignItems: "flex-start",
769
- marginBottom: "10px",
770
- paddingBottom: "8px",
771
- borderBottom: "1px solid var(--dsw-border-subtle, rgba(255, 255, 255, 0.08))"
964
+ gap: "16px",
965
+ marginBottom: "8px",
966
+ color: themeVar("labelPrimary"),
967
+ fontWeight: 500
772
968
  },
773
969
  children: [
774
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
775
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
776
- "div",
777
- {
778
- style: {
779
- fontWeight: 600,
780
- fontSize: "13px",
781
- color: "var(--dsw-text-default, #f1f5f9)"
782
- },
783
- children: "OpenViking Memory"
784
- }
785
- ),
970
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { minWidth: 0 }, children: [
971
+ "OpenViking Memory",
972
+ " ",
786
973
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
787
- "div",
974
+ "span",
788
975
  {
789
976
  "data-testid": "endpoint-label",
790
- style: {
791
- fontSize: "10px",
792
- color: "var(--dsw-text-muted, #94a3b8)",
793
- fontFamily: "var(--dsw-font-mono, monospace)",
794
- marginTop: "1px"
795
- },
977
+ style: { ...mutedStyle, ...monoStyle, fontWeight: 400 },
796
978
  children: formatEndpoint(endpoint)
797
979
  }
798
980
  )
799
981
  ] }),
800
982
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
801
- "div",
983
+ "span",
802
984
  {
803
985
  "data-testid": "status-badge",
804
986
  style: {
805
987
  display: "inline-flex",
806
988
  alignItems: "center",
807
- gap: "5px",
808
- fontSize: "10px",
809
- padding: "2px 7px",
810
- borderRadius: "4px",
811
- backgroundColor: isOnline ? "rgba(52, 211, 153, 0.15)" : "rgba(248, 113, 113, 0.15)",
989
+ gap: "6px",
812
990
  color: statusColor,
813
- fontWeight: 600
991
+ flexShrink: 0
814
992
  },
815
993
  children: [
816
994
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
817
995
  "span",
818
996
  {
819
997
  "data-testid": "status-badge-dot",
998
+ "aria-hidden": "true",
820
999
  style: {
821
1000
  width: "6px",
822
1001
  height: "6px",
@@ -826,13 +1005,27 @@ function OpenVikingStatusPopover({
826
1005
  }
827
1006
  }
828
1007
  ),
829
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: isOnline ? health?.version ? `ONLINE v${health.version}` : "ONLINE" : "OFFLINE" })
1008
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: isOnline ? [
1009
+ "ONLINE",
1010
+ formatDaemonVersion(health?.version),
1011
+ sessionUnreadable ? "\xB7 no session access" : null
1012
+ ].filter(Boolean).join(" ") : "OFFLINE" })
830
1013
  ]
831
1014
  }
832
1015
  )
833
1016
  ]
834
1017
  }
835
1018
  ),
1019
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1020
+ "div",
1021
+ {
1022
+ style: {
1023
+ borderTop: `.5px solid ${themeVar("hairline")}`,
1024
+ marginBottom: "10px"
1025
+ },
1026
+ "aria-hidden": "true"
1027
+ }
1028
+ ),
836
1029
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
837
1030
  "div",
838
1031
  {
@@ -843,142 +1036,88 @@ function OpenVikingStatusPopover({
843
1036
  marginBottom: "10px"
844
1037
  },
845
1038
  children: [
846
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
847
- "div",
848
- {
849
- style: {
850
- display: "flex",
851
- justifyContent: "space-between",
852
- alignItems: "center"
853
- },
854
- children: [
855
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--dsw-text-muted, #94a3b8)" }, children: "Session ID:" }),
856
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
857
- "div",
858
- {
859
- style: {
860
- display: "flex",
861
- alignItems: "center",
862
- gap: "4px"
863
- },
864
- children: [
865
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
866
- "span",
867
- {
868
- "data-testid": "session-id-value",
869
- role: "button",
870
- tabIndex: 0,
871
- onKeyDown: (e) => {
872
- if (e.key === "Enter" || e.key === " ") {
873
- e.preventDefault();
874
- handleCopySessionId();
875
- }
876
- },
877
- "aria-label": "Click to copy Session ID",
878
- style: {
879
- fontFamily: "var(--dsw-font-mono, monospace)",
880
- cursor: "pointer"
881
- },
882
- title: displaySessionId,
883
- onClick: handleCopySessionId,
884
- children: truncateSessionId(displaySessionId)
885
- }
886
- ),
887
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
888
- "button",
889
- {
890
- type: "button",
891
- "data-testid": "copy-session-btn",
892
- onClick: handleCopySessionId,
893
- title: copied ? "Copied!" : "Copy Session ID",
894
- "aria-label": copied ? "Copied!" : "Copy Session ID",
895
- style: {
896
- background: "none",
897
- border: "none",
898
- cursor: "pointer",
899
- padding: "2px 4px",
900
- fontSize: "10px",
901
- color: copied ? "var(--dsw-status-success, #34d399)" : "var(--dsw-text-muted, #94a3b8)",
902
- borderRadius: "3px"
903
- },
904
- children: copied ? "\u2713" : "\u{1F4CB}"
905
- }
906
- )
907
- ]
908
- }
909
- )
910
- ]
911
- }
912
- ),
913
- sessionData?.peer_id && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
914
- "div",
915
- {
916
- style: {
917
- display: "flex",
918
- justifyContent: "space-between",
919
- alignItems: "center"
920
- },
921
- children: [
922
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--dsw-text-muted, #94a3b8)" }, children: "Peer ID:" }),
923
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
924
- "span",
925
- {
926
- "data-testid": "peer-id-value",
927
- style: {
928
- maxWidth: "180px",
929
- overflow: "hidden",
930
- textOverflow: "ellipsis",
931
- whiteSpace: "nowrap",
932
- fontFamily: "var(--dsw-font-mono, monospace)"
933
- },
934
- title: sessionData.peer_id,
935
- children: sessionData.peer_id
936
- }
937
- )
938
- ]
939
- }
940
- ),
941
- sessionData?.last_commit_at && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
942
- "div",
943
- {
944
- style: {
945
- display: "flex",
946
- justifyContent: "space-between",
947
- alignItems: "center"
948
- },
949
- children: [
950
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--dsw-text-muted, #94a3b8)" }, children: "Last Commit:" }),
951
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
952
- "span",
953
- {
954
- "data-testid": "last-commit-value",
955
- title: sessionData.last_commit_at,
956
- style: { color: "var(--dsw-text-default, #e2e8f0)" },
957
- children: formatRelativeTime(sessionData.last_commit_at) || sessionData.last_commit_at
958
- }
959
- )
960
- ]
961
- }
962
- )
1039
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: rowStyle, children: [
1040
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: mutedStyle, children: "Session ID:" }),
1041
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { display: "flex", alignItems: "center", gap: "4px" }, children: [
1042
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1043
+ "span",
1044
+ {
1045
+ "data-testid": "session-id-value",
1046
+ role: "button",
1047
+ tabIndex: 0,
1048
+ onKeyDown: (e) => {
1049
+ if (e.key === "Enter" || e.key === " ") {
1050
+ e.preventDefault();
1051
+ handleCopySessionId();
1052
+ }
1053
+ },
1054
+ "aria-label": "Click to copy Session ID",
1055
+ style: { ...monoStyle, cursor: "pointer" },
1056
+ title: displaySessionId,
1057
+ onClick: handleCopySessionId,
1058
+ children: truncateSessionId(displaySessionId)
1059
+ }
1060
+ ),
1061
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1062
+ "button",
1063
+ {
1064
+ type: "button",
1065
+ "data-testid": "copy-session-btn",
1066
+ onClick: handleCopySessionId,
1067
+ title: copied ? "Copied!" : "Copy Session ID",
1068
+ "aria-label": copied ? "Copied!" : "Copy Session ID",
1069
+ style: {
1070
+ background: "none",
1071
+ border: "none",
1072
+ cursor: "pointer",
1073
+ padding: "2px",
1074
+ display: "inline-flex",
1075
+ alignItems: "center",
1076
+ color: copied ? themeVar("stateSuccess") : themeVar("labelTertiary")
1077
+ },
1078
+ children: copied ? CheckIcon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CheckIcon, { size: 14 }) : CopyIcon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CopyIcon, { size: 14 })
1079
+ }
1080
+ )
1081
+ ] })
1082
+ ] }),
1083
+ sessionData?.peer_id && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: rowStyle, children: [
1084
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: mutedStyle, children: "Peer ID:" }),
1085
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1086
+ "span",
1087
+ {
1088
+ "data-testid": "peer-id-value",
1089
+ style: {
1090
+ ...monoStyle,
1091
+ maxWidth: "180px",
1092
+ overflow: "hidden",
1093
+ textOverflow: "ellipsis",
1094
+ whiteSpace: "nowrap"
1095
+ },
1096
+ title: sessionData.peer_id,
1097
+ children: sessionData.peer_id
1098
+ }
1099
+ )
1100
+ ] }),
1101
+ sessionData?.last_commit_at && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: rowStyle, children: [
1102
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: mutedStyle, children: "Last Commit:" }),
1103
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1104
+ "span",
1105
+ {
1106
+ "data-testid": "last-commit-value",
1107
+ title: sessionData.last_commit_at,
1108
+ children: formatRelativeTime(sessionData.last_commit_at) || sessionData.last_commit_at
1109
+ }
1110
+ )
1111
+ ] })
963
1112
  ]
964
1113
  }
965
1114
  ),
966
1115
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginBottom: "12px" }, children: [
967
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
968
- "div",
969
- {
970
- style: {
971
- display: "flex",
972
- justifyContent: "space-between",
973
- marginBottom: "4px"
974
- },
975
- children: [
976
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--dsw-text-muted, #94a3b8)" }, children: "Pending Tokens:" }),
977
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "data-testid": "pending-tokens-label", style: { fontWeight: 500 }, children: `${pendingTokens.toLocaleString()} / ${COMMIT_THRESHOLD.toLocaleString()}` })
978
- ]
979
- }
980
- ),
981
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1116
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { ...rowStyle, marginBottom: "4px" }, children: [
1117
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: mutedStyle, children: "Pending Tokens:" }),
1118
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "data-testid": "pending-tokens-label", children: pendingLabel })
1119
+ ] }),
1120
+ !sessionUnreadable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
982
1121
  "div",
983
1122
  {
984
1123
  "data-testid": "progress-bar-track",
@@ -986,7 +1125,7 @@ function OpenVikingStatusPopover({
986
1125
  width: "100%",
987
1126
  height: "6px",
988
1127
  borderRadius: "3px",
989
- backgroundColor: "rgba(255, 255, 255, 0.1)",
1128
+ backgroundColor: themeVar("insetSurface"),
990
1129
  overflow: "hidden"
991
1130
  },
992
1131
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -1004,30 +1143,34 @@ function OpenVikingStatusPopover({
1004
1143
  }
1005
1144
  )
1006
1145
  ] }),
1146
+ sessionUnreadable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1147
+ "div",
1148
+ {
1149
+ "data-testid": "session-unreadable-notice",
1150
+ style: {
1151
+ marginBottom: "12px",
1152
+ color: themeVar("labelTertiary")
1153
+ },
1154
+ children: unauthorized ? "The daemon requires an API key. Set openviking_api_key in localStorage to read session counters." : "Session counters are unavailable right now."
1155
+ }
1156
+ ),
1007
1157
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginBottom: "12px" }, children: [
1008
1158
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1009
1159
  "div",
1010
1160
  {
1011
1161
  style: {
1012
- display: "flex",
1013
- justifyContent: "space-between",
1014
- alignItems: "center",
1015
- marginBottom: "4px"
1162
+ marginBottom: "4px",
1163
+ color: themeVar("labelPrimary"),
1164
+ fontWeight: 500
1016
1165
  },
1017
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontWeight: 600, fontSize: "11px" }, children: `Recalled Memories (${recalledCount})` })
1166
+ children: `Recalled Memories (${recalledCount})`
1018
1167
  }
1019
1168
  ),
1020
1169
  memoryItems.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1021
1170
  "div",
1022
1171
  {
1023
1172
  "data-testid": "empty-memories-message",
1024
- style: {
1025
- padding: "8px 0",
1026
- color: "var(--dsw-text-muted, #94a3b8)",
1027
- fontStyle: "italic",
1028
- fontSize: "11px",
1029
- textAlign: "center"
1030
- },
1173
+ style: { ...mutedStyle, padding: "4px 0" },
1031
1174
  children: "No memories recalled in this session"
1032
1175
  }
1033
1176
  ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -1051,11 +1194,7 @@ function OpenVikingStatusPopover({
1051
1194
  display: "flex",
1052
1195
  alignItems: "center",
1053
1196
  gap: "6px",
1054
- padding: "4px 6px",
1055
- borderRadius: "4px",
1056
- backgroundColor: "rgba(255, 255, 255, 0.04)",
1057
- border: "1px solid var(--dsw-border-subtle, rgba(255, 255, 255, 0.05))",
1058
- fontSize: "11px"
1197
+ minWidth: 0
1059
1198
  },
1060
1199
  children: [
1061
1200
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -1063,10 +1202,6 @@ function OpenVikingStatusPopover({
1063
1202
  {
1064
1203
  "data-testid": "memory-category-badge",
1065
1204
  style: {
1066
- fontSize: "9px",
1067
- fontWeight: 600,
1068
- padding: "1px 4px",
1069
- borderRadius: "3px",
1070
1205
  textTransform: "uppercase",
1071
1206
  flexShrink: 0,
1072
1207
  ...getCategoryBadgeStyle(item.category)
@@ -1078,21 +1213,7 @@ function OpenVikingStatusPopover({
1078
1213
  "span",
1079
1214
  {
1080
1215
  "data-testid": "memory-source-badge",
1081
- style: {
1082
- fontSize: "9px",
1083
- fontWeight: 600,
1084
- padding: "1px 4px",
1085
- borderRadius: "3px",
1086
- textTransform: "uppercase",
1087
- flexShrink: 0,
1088
- ...item.source === "profile" ? {
1089
- backgroundColor: "rgba(99, 102, 241, 0.15)",
1090
- color: "#818cf8"
1091
- } : {
1092
- backgroundColor: "rgba(16, 185, 129, 0.15)",
1093
- color: "var(--dsw-status-success, #34d399)"
1094
- }
1095
- },
1216
+ style: { ...mutedStyle, flexShrink: 0 },
1096
1217
  children: item.source
1097
1218
  }
1098
1219
  ),
@@ -1101,12 +1222,10 @@ function OpenVikingStatusPopover({
1101
1222
  {
1102
1223
  "data-testid": "memory-leaf-name",
1103
1224
  style: {
1104
- flex: 1,
1105
1225
  overflow: "hidden",
1106
1226
  textOverflow: "ellipsis",
1107
1227
  whiteSpace: "nowrap",
1108
- fontFamily: "var(--dsw-font-mono, monospace)",
1109
- color: "var(--dsw-text-default, #f1f5f9)"
1228
+ minWidth: 0
1110
1229
  },
1111
1230
  children: formatMemoryLeafName(item.uri)
1112
1231
  }
@@ -1122,16 +1241,7 @@ function OpenVikingStatusPopover({
1122
1241
  "div",
1123
1242
  {
1124
1243
  "data-testid": "commit-error-message",
1125
- style: {
1126
- marginBottom: "8px",
1127
- padding: "6px 8px",
1128
- borderRadius: "4px",
1129
- backgroundColor: "rgba(248, 113, 113, 0.1)",
1130
- border: "1px solid var(--dsw-status-error, #f87171)",
1131
- color: "var(--dsw-status-error, #f87171)",
1132
- fontSize: "11px",
1133
- wordBreak: "break-word"
1134
- },
1244
+ style: { marginBottom: "8px", color: themeVar("stateError") },
1135
1245
  children: commitError
1136
1246
  }
1137
1247
  ),
@@ -1140,19 +1250,17 @@ function OpenVikingStatusPopover({
1140
1250
  {
1141
1251
  type: "button",
1142
1252
  "data-testid": "commit-now-btn",
1143
- onClick: () => onCommitNow?.(),
1253
+ onClick: () => void onCommitNow?.(),
1144
1254
  disabled: isCommitDisabled,
1145
1255
  style: {
1146
1256
  width: "100%",
1147
- padding: "7px 0",
1148
- borderRadius: "6px",
1149
- border: "1px solid var(--dsw-border-default, #475569)",
1150
- backgroundColor: isCommitting ? "var(--dsw-surface-active, #334155)" : isCommitDisabled ? "rgba(255, 255, 255, 0.03)" : "var(--dsw-surface-base, #1e293b)",
1151
- color: isCommitDisabled ? "var(--dsw-text-muted, #64748b)" : "var(--dsw-text-default, #f8fafc)",
1152
- cursor: isCommitDisabled ? "not-allowed" : "pointer",
1153
- fontWeight: 600,
1154
- fontSize: "12px",
1155
- transition: "all 0.15s ease",
1257
+ padding: "6px 10px",
1258
+ borderRadius: "8px",
1259
+ border: `.5px solid ${themeVar("hairline")}`,
1260
+ background: isCommitDisabled ? "transparent" : themeVar("hoverBackground"),
1261
+ color: isCommitDisabled ? themeVar("labelTertiary") : themeVar("labelPrimary"),
1262
+ cursor: isCommitDisabled ? "default" : "pointer",
1263
+ font: "inherit",
1156
1264
  display: "flex",
1157
1265
  alignItems: "center",
1158
1266
  justifyContent: "center",
@@ -1162,12 +1270,13 @@ function OpenVikingStatusPopover({
1162
1270
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1163
1271
  "span",
1164
1272
  {
1273
+ "aria-hidden": "true",
1165
1274
  style: {
1166
1275
  display: "inline-block",
1167
1276
  width: "10px",
1168
1277
  height: "10px",
1169
1278
  borderRadius: "50%",
1170
- border: "2px solid var(--dsw-status-warning, #fbbf24)",
1279
+ border: `2px solid ${themeVar("stateWarning")}`,
1171
1280
  borderTopColor: "transparent",
1172
1281
  animation: "ov-spin 1s linear infinite"
1173
1282
  }
@@ -1199,11 +1308,15 @@ function formatTooltipTitle({
1199
1308
  isOnline,
1200
1309
  isCommitting = false,
1201
1310
  recalledCount,
1202
- pendingTokens
1311
+ pendingTokens,
1312
+ sessionUnreadable = false
1203
1313
  }) {
1204
1314
  if (!isOnline) {
1205
1315
  return "OpenViking: Offline";
1206
1316
  }
1317
+ if (sessionUnreadable) {
1318
+ return "OpenViking: session unreadable \u2014 the daemon requires an API key";
1319
+ }
1207
1320
  const countLabel = `${recalledCount} recalled`;
1208
1321
  const tokenLabel = `${(pendingTokens || 0).toLocaleString()} pending tokens`;
1209
1322
  if (isCommitting) {
@@ -1211,36 +1324,69 @@ function formatTooltipTitle({
1211
1324
  }
1212
1325
  return `OpenViking: Online (${countLabel}, ${tokenLabel})`;
1213
1326
  }
1214
- function getStatusIndicatorColor(isOnline, isCommitting = false) {
1327
+ function getStatusIndicatorColor(isOnline, isCommitting = false, sessionUnreadable = false) {
1215
1328
  if (!isOnline) {
1216
- return "var(--dsw-status-error, #f87171)";
1329
+ return themeVar("stateError");
1217
1330
  }
1218
- if (isCommitting) {
1219
- return "var(--dsw-status-warning, #fbbf24)";
1331
+ if (isCommitting || sessionUnreadable) {
1332
+ return themeVar("stateWarning");
1220
1333
  }
1221
- return "var(--dsw-status-success, #34d399)";
1334
+ return themeVar("stateSuccess");
1222
1335
  }
1223
- function getStatusGlow(isOnline, isCommitting = false) {
1224
- if (isOnline && !isCommitting) {
1225
- return "0 0 6px var(--dsw-status-success, #34d399)";
1226
- }
1227
- return "none";
1336
+ function chatNodesToText(nodes) {
1337
+ if (!nodes) return "";
1338
+ const source = Array.isArray(nodes) ? nodes : typeof nodes === "object" ? Object.values(nodes) : [];
1339
+ const parts = [];
1340
+ const visit = (value, depth = 0) => {
1341
+ if (depth > 4 || value === null || value === void 0) return;
1342
+ if (typeof value === "string") {
1343
+ parts.push(value);
1344
+ return;
1345
+ }
1346
+ if (Array.isArray(value)) {
1347
+ for (const item of value) visit(item, depth + 1);
1348
+ return;
1349
+ }
1350
+ if (typeof value === "object") {
1351
+ const record = value;
1352
+ for (const key of ["content", "text", "data", "body"]) {
1353
+ if (key in record) visit(record[key], depth + 1);
1354
+ }
1355
+ }
1356
+ };
1357
+ for (const node of source) visit(node);
1358
+ return parts.join("\n");
1228
1359
  }
1229
- function getFallbackSessionMessages(sessionId) {
1230
- if (!sessionId || typeof window === "undefined") {
1231
- return void 0;
1360
+ function OpenVikingStatusChip(props) {
1361
+ const { useChat, ...rest } = props;
1362
+ if (useChat && rest.contextText === void 0 && rest.messages === void 0) {
1363
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ChatReadBoundary, { fallback: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StatusChipView, { ...rest }), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ChatBackedStatusChip, { useChat, ...rest }) });
1232
1364
  }
1233
- const win = window;
1234
- if (win.__DSH_STORE__?.getState) {
1235
- const state = win.__DSH_STORE__.getState();
1236
- return state?.conversations?.[sessionId]?.messages || state?.sessions?.[sessionId]?.messages;
1365
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StatusChipView, { ...rest });
1366
+ }
1367
+ var ChatReadBoundary = class extends import_react2.default.Component {
1368
+ state = { failed: false };
1369
+ static getDerivedStateFromError() {
1370
+ return { failed: true };
1237
1371
  }
1238
- if (win.__DSH_SESSION_MESSAGES__?.[sessionId]) {
1239
- return win.__DSH_SESSION_MESSAGES__[sessionId];
1372
+ render() {
1373
+ return this.state.failed ? this.props.fallback : this.props.children;
1240
1374
  }
1241
- return void 0;
1375
+ };
1376
+ function ChatBackedStatusChip({
1377
+ useChat,
1378
+ ...rest
1379
+ }) {
1380
+ const nodes = useChat((snapshot) => {
1381
+ try {
1382
+ return snapshot?.legacy?.nodes !== void 0 ? snapshot.legacy.nodes : snapshot?.nodes;
1383
+ } catch {
1384
+ return void 0;
1385
+ }
1386
+ });
1387
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StatusChipView, { ...rest, messages: nodes });
1242
1388
  }
1243
- function OpenVikingStatusChip({
1389
+ function StatusChipView({
1244
1390
  sessionId,
1245
1391
  messages,
1246
1392
  contextText,
@@ -1249,32 +1395,42 @@ function OpenVikingStatusChip({
1249
1395
  className,
1250
1396
  initialHealth,
1251
1397
  initialSessionData,
1398
+ initialSessionRead,
1252
1399
  initialOpen = false
1253
1400
  }) {
1254
1401
  const [health, setHealth] = (0, import_react2.useState)(
1255
1402
  initialHealth ?? null
1256
1403
  );
1257
- const [sessionData, setSessionData] = (0, import_react2.useState)(
1258
- initialSessionData ?? null
1404
+ const [sessionRead, setSessionRead] = (0, import_react2.useState)(
1405
+ initialSessionRead ?? (initialSessionData ? { status: "ok", session: initialSessionData } : null)
1259
1406
  );
1260
1407
  const [isOpen, setIsOpen] = (0, import_react2.useState)(initialOpen);
1261
1408
  const [isCommitting, setIsCommitting] = (0, import_react2.useState)(false);
1262
1409
  const [commitError, setCommitError] = (0, import_react2.useState)(null);
1410
+ const [isHovered, setIsHovered] = (0, import_react2.useState)(false);
1411
+ const [statsHost, setStatsHost] = (0, import_react2.useState)(null);
1263
1412
  const popoverRef = (0, import_react2.useRef)(null);
1264
1413
  const apiClient = client ?? defaultOpenVikingClient;
1265
- const fallbackMessages = (0, import_react2.useMemo)(() => {
1266
- if (messages || contextText) return void 0;
1267
- return getFallbackSessionMessages(sessionId);
1268
- }, [sessionId, messages, contextText]);
1269
- const inputForParser = (0, import_react2.useMemo)(() => {
1270
- if (contextText && messages) {
1271
- return [contextText, ...messages];
1414
+ (0, import_react2.useEffect)(() => {
1415
+ if (typeof document === "undefined") return;
1416
+ function findHost() {
1417
+ const el = document.querySelector("[data-composer-stats]");
1418
+ setStatsHost((prev) => prev !== el ? el : prev);
1272
1419
  }
1273
- return contextText ?? messages ?? fallbackMessages;
1274
- }, [contextText, messages, fallbackMessages]);
1420
+ findHost();
1421
+ const observer = new MutationObserver(() => {
1422
+ findHost();
1423
+ });
1424
+ observer.observe(document.body, { childList: true, subtree: true });
1425
+ return () => observer.disconnect();
1426
+ }, []);
1427
+ const conversationText = (0, import_react2.useMemo)(() => {
1428
+ if (typeof contextText === "string") return contextText;
1429
+ return chatNodesToText(messages);
1430
+ }, [contextText, messages]);
1275
1431
  const recalledResult = (0, import_react2.useMemo)(
1276
- () => parseRecalledMemories(inputForParser),
1277
- [inputForParser]
1432
+ () => parseRecalledMemories(conversationText),
1433
+ [conversationText]
1278
1434
  );
1279
1435
  const fetchStatus = (0, import_react2.useCallback)(async () => {
1280
1436
  try {
@@ -1283,8 +1439,7 @@ function OpenVikingStatusChip({
1283
1439
  if (!healthRes.ok) {
1284
1440
  return;
1285
1441
  }
1286
- const session = await apiClient.fetchSession(sessionId);
1287
- setSessionData(session);
1442
+ setSessionRead(await apiClient.readSession(sessionId));
1288
1443
  } catch {
1289
1444
  setHealth({ ok: false });
1290
1445
  }
@@ -1337,26 +1492,29 @@ function OpenVikingStatusChip({
1337
1492
  }
1338
1493
  };
1339
1494
  const isOnline = health?.ok === true;
1495
+ const sessionData = sessionRead?.status === "ok" ? sessionRead.session : null;
1496
+ const sessionUnreadable = isOnline && sessionRead !== null && sessionRead.status !== "ok";
1340
1497
  const pendingTokens = sessionData?.pending_tokens ?? 0;
1341
- const pendingTokensK = Math.round(pendingTokens / 1e3);
1342
1498
  const recalledCount = recalledResult.recalledCount;
1343
- const statusColor = getStatusIndicatorColor(isOnline, isCommitting);
1344
- const statusGlow = getStatusGlow(isOnline, isCommitting);
1499
+ const statusColor = getStatusIndicatorColor(
1500
+ isOnline,
1501
+ isCommitting,
1502
+ sessionUnreadable
1503
+ );
1345
1504
  const tooltipTitle = formatTooltipTitle({
1346
1505
  isOnline,
1347
1506
  isCommitting,
1348
1507
  recalledCount,
1349
- pendingTokens
1508
+ pendingTokens,
1509
+ sessionUnreadable
1350
1510
  });
1351
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1352
- "div",
1511
+ const label = !isOnline ? "OV offline" : sessionUnreadable ? `OV: ${recalledCount} rec \xB7 no access` : `OV: ${recalledCount} rec \xB7 ${Math.round(pendingTokens / 1e3)}k pend`;
1512
+ const chipElement = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1513
+ "span",
1353
1514
  {
1354
1515
  className,
1355
- style: {
1356
- position: "relative",
1357
- display: "inline-flex",
1358
- alignItems: "center"
1359
- },
1516
+ "data-openviking-status": "true",
1517
+ style: { minWidth: 0, display: "inline-flex", position: "relative" },
1360
1518
  ref: popoverRef,
1361
1519
  children: [
1362
1520
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
@@ -1364,23 +1522,27 @@ function OpenVikingStatusChip({
1364
1522
  {
1365
1523
  type: "button",
1366
1524
  onClick: () => setIsOpen(!isOpen),
1525
+ onMouseEnter: () => setIsHovered(true),
1526
+ onMouseLeave: () => setIsHovered(false),
1367
1527
  "aria-expanded": isOpen,
1368
1528
  "aria-haspopup": "dialog",
1369
1529
  style: {
1370
- display: "inline-flex",
1530
+ boxSizing: "border-box",
1531
+ maxWidth: "100%",
1532
+ color: isHovered ? themeVar("labelSecondary") : themeVar("labelTertiary"),
1533
+ fontFamily: "var(--dsw-font-family, system-ui)",
1534
+ fontSize: "var(--dsh-content-font-size-secondary, 13px)",
1535
+ lineHeight: "calc(20px + var(--dsh-content-font-delta-secondary, 0px))",
1536
+ fontVariantNumeric: "tabular-nums",
1537
+ whiteSpace: "nowrap",
1538
+ background: isHovered ? themeVar("hoverBackground") : "transparent",
1539
+ border: "none",
1540
+ borderRadius: "24px",
1371
1541
  alignItems: "center",
1372
1542
  gap: "6px",
1373
- height: "26px",
1374
- padding: "0 8px",
1375
- fontSize: "12px",
1376
- fontFamily: "var(--dsw-font-mono, monospace)",
1377
- borderRadius: "6px",
1378
- background: "var(--dsw-surface-base, rgba(255, 255, 255, 0.05))",
1379
- border: "1px solid var(--dsw-border-subtle, rgba(255, 255, 255, 0.1))",
1380
- color: "var(--dsw-text-muted, #94a3b8)",
1381
- cursor: "pointer",
1382
- transition: "all 0.15s ease",
1383
- userSelect: "none"
1543
+ padding: "1px 8px",
1544
+ display: "inline-flex",
1545
+ cursor: "pointer"
1384
1546
  },
1385
1547
  title: tooltipTitle,
1386
1548
  "aria-label": tooltipTitle,
@@ -1389,25 +1551,17 @@ function OpenVikingStatusChip({
1389
1551
  "span",
1390
1552
  {
1391
1553
  "data-testid": "status-dot",
1554
+ "aria-hidden": "true",
1392
1555
  style: {
1393
- width: "7px",
1394
- height: "7px",
1556
+ width: "6px",
1557
+ height: "6px",
1395
1558
  borderRadius: "50%",
1396
- backgroundColor: statusColor,
1397
- boxShadow: statusGlow,
1559
+ background: statusColor,
1398
1560
  flexShrink: 0
1399
1561
  }
1400
1562
  }
1401
1563
  ),
1402
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1403
- "span",
1404
- {
1405
- style: { fontWeight: 600, color: "var(--dsw-text-default, #e2e8f0)" },
1406
- children: isOnline ? "OV:" : "OV"
1407
- }
1408
- ),
1409
- " ",
1410
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: isOnline ? `${recalledCount} rec \xB7 ${pendingTokensK}k pend` : "offline" })
1564
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { textOverflow: "ellipsis", minWidth: 0 }, children: label })
1411
1565
  ]
1412
1566
  }
1413
1567
  ),
@@ -1417,6 +1571,7 @@ function OpenVikingStatusChip({
1417
1571
  sessionId,
1418
1572
  health,
1419
1573
  sessionData,
1574
+ sessionRead,
1420
1575
  recalledResult,
1421
1576
  endpoint: apiClient.endpoint,
1422
1577
  isCommitting,
@@ -1428,6 +1583,572 @@ function OpenVikingStatusChip({
1428
1583
  ]
1429
1584
  }
1430
1585
  );
1586
+ if (statsHost && typeof document !== "undefined") {
1587
+ return import_react_dom.default.createPortal(chipElement, statsHost);
1588
+ }
1589
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1590
+ "div",
1591
+ {
1592
+ style: {
1593
+ maxWidth: "var(--dsh-chat-content-width, 748px)",
1594
+ boxSizing: "border-box",
1595
+ width: "100%",
1596
+ padding: "4px calc(var(--dsh-composer-side-clearance, 0px) + 16px) 0px",
1597
+ fontSize: "var(--dsh-content-font-size-secondary, 13px)",
1598
+ lineHeight: "calc(20px + var(--dsh-content-font-delta-secondary, 0px))",
1599
+ justifyContent: "center",
1600
+ gap: "12px",
1601
+ margin: "0 auto",
1602
+ display: "flex"
1603
+ },
1604
+ children: chipElement
1605
+ }
1606
+ );
1607
+ }
1608
+
1609
+ // src/client/OpenVikingSettingsSection.tsx
1610
+ var import_react3 = require("react");
1611
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1612
+ var API_CONFIG = "/openviking-status/api/config";
1613
+ var API_TEST = "/openviking-status/api/test-connection";
1614
+ var SOURCE_LABELS = {
1615
+ ovcli: "Auto-detected from ~/.openviking/ovcli.conf",
1616
+ env: "Auto-detected from environment variables",
1617
+ ov: "Auto-detected from ~/.openviking/ov.conf",
1618
+ settings: "Custom override in DSH settings.yaml",
1619
+ default: "Default local configuration"
1620
+ };
1621
+ function OpenVikingSettingsSection({
1622
+ initialConfig,
1623
+ onConfigSaved,
1624
+ className,
1625
+ style
1626
+ }) {
1627
+ const [endpoint, setEndpoint] = (0, import_react3.useState)(
1628
+ initialConfig?.endpoint || "http://127.0.0.1:1933"
1629
+ );
1630
+ const [apiKey, setApiKey] = (0, import_react3.useState)("");
1631
+ const [showKey, setShowKey] = (0, import_react3.useState)(false);
1632
+ const [source, setSource] = (0, import_react3.useState)(
1633
+ initialConfig?.source || "default"
1634
+ );
1635
+ const [hasStoredKey, setHasStoredKey] = (0, import_react3.useState)(
1636
+ initialConfig?.hasApiKey || false
1637
+ );
1638
+ const [loading, setLoading] = (0, import_react3.useState)(!initialConfig);
1639
+ const [testing, setTesting] = (0, import_react3.useState)(false);
1640
+ const [saving, setSaving] = (0, import_react3.useState)(false);
1641
+ const [testResult, setTestResult] = (0, import_react3.useState)(null);
1642
+ const [flash, setFlash] = (0, import_react3.useState)(null);
1643
+ (0, import_react3.useEffect)(() => {
1644
+ if (initialConfig) return;
1645
+ let active = true;
1646
+ async function fetchConfig() {
1647
+ try {
1648
+ setLoading(true);
1649
+ const res = await fetch(API_CONFIG, { cache: "no-store" });
1650
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
1651
+ const data = await res.json();
1652
+ if (!active) return;
1653
+ const newEp = data.endpoint || "http://127.0.0.1:1933";
1654
+ setEndpoint(newEp);
1655
+ setSource(data.source || "default");
1656
+ setHasStoredKey(data.hasApiKey);
1657
+ defaultOpenVikingClient.clearResolvedSessions();
1658
+ } catch {
1659
+ } finally {
1660
+ if (active) setLoading(false);
1661
+ }
1662
+ }
1663
+ void fetchConfig();
1664
+ return () => {
1665
+ active = false;
1666
+ };
1667
+ }, [initialConfig]);
1668
+ async function handleTestConnection() {
1669
+ setTesting(true);
1670
+ setTestResult(null);
1671
+ setFlash(null);
1672
+ try {
1673
+ const res = await fetch(API_TEST, {
1674
+ method: "POST",
1675
+ headers: { "Content-Type": "application/json" },
1676
+ body: JSON.stringify({
1677
+ endpoint: endpoint.trim(),
1678
+ apiKey: apiKey.trim() || void 0
1679
+ })
1680
+ });
1681
+ const data = await res.json();
1682
+ setTestResult(data);
1683
+ } catch (err) {
1684
+ setTestResult({
1685
+ ok: false,
1686
+ authenticated: false,
1687
+ error: String(err instanceof Error ? err.message : err)
1688
+ });
1689
+ } finally {
1690
+ setTesting(false);
1691
+ }
1692
+ }
1693
+ async function handleSave() {
1694
+ setSaving(true);
1695
+ setFlash(null);
1696
+ try {
1697
+ const res = await fetch(API_CONFIG, {
1698
+ method: "POST",
1699
+ headers: { "Content-Type": "application/json" },
1700
+ body: JSON.stringify({
1701
+ endpoint: endpoint.trim(),
1702
+ apiKey: apiKey.trim() || void 0
1703
+ })
1704
+ });
1705
+ const data = await res.json();
1706
+ if (!res.ok || data.error) {
1707
+ throw new Error(data.error || `HTTP ${res.status}`);
1708
+ }
1709
+ setFlash({ kind: "ok", message: "Settings saved successfully" });
1710
+ setSource("settings");
1711
+ defaultOpenVikingClient.clearResolvedSessions();
1712
+ if (apiKey.trim()) {
1713
+ setHasStoredKey(true);
1714
+ setApiKey("");
1715
+ }
1716
+ onConfigSaved?.(
1717
+ data.config || {
1718
+ endpoint,
1719
+ hasApiKey: hasStoredKey || Boolean(apiKey.trim()),
1720
+ source: "settings"
1721
+ }
1722
+ );
1723
+ } catch (err) {
1724
+ setFlash({
1725
+ kind: "err",
1726
+ message: `Failed to save: ${err instanceof Error ? err.message : String(err)}`
1727
+ });
1728
+ } finally {
1729
+ setSaving(false);
1730
+ }
1731
+ }
1732
+ async function handleReset() {
1733
+ setSaving(true);
1734
+ setFlash(null);
1735
+ setTestResult(null);
1736
+ try {
1737
+ const res = await fetch(API_CONFIG, {
1738
+ method: "POST",
1739
+ headers: { "Content-Type": "application/json" },
1740
+ body: JSON.stringify({ reset: true })
1741
+ });
1742
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
1743
+ const cfgRes = await fetch(API_CONFIG, { cache: "no-store" });
1744
+ if (cfgRes.ok) {
1745
+ const data = await cfgRes.json();
1746
+ const newEp = data.endpoint || "http://127.0.0.1:1933";
1747
+ setEndpoint(newEp);
1748
+ setSource(data.source || "default");
1749
+ setHasStoredKey(data.hasApiKey);
1750
+ setApiKey("");
1751
+ defaultOpenVikingClient.clearResolvedSessions();
1752
+ setFlash({
1753
+ kind: "ok",
1754
+ message: "Reset to auto-detected local settings"
1755
+ });
1756
+ onConfigSaved?.(data);
1757
+ }
1758
+ } catch (err) {
1759
+ setFlash({
1760
+ kind: "err",
1761
+ message: `Failed to reset: ${err instanceof Error ? err.message : String(err)}`
1762
+ });
1763
+ } finally {
1764
+ setSaving(false);
1765
+ }
1766
+ }
1767
+ const sourceBadgeText = SOURCE_LABELS[source] || SOURCE_LABELS["default"];
1768
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1769
+ "div",
1770
+ {
1771
+ className: `ov-settings-root ${className || ""}`,
1772
+ style: {
1773
+ display: "flex",
1774
+ flexDirection: "column",
1775
+ gap: 20,
1776
+ maxWidth: 720,
1777
+ padding: "24px 28px",
1778
+ color: themeVar("labelPrimary"),
1779
+ fontSize: 14,
1780
+ lineHeight: 1.6,
1781
+ ...style
1782
+ },
1783
+ children: [
1784
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1785
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1786
+ "div",
1787
+ {
1788
+ style: {
1789
+ display: "flex",
1790
+ alignItems: "baseline",
1791
+ gap: 12,
1792
+ marginBottom: 6
1793
+ },
1794
+ children: [
1795
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1796
+ "h2",
1797
+ {
1798
+ style: {
1799
+ fontSize: 20,
1800
+ fontWeight: 600,
1801
+ margin: 0,
1802
+ color: themeVar("labelPrimary")
1803
+ },
1804
+ children: "OpenViking Status"
1805
+ }
1806
+ ),
1807
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1808
+ "span",
1809
+ {
1810
+ style: {
1811
+ fontSize: 12,
1812
+ fontWeight: 500,
1813
+ padding: "2px 8px",
1814
+ borderRadius: 6,
1815
+ background: themeVar("insetSurface"),
1816
+ border: `1px solid ${themeVar("hairline")}`,
1817
+ color: themeVar("labelSecondary")
1818
+ },
1819
+ children: sourceBadgeText
1820
+ }
1821
+ )
1822
+ ]
1823
+ }
1824
+ ),
1825
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1826
+ "p",
1827
+ {
1828
+ style: { margin: 0, color: themeVar("labelSecondary"), fontSize: 13 },
1829
+ children: "Configure connection credentials for monitoring OpenViking persistent memory and session status."
1830
+ }
1831
+ )
1832
+ ] }),
1833
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1834
+ "div",
1835
+ {
1836
+ style: {
1837
+ border: `1px solid ${themeVar("hairline")}`,
1838
+ borderRadius: 12,
1839
+ padding: 20,
1840
+ background: themeVar("insetSurface"),
1841
+ display: "flex",
1842
+ flexDirection: "column",
1843
+ gap: 16
1844
+ },
1845
+ children: [
1846
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1847
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1848
+ "label",
1849
+ {
1850
+ style: {
1851
+ display: "block",
1852
+ fontSize: 13,
1853
+ fontWeight: 600,
1854
+ marginBottom: 6,
1855
+ color: themeVar("labelPrimary")
1856
+ },
1857
+ children: "Daemon Endpoint"
1858
+ }
1859
+ ),
1860
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1861
+ "input",
1862
+ {
1863
+ type: "text",
1864
+ value: endpoint,
1865
+ onChange: (e) => setEndpoint(e.target.value),
1866
+ placeholder: "http://127.0.0.1:1933",
1867
+ style: {
1868
+ width: "100%",
1869
+ boxSizing: "border-box",
1870
+ padding: "9px 12px",
1871
+ fontSize: 14,
1872
+ fontFamily: themeVar("fontMono"),
1873
+ borderRadius: 8,
1874
+ border: `1px solid ${themeVar("hairline")}`,
1875
+ background: themeVar("panelSurface"),
1876
+ color: themeVar("labelPrimary"),
1877
+ outline: "none"
1878
+ }
1879
+ }
1880
+ ),
1881
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1882
+ "span",
1883
+ {
1884
+ style: {
1885
+ fontSize: 12,
1886
+ color: themeVar("labelTertiary"),
1887
+ marginTop: 4,
1888
+ display: "block"
1889
+ },
1890
+ children: "The URL of the local or remote OpenViking HTTP server. Resolved from the DSH host."
1891
+ }
1892
+ )
1893
+ ] }),
1894
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
1895
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1896
+ "div",
1897
+ {
1898
+ style: {
1899
+ display: "flex",
1900
+ justifyContent: "space-between",
1901
+ alignItems: "center",
1902
+ marginBottom: 6
1903
+ },
1904
+ children: [
1905
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1906
+ "label",
1907
+ {
1908
+ style: {
1909
+ fontSize: 13,
1910
+ fontWeight: 600,
1911
+ color: themeVar("labelPrimary")
1912
+ },
1913
+ children: "API Token (Authentication)"
1914
+ }
1915
+ ),
1916
+ hasStoredKey && !apiKey && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1917
+ "span",
1918
+ {
1919
+ style: {
1920
+ fontSize: 12,
1921
+ color: themeVar("stateSuccess"),
1922
+ fontWeight: 500
1923
+ },
1924
+ children: "\u25CF Active token configured"
1925
+ }
1926
+ )
1927
+ ]
1928
+ }
1929
+ ),
1930
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", gap: 8 }, children: [
1931
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1932
+ "input",
1933
+ {
1934
+ type: showKey ? "text" : "password",
1935
+ value: apiKey,
1936
+ onChange: (e) => setApiKey(e.target.value),
1937
+ placeholder: hasStoredKey ? "(Token stored \u2014 leave blank to keep unchanged)" : "Optional or required if daemon uses auth_mode: api_key",
1938
+ style: {
1939
+ flex: 1,
1940
+ padding: "9px 12px",
1941
+ fontSize: 14,
1942
+ fontFamily: themeVar("fontMono"),
1943
+ borderRadius: 8,
1944
+ border: `1px solid ${themeVar("hairline")}`,
1945
+ background: themeVar("panelSurface"),
1946
+ color: themeVar("labelPrimary"),
1947
+ outline: "none"
1948
+ }
1949
+ }
1950
+ ),
1951
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1952
+ "button",
1953
+ {
1954
+ type: "button",
1955
+ onClick: () => setShowKey((v) => !v),
1956
+ style: {
1957
+ padding: "0 14px",
1958
+ fontSize: 13,
1959
+ fontWeight: 500,
1960
+ borderRadius: 8,
1961
+ border: `1px solid ${themeVar("hairline")}`,
1962
+ background: themeVar("panelSurface"),
1963
+ color: themeVar("labelSecondary"),
1964
+ cursor: "pointer"
1965
+ },
1966
+ children: showKey ? "Hide" : "Show"
1967
+ }
1968
+ )
1969
+ ] }),
1970
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1971
+ "span",
1972
+ {
1973
+ style: {
1974
+ fontSize: 12,
1975
+ color: themeVar("labelTertiary"),
1976
+ marginTop: 4,
1977
+ display: "block"
1978
+ },
1979
+ children: "Required when the daemon runs with auth_mode: api_key. Local tokens are typically found in ~/.openviking/ovcli.conf."
1980
+ }
1981
+ )
1982
+ ] }),
1983
+ testResult && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1984
+ "div",
1985
+ {
1986
+ style: {
1987
+ padding: "10px 14px",
1988
+ borderRadius: 8,
1989
+ fontSize: 13,
1990
+ display: "flex",
1991
+ alignItems: "center",
1992
+ gap: 8,
1993
+ border: `1px solid ${testResult.ok && testResult.authenticated ? themeVar("stateSuccess") : themeVar("stateError")}`,
1994
+ background: themeVar("panelSurface"),
1995
+ color: testResult.ok && testResult.authenticated ? themeVar("stateSuccess") : themeVar("stateError")
1996
+ },
1997
+ children: [
1998
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: testResult.ok && testResult.authenticated ? "\u25CF" : "\u2715" }),
1999
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { flex: 1 }, children: testResult.ok && testResult.authenticated ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
2000
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "Connected successfully" }),
2001
+ testResult.version && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { children: [
2002
+ " \xB7 ",
2003
+ testResult.version
2004
+ ] }),
2005
+ testResult.storage && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { children: [
2006
+ " (storage: ",
2007
+ testResult.storage,
2008
+ ")"
2009
+ ] })
2010
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
2011
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: "Connection failed: " }),
2012
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: testResult.error || "Unable to reach daemon or token unauthorized" })
2013
+ ] }) })
2014
+ ]
2015
+ }
2016
+ ),
2017
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2018
+ "div",
2019
+ {
2020
+ style: {
2021
+ display: "flex",
2022
+ flexWrap: "wrap",
2023
+ alignItems: "center",
2024
+ gap: 10,
2025
+ paddingTop: 4
2026
+ },
2027
+ children: [
2028
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2029
+ "button",
2030
+ {
2031
+ type: "button",
2032
+ disabled: testing || loading,
2033
+ onClick: () => void handleTestConnection(),
2034
+ style: {
2035
+ padding: "8px 16px",
2036
+ fontSize: 13,
2037
+ fontWeight: 500,
2038
+ borderRadius: 8,
2039
+ border: `1px solid ${themeVar("hairline")}`,
2040
+ background: themeVar("panelSurface"),
2041
+ color: themeVar("labelPrimary"),
2042
+ cursor: testing ? "not-allowed" : "pointer",
2043
+ opacity: testing ? 0.6 : 1
2044
+ },
2045
+ children: testing ? "Testing..." : "Test Connection"
2046
+ }
2047
+ ),
2048
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2049
+ "button",
2050
+ {
2051
+ type: "button",
2052
+ disabled: saving || loading,
2053
+ onClick: () => void handleSave(),
2054
+ style: {
2055
+ padding: "8px 18px",
2056
+ fontSize: 13,
2057
+ fontWeight: 600,
2058
+ borderRadius: 8,
2059
+ border: "none",
2060
+ background: themeVar("buttonPrimaryFill"),
2061
+ color: themeVar("buttonPrimaryText"),
2062
+ cursor: saving ? "not-allowed" : "pointer",
2063
+ opacity: saving ? 0.6 : 1
2064
+ },
2065
+ children: saving ? "Saving..." : "Save Settings"
2066
+ }
2067
+ ),
2068
+ source === "settings" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2069
+ "button",
2070
+ {
2071
+ type: "button",
2072
+ disabled: saving || loading,
2073
+ onClick: () => void handleReset(),
2074
+ style: {
2075
+ padding: "8px 14px",
2076
+ fontSize: 13,
2077
+ fontWeight: 500,
2078
+ borderRadius: 8,
2079
+ border: `1px solid ${themeVar("hairline")}`,
2080
+ background: "transparent",
2081
+ color: themeVar("labelSecondary"),
2082
+ cursor: "pointer",
2083
+ marginLeft: "auto"
2084
+ },
2085
+ children: "Reset to Auto-detected"
2086
+ }
2087
+ ),
2088
+ flash && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2089
+ "span",
2090
+ {
2091
+ style: {
2092
+ fontSize: 13,
2093
+ color: flash.kind === "ok" ? themeVar("stateSuccess") : themeVar("stateError"),
2094
+ fontWeight: 500
2095
+ },
2096
+ children: flash.message
2097
+ }
2098
+ )
2099
+ ]
2100
+ }
2101
+ )
2102
+ ]
2103
+ }
2104
+ ),
2105
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2106
+ "div",
2107
+ {
2108
+ style: {
2109
+ border: `1px solid ${themeVar("hairline")}`,
2110
+ borderRadius: 10,
2111
+ padding: "14px 18px",
2112
+ background: themeVar("panelSurface"),
2113
+ fontSize: 13,
2114
+ color: themeVar("labelSecondary"),
2115
+ lineHeight: 1.5
2116
+ },
2117
+ children: [
2118
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2119
+ "p",
2120
+ {
2121
+ style: {
2122
+ margin: "0 0 6px 0",
2123
+ fontWeight: 600,
2124
+ color: themeVar("labelPrimary")
2125
+ },
2126
+ children: "How OpenViking connection works:"
2127
+ }
2128
+ ),
2129
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("ul", { style: { margin: 0, paddingLeft: 18 }, children: [
2130
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("li", { style: { marginBottom: 4 }, children: "All requests to OpenViking proxy through the DSH Desktop host, making it work seamlessly even when managing DSH remotely over Tailscale, mobile, or LAN." }),
2131
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { style: { marginBottom: 4 }, children: [
2132
+ "If OpenViking runs locally on the standard port (1933), the plugin auto-detects credentials from ",
2133
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { children: "~/.openviking/ovcli.conf" }),
2134
+ "."
2135
+ ] }),
2136
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { children: [
2137
+ "Custom values saved here are persisted in",
2138
+ " ",
2139
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { children: "~/.dsh/settings.yaml" }),
2140
+ " under the",
2141
+ " ",
2142
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { children: "openviking-status" }),
2143
+ " namespace."
2144
+ ] })
2145
+ ] })
2146
+ ]
2147
+ }
2148
+ )
2149
+ ]
2150
+ }
2151
+ );
1431
2152
  }
1432
2153
 
1433
2154
  // src/client/index.tsx
@@ -1436,10 +2157,10 @@ var inject = ["slots"];
1436
2157
  function apply(ctx) {
1437
2158
  ctx.effect(
1438
2159
  () => ctx.slots.inject(
1439
- "conversation.input.right",
2160
+ "conversation.composer.dock",
1440
2161
  () => ctx.slots.register(
1441
2162
  {
1442
- name: "conversation.input.right",
2163
+ name: "conversation.composer.dock",
1443
2164
  id: "openviking-status",
1444
2165
  order: 50,
1445
2166
  label: "OpenViking"
@@ -1447,7 +2168,22 @@ function apply(ctx) {
1447
2168
  OpenVikingStatusChip
1448
2169
  )
1449
2170
  ),
1450
- "openviking-status: composer chip"
2171
+ "openviking-status: composer stats chip"
2172
+ );
2173
+ ctx.effect(
2174
+ () => ctx.slots.inject(
2175
+ "settings.section",
2176
+ () => ctx.slots.register(
2177
+ {
2178
+ name: "settings.section",
2179
+ id: "openviking-status",
2180
+ order: 35,
2181
+ label: () => "OpenViking"
2182
+ },
2183
+ OpenVikingSettingsSection
2184
+ )
2185
+ ),
2186
+ "openviking-status: settings section"
1451
2187
  );
1452
2188
  }
1453
2189
  return module.exports;