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