@dipertq/dsh-openviking-status 0.1.7 → 0.1.8

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
@@ -30,11 +40,14 @@ __export(client_exports, {
30
40
  OpenVikingClient: () => OpenVikingClient,
31
41
  OpenVikingStatusChip: () => OpenVikingStatusChip,
32
42
  OpenVikingStatusPopover: () => OpenVikingStatusPopover,
43
+ THEME: () => THEME,
33
44
  apply: () => apply,
45
+ chatNodesToText: () => chatNodesToText,
34
46
  checkHealth: () => checkHealth,
35
47
  commitSession: () => commitSession,
36
48
  defaultOpenVikingClient: () => defaultOpenVikingClient,
37
49
  fetchSession: () => fetchSession,
50
+ formatDaemonVersion: () => formatDaemonVersion,
38
51
  formatEndpoint: () => formatEndpoint,
39
52
  formatMemoryLeafName: () => formatMemoryLeafName,
40
53
  formatPendingTokens: () => formatPendingTokens,
@@ -42,11 +55,9 @@ __export(client_exports, {
42
55
  formatStatusLabel: () => formatStatusLabel,
43
56
  formatTooltipTitle: () => formatTooltipTitle,
44
57
  getCategoryBadgeStyle: () => getCategoryBadgeStyle,
45
- getFallbackSessionMessages: () => getFallbackSessionMessages,
46
58
  getProgressBarColor: () => getProgressBarColor,
47
59
  getProgressBarPercent: () => getProgressBarPercent,
48
60
  getSession: () => getSession,
49
- getStatusGlow: () => getStatusGlow,
50
61
  getStatusIndicatorColor: () => getStatusIndicatorColor,
51
62
  handleEscapeKey: () => handleEscapeKey,
52
63
  inferCategory: () => inferCategory,
@@ -55,12 +66,13 @@ __export(client_exports, {
55
66
  parseRecalledMemories: () => parseRecalledMemories,
56
67
  resolveApiKey: () => resolveApiKey,
57
68
  resolveEndpoint: () => resolveEndpoint,
69
+ themeVar: () => themeVar,
58
70
  truncateSessionId: () => truncateSessionId
59
71
  });
60
72
  module.exports = __toCommonJS(client_exports);
61
73
 
62
74
  // src/client/OpenVikingStatusChip.tsx
63
- var import_react2 = require("react");
75
+ var import_react2 = __toESM(require("react"), 1);
64
76
 
65
77
  // src/client/api.ts
66
78
  var DEFAULT_OPENVIKING_ENDPOINT = "http://127.0.0.1:1933";
@@ -144,6 +156,8 @@ var OpenVikingClient = class {
144
156
  } else if (raw.startsWith("dsh-")) {
145
157
  const suffix = raw.slice("dsh-".length);
146
158
  candidates.push(raw, `dsh-session-${suffix}`);
159
+ } else if (raw.startsWith("session-")) {
160
+ candidates.push(`dsh-${raw}`, raw);
147
161
  } else {
148
162
  candidates.push(`dsh-session-${raw}`, `dsh-${raw}`, raw);
149
163
  }
@@ -179,12 +193,16 @@ var OpenVikingClient = class {
179
193
  }
180
194
  }
181
195
  /**
182
- * Получение метаданных сессии по идентификатору с автоматическим разрешением префикса.
183
- * При сетевых сбоях или ошибках авторизации возвращает null, не выбрасывая исключений.
196
+ * Чтение метаданных сессии с явной причиной неудачи.
197
+ *
198
+ * Демон может работать с `auth_mode: api_key`: тогда `/health` остаётся
199
+ * открытым, а сессия отвечает 401. Схлопывать это в «нет данных» нельзя —
200
+ * иначе интерфейс покажет живой индикатор рядом с нулями и умолчит о том,
201
+ * что счётчики просто недоступны.
184
202
  */
185
- async fetchSession(sessionId) {
203
+ async readSession(sessionId) {
186
204
  if (!sessionId || !sessionId.trim()) {
187
- return null;
205
+ return { status: "missing" };
188
206
  }
189
207
  const candidates = this.getCandidateSessionIds(sessionId);
190
208
  for (const candidateId of candidates) {
@@ -199,30 +217,48 @@ var OpenVikingClient = class {
199
217
  if (res.status === 404) {
200
218
  continue;
201
219
  }
220
+ if (res.status === 401 || res.status === 403) {
221
+ return { status: "unauthorized" };
222
+ }
202
223
  if (!res.ok) {
203
- return null;
224
+ return { status: "error", detail: `HTTP ${res.status}` };
204
225
  }
205
226
  const data = await res.json();
206
227
  const raw = data?.result ?? data?.data ?? data;
207
228
  if (!raw || typeof raw !== "object") {
208
- return null;
229
+ return { status: "error", detail: "malformed response body" };
209
230
  }
210
231
  this.resolvedSessionIds.set(sessionId.trim(), candidateId);
211
232
  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
233
+ status: "ok",
234
+ session: {
235
+ session_id: typeof raw.session_id === "string" ? raw.session_id : candidateId,
236
+ peer_id: typeof raw.peer_id === "string" ? raw.peer_id : void 0,
237
+ pending_tokens: typeof raw.pending_tokens === "number" ? raw.pending_tokens : 0,
238
+ message_count: typeof raw.message_count === "number" ? raw.message_count : void 0,
239
+ commit_count: typeof raw.commit_count === "number" ? raw.commit_count : void 0,
240
+ last_commit_at: typeof raw.last_commit_at === "string" ? raw.last_commit_at : typeof raw.last_commit === "string" ? raw.last_commit : void 0,
241
+ created_at: typeof raw.created_at === "string" ? raw.created_at : void 0,
242
+ updated_at: typeof raw.updated_at === "string" ? raw.updated_at : void 0
243
+ }
244
+ };
245
+ } catch (err) {
246
+ return {
247
+ status: "unreachable",
248
+ detail: err instanceof Error ? err.message : String(err)
220
249
  };
221
- } catch {
222
- return null;
223
250
  }
224
251
  }
225
- return null;
252
+ return { status: "missing" };
253
+ }
254
+ /**
255
+ * Получение метаданных сессии по идентификатору.
256
+ * Обёртка над {@link readSession} для вызывающих, которым причина неудачи
257
+ * не нужна: любая неудача сводится к null.
258
+ */
259
+ async fetchSession(sessionId) {
260
+ const result = await this.readSession(sessionId);
261
+ return result.status === "ok" ? result.session : null;
226
262
  }
227
263
  /**
228
264
  * Алиас для fetchSession
@@ -288,6 +324,41 @@ function commitSession(sessionId, options, endpoint, apiKey) {
288
324
  return client.commitSession(sessionId, options);
289
325
  }
290
326
 
327
+ // src/client/theme.ts
328
+ var THEME = {
329
+ /** Фон всплывающей панели — тот же, что у меню и диалогов DSH. */
330
+ panelSurface: "--dsw-specific-menu",
331
+ /** Тень панели. */
332
+ panelElevation: "--dsw-elevation-prominent",
333
+ /** Контур панели; задаётся через переменную тени, а не через border. */
334
+ panelStroke: "--dsw-alias-border-l1",
335
+ /** Заголовки и акцентный текст. */
336
+ labelPrimary: "--dsw-alias-label-primary",
337
+ /** Основной текст панели. */
338
+ labelSecondary: "--dsw-alias-label-secondary",
339
+ /** Приглушённый текст: подписи, значения, текст чипа в покое. */
340
+ labelTertiary: "--dsw-alias-label-tertiary",
341
+ /** Разделительная линия внутри панели. */
342
+ hairline: "--dsw-alias-border-l2",
343
+ /** Подсветка интерактивного элемента под курсором. */
344
+ hoverBackground: "--dsw-alias-interactive-bg-hover",
345
+ /** Подсветка нажатого элемента. */
346
+ activeBackground: "--dsw-alias-interactive-bg-active",
347
+ /** Демон доступен. */
348
+ stateSuccess: "--dsw-alias-state-success-primary",
349
+ /** Демон недоступен или сессия нечитаема. */
350
+ stateError: "--dsw-alias-state-error-primary",
351
+ /** Идёт коммит, либо накоплено близко к порогу. */
352
+ stateWarning: "--dsw-alias-state-warn-primary",
353
+ /** Утопленная поверхность: дорожка прогресс-бара. */
354
+ insetSurface: "--dsw-alias-bg-layer-2",
355
+ /** Моноширинный шрифт для идентификаторов и путей. */
356
+ fontMono: "--dsw-font-markdown-code-font-family"
357
+ };
358
+ function themeVar(role) {
359
+ return `var(${THEME[role]})`;
360
+ }
361
+
291
362
  // src/client/recallParser.ts
292
363
  var KNOWN_CATEGORIES = /* @__PURE__ */ new Set([
293
364
  "preferences",
@@ -596,6 +667,15 @@ function parseRecalledMemories(input) {
596
667
  // src/client/OpenVikingStatusPopover.tsx
597
668
  var import_react = require("react");
598
669
  var import_jsx_runtime = require("react/jsx-runtime");
670
+ function dshIcon(name2) {
671
+ try {
672
+ const primitives = typeof require === "function" ? require("@deepseek-ai/dsh-client-ui-primitives") : null;
673
+ const icon = primitives?.[name2];
674
+ return typeof icon === "function" ? icon : null;
675
+ } catch {
676
+ return null;
677
+ }
678
+ }
599
679
  function getProgressBarPercent(pendingTokens, threshold = COMMIT_THRESHOLD) {
600
680
  if (!threshold || threshold <= 0) return 0;
601
681
  const ratio = (pendingTokens || 0) / threshold;
@@ -603,9 +683,14 @@ function getProgressBarPercent(pendingTokens, threshold = COMMIT_THRESHOLD) {
603
683
  }
604
684
  function getProgressBarColor(percent) {
605
685
  if (percent >= 80) {
606
- return "var(--dsw-status-warning, #fbbf24)";
686
+ return themeVar("stateWarning");
607
687
  }
608
- return "var(--dsw-status-success, #34d399)";
688
+ return themeVar("stateSuccess");
689
+ }
690
+ function formatDaemonVersion(version) {
691
+ const value = version?.trim();
692
+ if (!value) return void 0;
693
+ return /^v/i.test(value) ? value : `v${value}`;
609
694
  }
610
695
  function formatRelativeTime(isoOrTimestamp, now = Date.now()) {
611
696
  if (!isoOrTimestamp) return void 0;
@@ -647,39 +732,8 @@ function truncateSessionId(id, maxLen = 16) {
647
732
  if (id.length <= maxLen) return id;
648
733
  return `${id.slice(0, maxLen)}...`;
649
734
  }
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
- }
735
+ function getCategoryBadgeStyle(_category) {
736
+ return { color: themeVar("labelTertiary") };
683
737
  }
684
738
  function handleEscapeKey(event, onClose) {
685
739
  if (event.key === "Escape") {
@@ -692,6 +746,7 @@ function OpenVikingStatusPopover({
692
746
  sessionId,
693
747
  health,
694
748
  sessionData,
749
+ sessionRead,
695
750
  recalledResult,
696
751
  endpoint,
697
752
  isCommitting = false,
@@ -711,13 +766,17 @@ function OpenVikingStatusPopover({
711
766
  };
712
767
  }, []);
713
768
  const isOnline = health?.ok === true;
714
- const statusColor = isOnline ? "var(--dsw-status-success, #34d399)" : "var(--dsw-status-error, #f87171)";
769
+ const sessionUnreadable = isOnline && sessionRead != null && sessionRead.status !== "ok";
770
+ const unauthorized = sessionRead?.status === "unauthorized";
771
+ const statusColor = isOnline ? sessionUnreadable ? themeVar("stateWarning") : themeVar("stateSuccess") : themeVar("stateError");
715
772
  const displaySessionId = sessionData?.session_id || sessionId || "";
716
773
  const pendingTokens = sessionData?.pending_tokens ?? 0;
717
774
  const progressPercent = getProgressBarPercent(pendingTokens);
718
775
  const progressBarColor = getProgressBarColor(progressPercent);
719
776
  const memoryItems = recalledResult?.items || [];
720
777
  const recalledCount = recalledResult?.recalledCount ?? memoryItems.length;
778
+ const CopyIcon = dshIcon("IconCopyOutline16");
779
+ const CheckIcon = dshIcon("IconCheckOutline16");
721
780
  const handleCopySessionId = (0, import_react.useCallback)(() => {
722
781
  if (!displaySessionId) return;
723
782
  if (typeof navigator !== "undefined" && navigator.clipboard) {
@@ -733,7 +792,15 @@ function OpenVikingStatusPopover({
733
792
  }, 1500);
734
793
  }
735
794
  }, [displaySessionId]);
736
- const isCommitDisabled = isCommitting || !isOnline || pendingTokens === 0;
795
+ const isCommitDisabled = isCommitting || !isOnline || sessionUnreadable || pendingTokens === 0;
796
+ const pendingLabel = sessionUnreadable ? "unavailable" : `${pendingTokens.toLocaleString()} / ${COMMIT_THRESHOLD.toLocaleString()}`;
797
+ const rowStyle = {
798
+ display: "flex",
799
+ justifyContent: "space-between",
800
+ alignItems: "center"
801
+ };
802
+ const mutedStyle = { color: themeVar("labelTertiary") };
803
+ const monoStyle = { fontFamily: themeVar("fontMono") };
737
804
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
738
805
  "div",
739
806
  {
@@ -744,17 +811,23 @@ function OpenVikingStatusPopover({
744
811
  position: "absolute",
745
812
  bottom: "calc(100% + 8px)",
746
813
  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)",
814
+ zIndex: 1100,
757
815
  boxSizing: "border-box",
816
+ width: "max-content",
817
+ minWidth: "min(300px, 100vw - 24px)",
818
+ maxWidth: "min(440px, 100vw - 24px)",
819
+ background: themeVar("panelSurface"),
820
+ boxShadow: themeVar("panelElevation"),
821
+ color: themeVar("labelSecondary"),
822
+ cursor: "default",
823
+ border: 0,
824
+ borderRadius: "12px",
825
+ padding: "16px",
826
+ fontSize: "12px",
827
+ lineHeight: "18px",
828
+ ...{
829
+ "--dsw-elevation-stroke-color": themeVar("panelStroke")
830
+ },
758
831
  ...style
759
832
  },
760
833
  children: [
@@ -765,58 +838,41 @@ function OpenVikingStatusPopover({
765
838
  style: {
766
839
  display: "flex",
767
840
  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))"
841
+ gap: "16px",
842
+ marginBottom: "8px",
843
+ color: themeVar("labelPrimary"),
844
+ fontWeight: 500
772
845
  },
773
846
  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
- ),
847
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { minWidth: 0 }, children: [
848
+ "OpenViking Memory",
849
+ " ",
786
850
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
787
- "div",
851
+ "span",
788
852
  {
789
853
  "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
- },
854
+ style: { ...mutedStyle, ...monoStyle, fontWeight: 400 },
796
855
  children: formatEndpoint(endpoint)
797
856
  }
798
857
  )
799
858
  ] }),
800
859
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
801
- "div",
860
+ "span",
802
861
  {
803
862
  "data-testid": "status-badge",
804
863
  style: {
805
864
  display: "inline-flex",
806
865
  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)",
866
+ gap: "6px",
812
867
  color: statusColor,
813
- fontWeight: 600
868
+ flexShrink: 0
814
869
  },
815
870
  children: [
816
871
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
817
872
  "span",
818
873
  {
819
874
  "data-testid": "status-badge-dot",
875
+ "aria-hidden": "true",
820
876
  style: {
821
877
  width: "6px",
822
878
  height: "6px",
@@ -826,13 +882,27 @@ function OpenVikingStatusPopover({
826
882
  }
827
883
  }
828
884
  ),
829
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: isOnline ? health?.version ? `ONLINE v${health.version}` : "ONLINE" : "OFFLINE" })
885
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: isOnline ? [
886
+ "ONLINE",
887
+ formatDaemonVersion(health?.version),
888
+ sessionUnreadable ? "\xB7 no session access" : null
889
+ ].filter(Boolean).join(" ") : "OFFLINE" })
830
890
  ]
831
891
  }
832
892
  )
833
893
  ]
