opencode-subagent-magazine 1.6.0-beta.3 → 1.6.0-beta.4

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/dist/v2.js CHANGED
@@ -307,7 +307,8 @@ var SETTING_KEYS = {
307
307
  order: `${KV_PREFIX}.order`,
308
308
  scrollMode: `${KV_PREFIX}.scroll_mode`,
309
309
  open: `${KV_PREFIX}.open`,
310
- ttlDays: `${KV_PREFIX}.ttl_days`
310
+ ttlDays: `${KV_PREFIX}.ttl_days`,
311
+ border: `${KV_PREFIX}.border`
311
312
  };
312
313
  function loadSessionData(kv) {
313
314
  try {
@@ -329,7 +330,7 @@ function readTTLDays(kv) {
329
330
  }
330
331
 
331
332
  // src/_version.ts
332
- var PLUGIN_VERSION = "1.6.0-beta.3";
333
+ var PLUGIN_VERSION = "1.6.0-beta.4";
333
334
 
334
335
  // src/i18n.ts
335
336
  var ZH_T = {
@@ -381,7 +382,9 @@ var ZH_T = {
381
382
  "cancel.already_ended": "\u4F1A\u8BDD\u5DF2\u7ED3\u675F\uFF0C\u65E0\u9700\u53D6\u6D88",
382
383
  "cancel.status_error": "\u65E0\u6CD5\u67E5\u8BE2\u4F1A\u8BDD\u72B6\u6001",
383
384
  "cancel.sent": "\u5DF2\u53D1\u9001\u53D6\u6D88\u6307\u4EE4",
384
- "cancel.failed": "\u53D6\u6D88\u5931\u8D25"
385
+ "cancel.failed": "\u53D6\u6D88\u5931\u8D25",
386
+ "borderShown": "\u9762\u677F\u8FB9\u6846 \u5DF2\u663E\u793A",
387
+ "borderHidden": "\u9762\u677F\u8FB9\u6846 \u5DF2\u9690\u85CF"
385
388
  };
386
389
  var EN_T = {
387
390
  "panel.title": "SubAgent",
@@ -432,7 +435,9 @@ var EN_T = {
432
435
  "cancel.already_ended": "Session already ended, no need to cancel",
433
436
  "cancel.status_error": "Cannot query session status",
434
437
  "cancel.sent": "Cancel instruction sent",
435
- "cancel.failed": "Cancellation failed"
438
+ "cancel.failed": "Cancellation failed",
439
+ "borderShown": "Panel border shown",
440
+ "borderHidden": "Panel border hidden"
436
441
  };
437
442
  var JA_T = {
438
443
  "panel.title": "\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8",
@@ -483,7 +488,9 @@ var JA_T = {
483
488
  "cancel.already_ended": "\u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u65E2\u306B\u7D42\u4E86\u3057\u3066\u3044\u307E\u3059",
484
489
  "cancel.status_error": "\u30BB\u30C3\u30B7\u30E7\u30F3\u72B6\u614B\u3092\u7167\u4F1A\u3067\u304D\u307E\u305B\u3093",
485
490
  "cancel.sent": "\u30AD\u30E3\u30F3\u30BB\u30EB\u6307\u793A\u3092\u9001\u4FE1\u3057\u307E\u3057\u305F",
486
- "cancel.failed": "\u30AD\u30E3\u30F3\u30BB\u30EB\u306B\u5931\u6557\u3057\u307E\u3057\u305F"
491
+ "cancel.failed": "\u30AD\u30E3\u30F3\u30BB\u30EB\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
492
+ "borderShown": "\u30D1\u30CD\u30EB\u67A0\u7DDA \u8868\u793A",
493
+ "borderHidden": "\u30D1\u30CD\u30EB\u67A0\u7DDA \u975E\u8868\u793A"
487
494
  };
488
495
  var KO_T = {
489
496
  "panel.title": "\uC11C\uBE0C \uC5D0\uC774\uC804\uD2B8",
@@ -534,7 +541,9 @@ var KO_T = {
534
541
  "cancel.already_ended": "\uC138\uC158\uC774 \uC774\uBBF8 \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
535
542
  "cancel.status_error": "\uC138\uC158 \uC0C1\uD0DC\uB97C \uC870\uD68C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
536
543
  "cancel.sent": "\uCDE8\uC18C \uC9C0\uC2DC\uB97C \uBCF4\uB0C8\uC2B5\uB2C8\uB2E4",
537
- "cancel.failed": "\uCDE8\uC18C\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4"
544
+ "cancel.failed": "\uCDE8\uC18C\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
545
+ "borderShown": "\uD328\uB110 \uD14C\uB450\uB9AC \uD45C\uC2DC",
546
+ "borderHidden": "\uD328\uB110 \uD14C\uB450\uB9AC \uC228\uAE40"
538
547
  };
539
548
  var LANGS = { zh: ZH_T, en: EN_T, ja: JA_T, ko: KO_T };
540
549
  var LANG_META = [
@@ -809,6 +818,19 @@ function makeCommands(context, api, signals) {
809
818
  } catch {
810
819
  }
811
820
  }
821
+ },
822
+ {
823
+ id: "opencode-subagent-magazine.subagent.border",
824
+ title: "SubAgent Magazine: Border",
825
+ description: "Show or hide the panel border",
826
+ slash: { name: "subagent-border" },
827
+ palette: true,
828
+ run: () => {
829
+ const cur = Boolean(kv.get(SETTING_KEYS.border, false));
830
+ kv.set(SETTING_KEYS.border, !cur);
831
+ signals.setBorderVisible(!cur);
832
+ api.ui.toast(!cur ? t("borderShown") : t("borderHidden"));
833
+ }
812
834
  }
813
835
  ];
814
836
  }
@@ -832,9 +854,11 @@ import { insertNode as _$insertNode } from "@opentui/solid";
832
854
  import { createComponent as _$createComponent } from "@opentui/solid";
833
855
  import { effect as _$effect } from "@opentui/solid";
834
856
  import { insert as _$insert } from "@opentui/solid";
835
- import { memo as _$memo } from "@opentui/solid";
836
- import { use as _$use } from "@opentui/solid";
837
857
  import { setProp as _$setProp } from "@opentui/solid";
858
+ import { use as _$use } from "@opentui/solid";
859
+ import { spread as _$spread } from "@opentui/solid";
860
+ import { mergeProps as _$mergeProps } from "@opentui/solid";
861
+ import { memo as _$memo } from "@opentui/solid";
838
862
  import { createElement as _$createElement } from "@opentui/solid";
839
863
  import { createMemo, createSignal as createSignal2, createEffect, onMount, onCleanup, untrack, Show, For } from "solid-js";
840
864
 
@@ -2117,13 +2141,21 @@ function SubAgentPanel(props) {
2117
2141
  return next;
2118
2142
  });
2119
2143
  };
2120
- const sep = () => "\u2500".repeat(Math.max(1, panelWidth()));
2144
+ const gutter = createMemo(() => props.borderVisible() ? 6 : 0);
2145
+ const sep = () => "\u2500".repeat(Math.max(1, panelWidth() - gutter()));
2146
+ createEffect(() => {
2147
+ props.borderVisible();
2148
+ if (boxEl && typeof boxEl.width === "number" && boxEl.width > 0) {
2149
+ const w = Math.max(20, boxEl.width);
2150
+ setPanelWidth((prev) => prev === w ? prev : w);
2151
+ }
2152
+ });
2121
2153
  const expandedMaxLabelW = createMemo(() => {
2122
2154
  const labels = [t("agent.label"), t("status.label"), t("time.label"), t("tokens.label"), t("error.label"), t("cost.label"), t("model.label"), t("todo.label"), t("session.label")];
2123
2155
  return Math.max(...labels.map((l) => visualWidth(l + ": ")));
2124
2156
  });
2125
2157
  const expandedPad = (label) => Math.max(0, expandedMaxLabelW() - visualWidth(label + ": "));
2126
- const expandedValAvail = () => Math.max(6, panelWidth() - INDENT - expandedMaxLabelW());
2158
+ const expandedValAvail = () => Math.max(6, panelWidth() - gutter() - INDENT - expandedMaxLabelW());
2127
2159
  const summaryParts = createMemo(() => {
2128
2160
  if (!anyEntry()) return null;
2129
2161
  const dot = "\u25CF";
@@ -2152,7 +2184,7 @@ function SubAgentPanel(props) {
2152
2184
  if (!open()) return false;
2153
2185
  const icon = "\u25BC";
2154
2186
  const need = visualWidth(icon) + 1 + visualWidth(t("panel.title")) + versionW + summaryCols();
2155
- return need <= panelWidth();
2187
+ return need <= panelWidth() - gutter();
2156
2188
  });
2157
2189
  const leftCols = createMemo(() => {
2158
2190
  const icon = open() ? "\u25BC" : "\u25B6";
@@ -2162,25 +2194,36 @@ function SubAgentPanel(props) {
2162
2194
  });
2163
2195
  const spacerCols = createMemo(() => {
2164
2196
  if (!anyEntry()) return 0;
2165
- return Math.max(0, panelWidth() - leftCols() - summaryCols());
2197
+ return Math.max(0, panelWidth() - gutter() - leftCols() - summaryCols());
2166
2198
  });
2167
- const valueCols = (label) => Math.max(4, panelWidth() - INDENT - visualWidth(label + ": "));
2199
+ const valueCols = (label) => Math.max(4, panelWidth() - gutter() - INDENT - visualWidth(label + ": "));
2168
2200
  return (() => {
2169
2201
  var _el$ = _$createElement("box"), _el$2 = _$createElement("text"), _el$3 = _$createElement("span"), _el$4 = _$createElement("span");
2170
2202
  _$insertNode(_el$, _el$2);
2171
2203
  var _ref$ = boxEl;
2172
2204
  typeof _ref$ === "function" ? _$use(_ref$, _el$) : boxEl = _el$;
2173
- _$setProp(_el$, "border", false);
2174
- _$setProp(_el$, "paddingTop", 0);
2175
- _$setProp(_el$, "paddingBottom", 0);
2176
- _$setProp(_el$, "paddingLeft", 0);
2177
- _$setProp(_el$, "paddingRight", 0);
2178
- _$setProp(_el$, "flexDirection", "column");
2179
- _$setProp(_el$, "gap", 0);
2180
- _$setProp(_el$, "onSizeChange", () => {
2181
- const w = boxEl ? Math.max(20, boxEl.width ?? 0) : 28;
2182
- setPanelWidth((prev) => prev === w ? prev : w);
2183
- });
2205
+ _$spread(_el$, _$mergeProps({
2206
+ get border() {
2207
+ return props.borderVisible();
2208
+ }
2209
+ }, () => props.borderVisible() ? {
2210
+ borderColor: pal().border
2211
+ } : {}, {
2212
+ "paddingTop": 0,
2213
+ "paddingBottom": 0,
2214
+ get paddingLeft() {
2215
+ return props.borderVisible() ? 2 : 0;
2216
+ },
2217
+ get paddingRight() {
2218
+ return props.borderVisible() ? 2 : 0;
2219
+ },
2220
+ "flexDirection": "column",
2221
+ "gap": 0,
2222
+ "onSizeChange": () => {
2223
+ const w = boxEl ? Math.max(20, boxEl.width ?? 0) : 28;
2224
+ setPanelWidth((prev) => prev === w ? prev : w);
2225
+ }
2226
+ }), true);
2184
2227
  _$insertNode(_el$2, _el$3);
2185
2228
  _$insertNode(_el$2, _el$4);
2186
2229
  _$setProp(_el$2, "onMouseUp", () => {
@@ -2377,7 +2420,7 @@ function SubAgentPanel(props) {
2377
2420
  if (tk) w += visualWidth(tk);
2378
2421
  return w;
2379
2422
  };
2380
- const labelAvail = () => Math.max(6, panelWidth() - LEFT_PAD - suffixW());
2423
+ const labelAvail = () => Math.max(6, panelWidth() - gutter() - LEFT_PAD - suffixW());
2381
2424
  const labelText = () => {
2382
2425
  const max2 = labelAvail();
2383
2426
  const text = entry.title || entry.agent;
@@ -2774,7 +2817,7 @@ ${t("session.toast.copy_failed")}`, {
2774
2817
  const cancelLabel = () => ` ${t("cancel.label")}`;
2775
2818
  const dismissLabel = () => ` ${t("dismiss.label")}`;
2776
2819
  const rightW = (isRunning ? visualWidth(dismissLabel()) : 0) + (isRunning && entry.sessionId ? visualWidth(cancelLabel()) : 0);
2777
- const spacerW = () => Math.max(1, panelWidth() - openW() - rightW - 2);
2820
+ const spacerW = () => Math.max(1, panelWidth() - gutter() - openW() - rightW - 2);
2778
2821
  return (() => {
2779
2822
  var _el$91 = _$createElement("box"), _el$95 = _$createElement("text");
2780
2823
  _$insertNode(_el$91, _el$95);
@@ -2875,7 +2918,7 @@ ${t("session.toast.copy_failed")}`, {
2875
2918
  const showTop = props.sortOrder() === "desc" ? scrollOffset() > 0 : entryList().length > max() && clampedOffset() < entryList().length - max();
2876
2919
  const left = showMore ? ` \u2193 ${hiddenBelow()} ${t("scroll.more")}` : " ";
2877
2920
  const right = props.sortOrder() === "desc" ? `\u2191 ${t("scroll.top")}` : `\u2193 ${t("scroll.bottom")}`;
2878
- const pad = showTop ? Math.max(1, panelWidth() - visualWidth(left) - visualWidth(right)) : 0;
2921
+ const pad = showTop ? Math.max(1, panelWidth() - gutter() - visualWidth(left) - visualWidth(right)) : 0;
2879
2922
  return (() => {
2880
2923
  var _el$100 = _$createElement("box"), _el$101 = _$createElement("text"), _el$102 = _$createElement("span");
2881
2924
  _$insertNode(_el$100, _el$101);
@@ -2979,6 +3022,9 @@ function PluginRoot(props) {
2979
3022
  get scrollMode() {
2980
3023
  return props.signals.scrollMode;
2981
3024
  },
3025
+ get borderVisible() {
3026
+ return props.signals.borderVisible;
3027
+ },
2982
3028
  get sessionId() {
2983
3029
  return props.sessionID;
2984
3030
  }
@@ -3003,6 +3049,7 @@ var mod = {
3003
3049
  const [maxSignal, setMaxEntries] = createSignal3(Number(api.kv.get(SETTING_KEYS.maxEntries, "10")) || 10);
3004
3050
  const [orderSignal, setSortOrder] = createSignal3(String(api.kv.get(SETTING_KEYS.order, "desc")) === "asc" ? "asc" : "desc");
3005
3051
  const [scrollSignal, setScrollMode] = createSignal3(String(api.kv.get(SETTING_KEYS.scrollMode, "wheel")) === "click" ? "click" : "wheel");
3052
+ const [borderSignal, setBorderVisible] = createSignal3(api.kv.get(SETTING_KEYS.border, false) === true);
3006
3053
  lang = langSignal;
3007
3054
  maxEntries = maxSignal;
3008
3055
  sortOrder = orderSignal;
@@ -3016,6 +3063,8 @@ var mod = {
3016
3063
  setSortOrder,
3017
3064
  scrollMode,
3018
3065
  setScrollMode,
3066
+ borderVisible: borderSignal,
3067
+ setBorderVisible,
3019
3068
  sessionId: ""
3020
3069
  };
3021
3070
  context.ui.slot({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-subagent-magazine",
3
- "version": "1.6.0-beta.3",
3
+ "version": "1.6.0-beta.4",
4
4
  "description": "OpenCode TUI plugin monitoring sub-agent invocation status in real time",
5
5
  "type": "module",
6
6
  "types": "dist/index.d.ts",
package/src/_version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // auto-generated
2
- export const PLUGIN_VERSION="1.6.0-beta.3";
2
+ export const PLUGIN_VERSION="1.6.0-beta.4";
package/src/core/color.ts CHANGED
@@ -1,70 +1,70 @@
1
- /** Morandi palette helpers — desaturate any theme color toward a muted fallback. */
2
-
3
- export function rgb(raw: unknown): { r: number; g: number; b: number } | null {
4
- if (typeof raw === "string" && raw.startsWith("#")) {
5
- const h = raw.slice(1)
6
- return {
7
- r: parseInt(h.slice(0, 2), 16),
8
- g: parseInt(h.slice(2, 4), 16),
9
- b: parseInt(h.slice(4, 6), 16),
10
- }
11
- }
12
- if (raw && typeof raw === "object") {
13
- const o = raw as Record<string, unknown>
14
- if (typeof o.r === "number" && typeof o.g === "number" && typeof o.b === "number") {
15
- const scale = o.r > 1 || o.g > 1 || o.b > 1 ? 1 : 255
16
- return { r: Math.round(o.r * scale), g: Math.round(o.g * scale), b: Math.round(o.b * scale) }
17
- }
18
- }
19
- return null
20
- }
21
-
22
- export function saturation(r: number, g: number, b: number): number {
23
- const max = Math.max(r, g, b) / 255
24
- const min = Math.min(r, g, b) / 255
25
- const delta = max - min
26
- if (delta === 0) return 0
27
- const L = (max + min) / 2
28
- return L <= 0.5 ? delta / (max + min) : delta / (2 - max - min)
29
- }
30
-
31
- function desaturateTo(raw: unknown, maxSat: number, fallback: string): string {
32
- const c = rgb(raw)
33
- if (!c) return fallback
34
- const sat = saturation(c.r, c.g, c.b)
35
- if (sat <= maxSat) {
36
- return "#" + [c.r, c.g, c.b].map((v) => v.toString(16).padStart(2, "0")).join("")
37
- }
38
- const luma = c.r * 0.299 + c.g * 0.587 + c.b * 0.114
39
- let lo = 0, hi = 1
40
- for (let i = 0; i < 12; i++) {
41
- const mid = (lo + hi) / 2
42
- const nr = Math.round(c.r + (luma - c.r) * mid)
43
- const ng = Math.round(c.g + (luma - c.g) * mid)
44
- const nb = Math.round(c.b + (luma - c.b) * mid)
45
- if (saturation(nr, ng, nb) > maxSat) lo = mid
46
- else hi = mid
47
- }
48
- const nr = Math.round(c.r + (luma - c.r) * hi)
49
- const ng = Math.round(c.g + (luma - c.g) * hi)
50
- const nb = Math.round(c.b + (luma - c.b) * hi)
51
- return "#" + [nr, ng, nb].map((v) => Math.max(0, Math.min(255, v)).toString(16).padStart(2, "0")).join("")
52
- }
53
-
54
- function dimColor(hex: string, factor = 0.5): string {
55
- const c = rgb(hex)
56
- if (!c) return hex
57
- const r = Math.round(c.r * factor)
58
- const g = Math.round(c.g * factor)
59
- const b = Math.round(c.b * factor)
60
- return "#" + [r, g, b].map((v) => Math.max(0, Math.min(255, v)).toString(16).padStart(2, "0")).join("")
61
- }
62
-
63
- export const FALLBACK = {
64
- primary: "#8B9DAF", text: "#C5C5BB", muted: "#7A7A72",
65
- success: "#9CAF8B", warning: "#C5B88D", error: "#B08A8A", border: "#6B6B63",
66
- } as const
67
-
68
- export const MAX_SAT = 0.28
69
-
70
- export { desaturateTo, dimColor }
1
+ /** Morandi palette helpers — desaturate any theme color toward a muted fallback. */
2
+
3
+ export function rgb(raw: unknown): { r: number; g: number; b: number } | null {
4
+ if (typeof raw === "string" && raw.startsWith("#")) {
5
+ const h = raw.slice(1)
6
+ return {
7
+ r: parseInt(h.slice(0, 2), 16),
8
+ g: parseInt(h.slice(2, 4), 16),
9
+ b: parseInt(h.slice(4, 6), 16),
10
+ }
11
+ }
12
+ if (raw && typeof raw === "object") {
13
+ const o = raw as Record<string, unknown>
14
+ if (typeof o.r === "number" && typeof o.g === "number" && typeof o.b === "number") {
15
+ const scale = o.r > 1 || o.g > 1 || o.b > 1 ? 1 : 255
16
+ return { r: Math.round(o.r * scale), g: Math.round(o.g * scale), b: Math.round(o.b * scale) }
17
+ }
18
+ }
19
+ return null
20
+ }
21
+
22
+ export function saturation(r: number, g: number, b: number): number {
23
+ const max = Math.max(r, g, b) / 255
24
+ const min = Math.min(r, g, b) / 255
25
+ const delta = max - min
26
+ if (delta === 0) return 0
27
+ const L = (max + min) / 2
28
+ return L <= 0.5 ? delta / (max + min) : delta / (2 - max - min)
29
+ }
30
+
31
+ function desaturateTo(raw: unknown, maxSat: number, fallback: string): string {
32
+ const c = rgb(raw)
33
+ if (!c) return fallback
34
+ const sat = saturation(c.r, c.g, c.b)
35
+ if (sat <= maxSat) {
36
+ return "#" + [c.r, c.g, c.b].map((v) => v.toString(16).padStart(2, "0")).join("")
37
+ }
38
+ const luma = c.r * 0.299 + c.g * 0.587 + c.b * 0.114
39
+ let lo = 0, hi = 1
40
+ for (let i = 0; i < 12; i++) {
41
+ const mid = (lo + hi) / 2
42
+ const nr = Math.round(c.r + (luma - c.r) * mid)
43
+ const ng = Math.round(c.g + (luma - c.g) * mid)
44
+ const nb = Math.round(c.b + (luma - c.b) * mid)
45
+ if (saturation(nr, ng, nb) > maxSat) lo = mid
46
+ else hi = mid
47
+ }
48
+ const nr = Math.round(c.r + (luma - c.r) * hi)
49
+ const ng = Math.round(c.g + (luma - c.g) * hi)
50
+ const nb = Math.round(c.b + (luma - c.b) * hi)
51
+ return "#" + [nr, ng, nb].map((v) => Math.max(0, Math.min(255, v)).toString(16).padStart(2, "0")).join("")
52
+ }
53
+
54
+ function dimColor(hex: string, factor = 0.5): string {
55
+ const c = rgb(hex)
56
+ if (!c) return hex
57
+ const r = Math.round(c.r * factor)
58
+ const g = Math.round(c.g * factor)
59
+ const b = Math.round(c.b * factor)
60
+ return "#" + [r, g, b].map((v) => Math.max(0, Math.min(255, v)).toString(16).padStart(2, "0")).join("")
61
+ }
62
+
63
+ export const FALLBACK = {
64
+ primary: "#8B9DAF", text: "#C5C5BB", muted: "#7A7A72",
65
+ success: "#9CAF8B", warning: "#C5B88D", error: "#B08A8A", border: "#6B6B63",
66
+ } as const
67
+
68
+ export const MAX_SAT = 0.28
69
+
70
+ export { desaturateTo, dimColor }
@@ -1,61 +1,61 @@
1
- /** Visual width of a single character (CJK/wide chars count as 2). */
2
- function charColumns(c: string): number {
3
- const code = c.codePointAt(0) ?? 0
4
- if (code < 0x20) return 0
5
- if (code < 0x7f) return 1
6
- if (code < 0xa0) return 0
7
- if (
8
- (code >= 0x1100 && code <= 0x115f) ||
9
- (code >= 0x2e80 && code <= 0xa4cf) ||
10
- (code >= 0xac00 && code <= 0xd7a3) ||
11
- (code >= 0xf900 && code <= 0xfaff) ||
12
- (code >= 0xfe10 && code <= 0xfe6f) ||
13
- (code >= 0xff01 && code <= 0xff60) ||
14
- (code >= 0xffe0 && code <= 0xffe6) ||
15
- (code >= 0x1f300 && code <= 0x1f64f) ||
16
- (code >= 0x20000 && code <= 0x3fffd)
17
- )
18
- return 2
19
- return 1
20
- }
21
-
22
- export function visualWidth(s: string): number {
23
- let w = 0
24
- for (const c of s) w += charColumns(c)
25
- return w
26
- }
27
-
28
- export function truncate(text: string, maxCols: number): string {
29
- if (visualWidth(text) <= maxCols) return text
30
- let cols = 0
31
- let i = 0
32
- for (const c of text) {
33
- const w = charColumns(c)
34
- if (cols + w > maxCols - 1) break
35
- cols += w
36
- i += c.length
37
- }
38
- return text.slice(0, i) + "\u2026"
39
- }
40
-
41
- export function fmtDurationShort(ms: number, running: boolean): string {
42
- if (running && ms < 2000) return ""
43
- if (ms < 1000) return (ms / 1000).toFixed(2) + "s"
44
- if (ms < 60000) return (ms / 1000).toFixed(2) + "s"
45
- const m = Math.floor(ms / 60000)
46
- const s = Math.round((ms % 60000) / 1000)
47
- return `${m}m${s}s`
48
- }
49
-
50
- export function fmtTokens(n: number): string {
51
- if (n < 1000) return `${n}`
52
- if (n < 1000000) return `${(n / 1000).toFixed(1)}k`
53
- return `${(n / 1000000).toFixed(1)}M`
54
- }
55
-
56
- export function safeErrorMsg(err: unknown): string {
57
- if (!err) return ""
58
- if (typeof err === "string") return err
59
- if (typeof err === "object") return String((err as any).message || (err as any).code || "")
60
- return ""
61
- }
1
+ /** Visual width of a single character (CJK/wide chars count as 2). */
2
+ function charColumns(c: string): number {
3
+ const code = c.codePointAt(0) ?? 0
4
+ if (code < 0x20) return 0
5
+ if (code < 0x7f) return 1
6
+ if (code < 0xa0) return 0
7
+ if (
8
+ (code >= 0x1100 && code <= 0x115f) ||
9
+ (code >= 0x2e80 && code <= 0xa4cf) ||
10
+ (code >= 0xac00 && code <= 0xd7a3) ||
11
+ (code >= 0xf900 && code <= 0xfaff) ||
12
+ (code >= 0xfe10 && code <= 0xfe6f) ||
13
+ (code >= 0xff01 && code <= 0xff60) ||
14
+ (code >= 0xffe0 && code <= 0xffe6) ||
15
+ (code >= 0x1f300 && code <= 0x1f64f) ||
16
+ (code >= 0x20000 && code <= 0x3fffd)
17
+ )
18
+ return 2
19
+ return 1
20
+ }
21
+
22
+ export function visualWidth(s: string): number {
23
+ let w = 0
24
+ for (const c of s) w += charColumns(c)
25
+ return w
26
+ }
27
+
28
+ export function truncate(text: string, maxCols: number): string {
29
+ if (visualWidth(text) <= maxCols) return text
30
+ let cols = 0
31
+ let i = 0
32
+ for (const c of text) {
33
+ const w = charColumns(c)
34
+ if (cols + w > maxCols - 1) break
35
+ cols += w
36
+ i += c.length
37
+ }
38
+ return text.slice(0, i) + "\u2026"
39
+ }
40
+
41
+ export function fmtDurationShort(ms: number, running: boolean): string {
42
+ if (running && ms < 2000) return ""
43
+ if (ms < 1000) return (ms / 1000).toFixed(2) + "s"
44
+ if (ms < 60000) return (ms / 1000).toFixed(2) + "s"
45
+ const m = Math.floor(ms / 60000)
46
+ const s = Math.round((ms % 60000) / 1000)
47
+ return `${m}m${s}s`
48
+ }
49
+
50
+ export function fmtTokens(n: number): string {
51
+ if (n < 1000) return `${n}`
52
+ if (n < 1000000) return `${(n / 1000).toFixed(1)}k`
53
+ return `${(n / 1000000).toFixed(1)}M`
54
+ }
55
+
56
+ export function safeErrorMsg(err: unknown): string {
57
+ if (!err) return ""
58
+ if (typeof err === "string") return err
59
+ if (typeof err === "object") return String((err as any).message || (err as any).code || "")
60
+ return ""
61
+ }
package/src/core/index.ts CHANGED
@@ -1,6 +1,6 @@
1
- export * from "./types"
2
- export * from "./format"
3
- export * from "./color"
4
- export * from "./kv"
5
- export * from "./usage"
6
- export * from "./state-machine"
1
+ export * from "./types"
2
+ export * from "./format"
3
+ export * from "./color"
4
+ export * from "./kv"
5
+ export * from "./usage"
6
+ export * from "./state-machine"
package/src/core/kv.ts CHANGED
@@ -1,36 +1,37 @@
1
- import type { SessionRecord } from "./types"
2
-
3
- export const KV_PREFIX = "subagent_magazine"
4
- export const SESSION_DATA_KEY = `${KV_PREFIX}.session_data`
5
-
6
- /** Minimal KV surface used by the magazine's persistence layer. */
7
- export interface KVApi {
8
- get(key: string, fallback?: unknown): unknown
9
- set(key: string, value: unknown): void
10
- }
11
-
12
- /** Setting keys shared by V1/V2 (values are raw strings in KV). */
13
- export const SETTING_KEYS = {
14
- lang: `${KV_PREFIX}.lang`,
15
- maxEntries: `${KV_PREFIX}.max_entries`,
16
- order: `${KV_PREFIX}.order`,
17
- scrollMode: `${KV_PREFIX}.scroll_mode`,
18
- open: `${KV_PREFIX}.open`,
19
- ttlDays: `${KV_PREFIX}.ttl_days`,
20
- } as const
21
-
22
- export function loadSessionData(kv: KVApi): Record<string, SessionRecord> {
23
- try {
24
- const raw = kv.get(SESSION_DATA_KEY, "{}")
25
- return JSON.parse(String(raw))
26
- } catch { return {} }
27
- }
28
-
29
- export function saveSessionData(kv: KVApi, data: Record<string, SessionRecord>) {
30
- try { kv.set(SESSION_DATA_KEY, JSON.stringify(data)) } catch {}
31
- }
32
-
33
- export function readTTLDays(kv: KVApi): number {
34
- const ttlDaysRaw = parseInt(String(kv.get(SETTING_KEYS.ttlDays, "3")), 10)
35
- return Number.isNaN(ttlDaysRaw) ? 3 : ttlDaysRaw
36
- }
1
+ import type { SessionRecord } from "./types"
2
+
3
+ export const KV_PREFIX = "subagent_magazine"
4
+ export const SESSION_DATA_KEY = `${KV_PREFIX}.session_data`
5
+
6
+ /** Minimal KV surface used by the magazine's persistence layer. */
7
+ export interface KVApi {
8
+ get(key: string, fallback?: unknown): unknown
9
+ set(key: string, value: unknown): void
10
+ }
11
+
12
+ /** Setting keys shared by V1/V2 (values are raw strings in KV). */
13
+ export const SETTING_KEYS = {
14
+ lang: `${KV_PREFIX}.lang`,
15
+ maxEntries: `${KV_PREFIX}.max_entries`,
16
+ order: `${KV_PREFIX}.order`,
17
+ scrollMode: `${KV_PREFIX}.scroll_mode`,
18
+ open: `${KV_PREFIX}.open`,
19
+ ttlDays: `${KV_PREFIX}.ttl_days`,
20
+ border: `${KV_PREFIX}.border`,
21
+ } as const
22
+
23
+ export function loadSessionData(kv: KVApi): Record<string, SessionRecord> {
24
+ try {
25
+ const raw = kv.get(SESSION_DATA_KEY, "{}")
26
+ return JSON.parse(String(raw))
27
+ } catch { return {} }
28
+ }
29
+
30
+ export function saveSessionData(kv: KVApi, data: Record<string, SessionRecord>) {
31
+ try { kv.set(SESSION_DATA_KEY, JSON.stringify(data)) } catch {}
32
+ }
33
+
34
+ export function readTTLDays(kv: KVApi): number {
35
+ const ttlDaysRaw = parseInt(String(kv.get(SETTING_KEYS.ttlDays, "3")), 10)
36
+ return Number.isNaN(ttlDaysRaw) ? 3 : ttlDaysRaw
37
+ }