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/README.md +3 -1
- package/dist/_version.d.ts +1 -1
- package/dist/_version.js +1 -1
- package/dist/core/kv.d.ts +1 -0
- package/dist/core/kv.js +1 -0
- package/dist/core/types.d.ts +2 -0
- package/dist/i18n.d.ts +2 -0
- package/dist/i18n.js +8 -0
- package/dist/index.js +17 -2
- package/dist/panel/SubAgentPanel.d.ts +1 -0
- package/dist/panel/SubAgentPanel.js +21 -9
- package/dist/tui.js +80 -27
- package/dist/v2/commands.js +13 -0
- package/dist/v2/index.js +3 -2
- package/dist/v2.js +76 -27
- package/package.json +1 -1
- package/src/_version.ts +1 -1
- package/src/core/color.ts +70 -70
- package/src/core/format.ts +61 -61
- package/src/core/index.ts +6 -6
- package/src/core/kv.ts +37 -36
- package/src/core/state-machine.ts +46 -46
- package/src/core/types.ts +2 -0
- package/src/core/usage.ts +16 -16
- package/src/i18n.ts +8 -0
- package/src/index.tsx +472 -454
- package/src/panel/SubAgentPanel.tsx +27 -10
- package/src/panel/store.ts +8 -8
- package/src/v2/commands.ts +264 -251
- package/src/v2/index.tsx +102 -98
- package/src/v2/theme.ts +14 -14
- package/src/v2/types.ts +165 -165
- package/tui/index.js +11 -11
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.
|
|
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
|
|
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
|
-
_$
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
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
package/src/_version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// auto-generated
|
|
2
|
-
export const PLUGIN_VERSION="1.6.0-beta.
|
|
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 }
|
package/src/core/format.ts
CHANGED
|
@@ -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
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
+
}
|