834
894
  }
835
895
  ),
896
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
897
+ "div",
898
+ {
899
+ style: {
900
+ borderTop: `.5px solid ${themeVar("hairline")}`,
901
+ marginBottom: "10px"
902
+ },
903
+ "aria-hidden": "true"
904
+ }
905
+ ),
836
906
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
837
907
  "div",
838
908
  {
@@ -843,142 +913,88 @@ function OpenVikingStatusPopover({
843
913
  marginBottom: "10px"
844
914
  },
845
915
  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
- )
916
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: rowStyle, children: [
917
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: mutedStyle, children: "Session ID:" }),
918
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { display: "flex", alignItems: "center", gap: "4px" }, children: [
919
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
920
+ "span",
921
+ {
922
+ "data-testid": "session-id-value",
923
+ role: "button",
924
+ tabIndex: 0,
925
+ onKeyDown: (e) => {
926
+ if (e.key === "Enter" || e.key === " ") {
927
+ e.preventDefault();
928
+ handleCopySessionId();
929
+ }
930
+ },
931
+ "aria-label": "Click to copy Session ID",
932
+ style: { ...monoStyle, cursor: "pointer" },
933
+ title: displaySessionId,
934
+ onClick: handleCopySessionId,
935
+ children: truncateSessionId(displaySessionId)
936
+ }
937
+ ),
938
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
939
+ "button",
940
+ {
941
+ type: "button",
942
+ "data-testid": "copy-session-btn",
943
+ onClick: handleCopySessionId,
944
+ title: copied ? "Copied!" : "Copy Session ID",
945
+ "aria-label": copied ? "Copied!" : "Copy Session ID",
946
+ style: {
947
+ background: "none",
948
+ border: "none",
949
+ cursor: "pointer",
950
+ padding: "2px",
951
+ display: "inline-flex",
952
+ alignItems: "center",
953
+ color: copied ? themeVar("stateSuccess") : themeVar("labelTertiary")
954
+ },
955
+ children: copied ? CheckIcon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CheckIcon, { size: 14 }) : CopyIcon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CopyIcon, { size: 14 })
956
+ }
957
+ )
958
+ ] })
959
+ ] }),
960
+ sessionData?.peer_id && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: rowStyle, children: [
961
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: mutedStyle, children: "Peer ID:" }),
962
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
963
+ "span",
964
+ {
965
+ "data-testid": "peer-id-value",
966
+ style: {
967
+ ...monoStyle,
968
+ maxWidth: "180px",
969
+ overflow: "hidden",
970
+ textOverflow: "ellipsis",
971
+ whiteSpace: "nowrap"
972
+ },
973
+ title: sessionData.peer_id,
974
+ children: sessionData.peer_id
975
+ }
976
+ )
977
+ ] }),
978
+ sessionData?.last_commit_at && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: rowStyle, children: [
979
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: mutedStyle, children: "Last Commit:" }),
980
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
981
+ "span",
982
+ {
983
+ "data-testid": "last-commit-value",
984
+ title: sessionData.last_commit_at,
985
+ children: formatRelativeTime(sessionData.last_commit_at) || sessionData.last_commit_at
986
+ }
987
+ )
988
+ ] })
963
989
  ]
964
990
  }
965
991
  ),
966
992
  /* @__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)(
993
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { ...rowStyle, marginBottom: "4px" }, children: [
994
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: mutedStyle, children: "Pending Tokens:" }),
995
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "data-testid": "pending-tokens-label", children: pendingLabel })
996
+ ] }),
997
+ !sessionUnreadable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
982
998
  "div",
983
999
  {
984
1000
  "data-testid": "progress-bar-track",
@@ -986,7 +1002,7 @@ function OpenVikingStatusPopover({
986
1002
  width: "100%",
987
1003
  height: "6px",
988
1004
  borderRadius: "3px",
989
- backgroundColor: "rgba(255, 255, 255, 0.1)",
1005
+ backgroundColor: themeVar("insetSurface"),
990
1006
  overflow: "hidden"
991
1007
  },
992
1008
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -1004,30 +1020,34 @@ function OpenVikingStatusPopover({
1004
1020
  }
1005
1021
  )
1006
1022
  ] }),
1023
+ sessionUnreadable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1024
+ "div",
1025
+ {
1026
+ "data-testid": "session-unreadable-notice",
1027
+ style: {
1028
+ marginBottom: "12px",
1029
+ color: themeVar("labelTertiary")
1030
+ },
1031
+ children: unauthorized ? "The daemon requires an API key. Set openviking_api_key in localStorage to read session counters." : "Session counters are unavailable right now."
1032
+ }
1033
+ ),
1007
1034
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginBottom: "12px" }, children: [
1008
1035
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1009
1036
  "div",
1010
1037
  {
1011
1038
  style: {
1012
- display: "flex",
1013
- justifyContent: "space-between",
1014
- alignItems: "center",
1015
- marginBottom: "4px"
1039
+ marginBottom: "4px",
1040
+ color: themeVar("labelPrimary"),
1041
+ fontWeight: 500
1016
1042
  },
1017
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontWeight: 600, fontSize: "11px" }, children: `Recalled Memories (${recalledCount})` })
1043
+ children: `Recalled Memories (${recalledCount})`
1018
1044
  }
1019
1045
  ),
1020
1046
  memoryItems.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1021
1047
  "div",
1022
1048
  {
1023
1049
  "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
- },
1050
+ style: { ...mutedStyle, padding: "4px 0" },
1031
1051
  children: "No memories recalled in this session"
1032
1052
  }
1033
1053
  ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -1051,11 +1071,7 @@ function OpenVikingStatusPopover({
1051
1071
  display: "flex",
1052
1072
  alignItems: "center",
1053
1073
  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"
1074
+ minWidth: 0
1059
1075
  },
1060
1076
  children: [
1061
1077
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -1063,10 +1079,6 @@ function OpenVikingStatusPopover({
1063
1079
  {
1064
1080
  "data-testid": "memory-category-badge",
1065
1081
  style: {
1066
- fontSize: "9px",
1067
- fontWeight: 600,
1068
- padding: "1px 4px",
1069
- borderRadius: "3px",
1070
1082
  textTransform: "uppercase",
1071
1083
  flexShrink: 0,
1072
1084
  ...getCategoryBadgeStyle(item.category)
@@ -1078,21 +1090,7 @@ function OpenVikingStatusPopover({
1078
1090
  "span",
1079
1091
  {
1080
1092
  "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
- },
1093
+ style: { ...mutedStyle, flexShrink: 0 },
1096
1094
  children: item.source
1097
1095
  }
1098
1096
  ),
@@ -1101,12 +1099,10 @@ function OpenVikingStatusPopover({
1101
1099
  {
1102
1100
  "data-testid": "memory-leaf-name",
1103
1101
  style: {
1104
- flex: 1,
1105
1102
  overflow: "hidden",
1106
1103
  textOverflow: "ellipsis",
1107
1104
  whiteSpace: "nowrap",
1108
- fontFamily: "var(--dsw-font-mono, monospace)",
1109
- color: "var(--dsw-text-default, #f1f5f9)"
1105
+ minWidth: 0
1110
1106
  },
1111
1107
  children: formatMemoryLeafName(item.uri)
1112
1108
  }
@@ -1122,16 +1118,7 @@ function OpenVikingStatusPopover({
1122
1118
  "div",
1123
1119
  {
1124
1120
  "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
- },
1121
+ style: { marginBottom: "8px", color: themeVar("stateError") },
1135
1122
  children: commitError
1136
1123
  }
1137
1124
  ),
@@ -1140,19 +1127,17 @@ function OpenVikingStatusPopover({
1140
1127
  {
1141
1128
  type: "button",
1142
1129
  "data-testid": "commit-now-btn",
1143
- onClick: () => onCommitNow?.(),
1130
+ onClick: () => void onCommitNow?.(),
1144
1131
  disabled: isCommitDisabled,
1145
1132
  style: {
1146
1133
  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",
1134
+ padding: "6px 10px",
1135
+ borderRadius: "8px",
1136
+ border: `.5px solid ${themeVar("hairline")}`,
1137
+ background: isCommitDisabled ? "transparent" : themeVar("hoverBackground"),
1138
+ color: isCommitDisabled ? themeVar("labelTertiary") : themeVar("labelPrimary"),
1139
+ cursor: isCommitDisabled ? "default" : "pointer",
1140
+ font: "inherit",
1156
1141
  display: "flex",
1157
1142
  alignItems: "center",
1158
1143
  justifyContent: "center",
@@ -1162,12 +1147,13 @@ function OpenVikingStatusPopover({
1162
1147
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1163
1148
  "span",
1164
1149
  {
1150
+ "aria-hidden": "true",
1165
1151
  style: {
1166
1152
  display: "inline-block",
1167
1153
  width: "10px",
1168
1154
  height: "10px",
1169
1155
  borderRadius: "50%",
1170
- border: "2px solid var(--dsw-status-warning, #fbbf24)",
1156
+ border: `2px solid ${themeVar("stateWarning")}`,
1171
1157
  borderTopColor: "transparent",
1172
1158
  animation: "ov-spin 1s linear infinite"
1173
1159
  }
@@ -1199,11 +1185,15 @@ function formatTooltipTitle({
1199
1185
  isOnline,
1200
1186
  isCommitting = false,
1201
1187
  recalledCount,
1202
- pendingTokens
1188
+ pendingTokens,
1189
+ sessionUnreadable = false
1203
1190
  }) {
1204
1191
  if (!isOnline) {
1205
1192
  return "OpenViking: Offline";
1206
1193
  }
1194
+ if (sessionUnreadable) {
1195
+ return "OpenViking: session unreadable \u2014 the daemon requires an API key";
1196
+ }
1207
1197
  const countLabel = `${recalledCount} recalled`;
1208
1198
  const tokenLabel = `${(pendingTokens || 0).toLocaleString()} pending tokens`;
1209
1199
  if (isCommitting) {
@@ -1211,36 +1201,69 @@ function formatTooltipTitle({
1211
1201
  }
1212
1202
  return `OpenViking: Online (${countLabel}, ${tokenLabel})`;
1213
1203
  }
1214
- function getStatusIndicatorColor(isOnline, isCommitting = false) {
1204
+ function getStatusIndicatorColor(isOnline, isCommitting = false, sessionUnreadable = false) {
1215
1205
  if (!isOnline) {
1216
- return "var(--dsw-status-error, #f87171)";
1206
+ return themeVar("stateError");
1217
1207
  }
1218
- if (isCommitting) {
1219
- return "var(--dsw-status-warning, #fbbf24)";
1208
+ if (isCommitting || sessionUnreadable) {
1209
+ return themeVar("stateWarning");
1220
1210
  }
1221
- return "var(--dsw-status-success, #34d399)";
1211
+ return themeVar("stateSuccess");
1222
1212
  }
1223
- function getStatusGlow(isOnline, isCommitting = false) {
1224
- if (isOnline && !isCommitting) {
1225
- return "0 0 6px var(--dsw-status-success, #34d399)";
1226
- }
1227
- return "none";
1213
+ function chatNodesToText(nodes) {
1214
+ if (!nodes) return "";
1215
+ const source = Array.isArray(nodes) ? nodes : typeof nodes === "object" ? Object.values(nodes) : [];
1216
+ const parts = [];
1217
+ const visit = (value, depth = 0) => {
1218
+ if (depth > 4 || value === null || value === void 0) return;
1219
+ if (typeof value === "string") {
1220
+ parts.push(value);
1221
+ return;
1222
+ }
1223
+ if (Array.isArray(value)) {
1224
+ for (const item of value) visit(item, depth + 1);
1225
+ return;
1226
+ }
1227
+ if (typeof value === "object") {
1228
+ const record = value;
1229
+ for (const key of ["content", "text", "data", "body"]) {
1230
+ if (key in record) visit(record[key], depth + 1);
1231
+ }
1232
+ }
1233
+ };
1234
+ for (const node of source) visit(node);
1235
+ return parts.join("\n");
1228
1236
  }
1229
- function getFallbackSessionMessages(sessionId) {
1230
- if (!sessionId || typeof window === "undefined") {
1231
- return void 0;
1237
+ function OpenVikingStatusChip(props) {
1238
+ const { useChat, ...rest } = props;
1239
+ if (useChat && rest.contextText === void 0 && rest.messages === void 0) {
1240
+ 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
1241
  }
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;
1242
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StatusChipView, { ...rest });
1243
+ }
1244
+ var ChatReadBoundary = class extends import_react2.default.Component {
1245
+ state = { failed: false };
1246
+ static getDerivedStateFromError() {
1247
+ return { failed: true };
1237
1248
  }
1238
- if (win.__DSH_SESSION_MESSAGES__?.[sessionId]) {
1239
- return win.__DSH_SESSION_MESSAGES__[sessionId];
1249
+ render() {
1250
+ return this.state.failed ? this.props.fallback : this.props.children;
1240
1251
  }
1241
- return void 0;
1252
+ };
1253
+ function ChatBackedStatusChip({
1254
+ useChat,
1255
+ ...rest
1256
+ }) {
1257
+ const nodes = useChat((snapshot) => {
1258
+ try {
1259
+ return snapshot?.legacy?.nodes !== void 0 ? snapshot.legacy.nodes : snapshot?.nodes;
1260
+ } catch {
1261
+ return void 0;
1262
+ }
1263
+ });
1264
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StatusChipView, { ...rest, messages: nodes });
1242
1265
  }
1243
- function OpenVikingStatusChip({
1266
+ function StatusChipView({
1244
1267
  sessionId,
1245
1268
  messages,
1246
1269
  contextText,
@@ -1249,32 +1272,28 @@ function OpenVikingStatusChip({
1249
1272
  className,
1250
1273
  initialHealth,
1251
1274
  initialSessionData,
1275
+ initialSessionRead,
1252
1276
  initialOpen = false
1253
1277
  }) {
1254
1278
  const [health, setHealth] = (0, import_react2.useState)(
1255
1279
  initialHealth ?? null
1256
1280
  );
1257
- const [sessionData, setSessionData] = (0, import_react2.useState)(
1258
- initialSessionData ?? null
1281
+ const [sessionRead, setSessionRead] = (0, import_react2.useState)(
1282
+ initialSessionRead ?? (initialSessionData ? { status: "ok", session: initialSessionData } : null)
1259
1283
  );
1260
1284
  const [isOpen, setIsOpen] = (0, import_react2.useState)(initialOpen);
1261
1285
  const [isCommitting, setIsCommitting] = (0, import_react2.useState)(false);
1262
1286
  const [commitError, setCommitError] = (0, import_react2.useState)(null);
1287
+ const [isHovered, setIsHovered] = (0, import_react2.useState)(false);
1263
1288
  const popoverRef = (0, import_react2.useRef)(null);
1264
1289
  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];
1272
- }
1273
- return contextText ?? messages ?? fallbackMessages;
1274
- }, [contextText, messages, fallbackMessages]);
1290
+ const conversationText = (0, import_react2.useMemo)(() => {
1291
+ if (typeof contextText === "string") return contextText;
1292
+ return chatNodesToText(messages);
1293
+ }, [contextText, messages]);
1275
1294
  const recalledResult = (0, import_react2.useMemo)(
1276
- () => parseRecalledMemories(inputForParser),
1277
- [inputForParser]
1295
+ () => parseRecalledMemories(conversationText),
1296
+ [conversationText]
1278
1297
  );
1279
1298
  const fetchStatus = (0, import_react2.useCallback)(async () => {
1280
1299
  try {
@@ -1283,8 +1302,7 @@ function OpenVikingStatusChip({
1283
1302
  if (!healthRes.ok) {
1284
1303
  return;
1285
1304
  }
1286
- const session = await apiClient.fetchSession(sessionId);
1287
- setSessionData(session);
1305
+ setSessionRead(await apiClient.readSession(sessionId));
1288
1306
  } catch {
1289
1307
  setHealth({ ok: false });
1290
1308
  }
@@ -1337,26 +1355,28 @@ function OpenVikingStatusChip({
1337
1355
  }
1338
1356
  };
1339
1357
  const isOnline = health?.ok === true;
1358
+ const sessionData = sessionRead?.status === "ok" ? sessionRead.session : null;
1359
+ const sessionUnreadable = isOnline && sessionRead !== null && sessionRead.status !== "ok";
1340
1360
  const pendingTokens = sessionData?.pending_tokens ?? 0;
1341
- const pendingTokensK = Math.round(pendingTokens / 1e3);
1342
1361
  const recalledCount = recalledResult.recalledCount;
1343
- const statusColor = getStatusIndicatorColor(isOnline, isCommitting);
1344
- const statusGlow = getStatusGlow(isOnline, isCommitting);
1362
+ const statusColor = getStatusIndicatorColor(
1363
+ isOnline,
1364
+ isCommitting,
1365
+ sessionUnreadable
1366
+ );
1345
1367
  const tooltipTitle = formatTooltipTitle({
1346
1368
  isOnline,
1347
1369
  isCommitting,
1348
1370
  recalledCount,
1349
- pendingTokens
1371
+ pendingTokens,
1372
+ sessionUnreadable
1350
1373
  });
1374
+ const label = !isOnline ? "OV offline" : sessionUnreadable ? `OV: ${recalledCount} rec \xB7 no access` : `OV: ${recalledCount} rec \xB7 ${Math.round(pendingTokens / 1e3)}k pend`;
1351
1375
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1352
- "div",
1376
+ "span",
1353
1377
  {
1354
1378
  className,
1355
- style: {
1356
- position: "relative",
1357
- display: "inline-flex",
1358
- alignItems: "center"
1359
- },
1379
+ style: { minWidth: 0, display: "inline-flex", position: "relative" },
1360
1380
  ref: popoverRef,
1361
1381
  children: [
1362
1382
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
@@ -1364,23 +1384,28 @@ function OpenVikingStatusChip({
1364
1384
  {
1365
1385
  type: "button",
1366
1386
  onClick: () => setIsOpen(!isOpen),
1387
+ onMouseEnter: () => setIsHovered(true),
1388
+ onMouseLeave: () => setIsHovered(false),
1367
1389
  "aria-expanded": isOpen,
1368
1390
  "aria-haspopup": "dialog",
1369
1391
  style: {
1370
- display: "inline-flex",
1392
+ // Геометрия и типографика повторяют штатный чип статистики DSH:
1393
+ // прозрачный фон, без рамки, шрифт и кегль наследуются от строки.
1394
+ boxSizing: "border-box",
1395
+ maxWidth: "100%",
1396
+ color: themeVar("labelTertiary"),
1397
+ font: "inherit",
1398
+ fontVariantNumeric: "tabular-nums",
1399
+ lineHeight: "inherit",
1400
+ whiteSpace: "nowrap",
1401
+ background: isHovered ? themeVar("hoverBackground") : "transparent",
1402
+ border: "none",
1403
+ borderRadius: "24px",
1371
1404
  alignItems: "center",
1372
1405
  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"
1406
+ padding: "1px 8px",
1407
+ display: "inline-flex",
1408
+ cursor: "pointer"
1384
1409
  },
1385
1410
  title: tooltipTitle,
1386
1411
  "aria-label": tooltipTitle,
@@ -1389,25 +1414,17 @@ function OpenVikingStatusChip({
1389
1414
  "span",
1390
1415
  {
1391
1416
  "data-testid": "status-dot",
1417
+ "aria-hidden": "true",
1392
1418
  style: {
1393
- width: "7px",
1394
- height: "7px",
1419
+ width: "6px",
1420
+ height: "6px",
1395
1421
  borderRadius: "50%",
1396
- backgroundColor: statusColor,
1397
- boxShadow: statusGlow,
1422
+ background: statusColor,
1398
1423
  flexShrink: 0
1399
1424
  }
1400
1425
  }
1401
1426
  ),
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" })
1427
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { textOverflow: "ellipsis", minWidth: 0 }, children: label })
1411
1428
  ]
1412
1429
  }
1413
1430
  ),
@@ -1417,6 +1434,7 @@ function OpenVikingStatusChip({
1417
1434
  sessionId,
1418
1435
  health,
1419
1436
  sessionData,
1437
+ sessionRead,
1420
1438
  recalledResult,
1421
1439
  endpoint: apiClient.endpoint,
1422
1440
  isCommitting,
@@ -1436,10 +1454,10 @@ var inject = ["slots"];
1436
1454
  function apply(ctx) {
1437
1455
  ctx.effect(
1438
1456
  () => ctx.slots.inject(
1439
- "conversation.input.right",
1457
+ "conversation.composer.dock",
1440
1458
  () => ctx.slots.register(
1441
1459
  {
1442
- name: "conversation.input.right",
1460
+ name: "conversation.composer.dock",
1443
1461
  id: "openviking-status",
1444
1462
  order: 50,
1445
1463
  label: "OpenViking"
@@ -1447,7 +1465,7 @@ function apply(ctx) {
1447
1465
  OpenVikingStatusChip
1448
1466
  )
1449
1467
  ),
1450
- "openviking-status: composer chip"
1468
+ "openviking-status: composer stats chip"
1451
1469
  );
1452
1470
  }
1453
1471
  return module.exports;