opencode-subagent-magazine 1.6.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,225 @@
1
+ import { SETTING_KEYS, readTTLDays } from "../core/kv";
2
+ import { TIME_FORMATS, TIME_FORMAT_SAMPLES } from "../core/format";
3
+ import { LANG_META, createT } from "../i18n";
4
+ /** 与侧栏设置共用的 TTL 周期:3/7/14/30 天或无限期。 */
5
+ const TTL_VALUES = [3, 7, 14, 30, 0];
6
+ /** 链式原生对话框之间的延迟,让宿主能关闭上一个对话框。 */
7
+ const nextTick = () => new Promise((resolve) => setTimeout(resolve, 0));
8
+ /**
9
+ * 原生设置菜单(visual-cache 风格):用单个选择列表展示每项设置
10
+ * 及其当前值;选中某项会打开对应的编辑器,
11
+ * 随后菜单重新打开,直到用户关闭(Esc / Close)。
12
+ */
13
+ export function openSettingsMenu(context, api, signals) {
14
+ runSettingsMenu(context, api, signals).catch((err) => {
15
+ try {
16
+ context.ui.toast.show({ message: String(err), variant: "error" });
17
+ }
18
+ catch { }
19
+ });
20
+ }
21
+ async function runSettingsMenu(context, api, signals) {
22
+ const t = createT(() => signals.lang());
23
+ const kv = api.kv;
24
+ const onOff = (v) => (v ? t("settings.on") : t("settings.off"));
25
+ const langLabel = () => LANG_META.find((m) => m.code === signals.lang())?.label ?? signals.lang();
26
+ const ttlTitle = (d) => (d === 0 ? t("ttl.unlimited") : t(d === 3 ? "ttl.3d" : d === 7 ? "ttl.7d" : d === 14 ? "ttl.14d" : "ttl.30d"));
27
+ const ttlLabel = () => ttlTitle(readTTLDays(kv));
28
+ const orderLabel = () => (signals.sortOrder() === "desc" ? t("order.desc") : t("order.asc"));
29
+ const scrollLabel = () => (signals.scrollMode() === "wheel" ? t("scroll.wheel") : t("scroll.click"));
30
+ const toast = (item, value) => context.ui.toast.show({ message: t("settings.saved", { item, value }) });
31
+ while (true) {
32
+ const category = t("settings.section.general");
33
+ const choice = await context.ui.dialog.select({
34
+ title: t("settings.title"),
35
+ options: [
36
+ { title: t("settings.lang"), value: "lang", description: langLabel(), category },
37
+ { title: t("settings.max"), value: "max", description: String(signals.maxEntries()), category },
38
+ { title: t("settings.order"), value: "order", description: orderLabel(), category },
39
+ { title: t("settings.scroll"), value: "scroll", description: scrollLabel(), category },
40
+ { title: t("settings.ttl"), value: "ttl", description: ttlLabel(), category },
41
+ { title: t("settings.border"), value: "border", description: onOff(signals.borderVisible()), category },
42
+ { title: t("settings.showEntryCost"), value: "cost", description: onOff(signals.showEntryCost()), category },
43
+ { title: t("settings.showEntryTime"), value: "time", description: onOff(signals.showEntryTime()), category },
44
+ { title: t("settings.timeFormat"), value: "timefmt", description: TIME_FORMAT_SAMPLES[signals.timeFormat()], category },
45
+ { title: t("settings.showEntryTokens"), value: "tokens", description: onOff(signals.showEntryTokens()), category },
46
+ { title: t("settings.dbSync"), value: "dbsync", description: onOff(signals.dbSync()), category },
47
+ { title: t("settings.close"), value: "close" },
48
+ ],
49
+ });
50
+ if (choice === undefined || choice === "close")
51
+ return;
52
+ // ── 语言 ──
53
+ if (choice === "lang") {
54
+ const picked = await context.ui.dialog.select({
55
+ title: t("settings.lang"),
56
+ options: LANG_META.map((m) => ({ title: m.label, value: m.code })),
57
+ current: signals.lang(),
58
+ });
59
+ if (picked) {
60
+ signals.setLang(picked);
61
+ kv.set(SETTING_KEYS.lang, picked);
62
+ toast(t("settings.lang"), LANG_META.find((m) => m.code === picked)?.label ?? picked);
63
+ }
64
+ }
65
+ // ── 最大可见条目数 ──
66
+ if (choice === "max") {
67
+ const val = await context.ui.dialog.prompt({
68
+ title: t("settings.max"),
69
+ message: "1–50",
70
+ placeholder: String(signals.maxEntries()),
71
+ });
72
+ if (val !== undefined) {
73
+ const n = parseInt(val.trim(), 10);
74
+ if (Number.isNaN(n)) {
75
+ context.ui.toast.show({ message: t("settings.invalid"), variant: "warning" });
76
+ }
77
+ else {
78
+ const clamped = Math.max(1, Math.min(50, n));
79
+ signals.setMaxEntries(clamped);
80
+ kv.set(SETTING_KEYS.maxEntries, clamped);
81
+ toast(t("settings.max"), String(clamped));
82
+ }
83
+ }
84
+ }
85
+ // ── 排序方式 ──
86
+ if (choice === "order") {
87
+ const picked = await context.ui.dialog.select({
88
+ title: t("settings.order"),
89
+ options: [
90
+ { title: t("order.desc"), value: "desc" },
91
+ { title: t("order.asc"), value: "asc" },
92
+ ],
93
+ current: signals.sortOrder(),
94
+ });
95
+ if (picked) {
96
+ signals.setSortOrder(picked);
97
+ kv.set(SETTING_KEYS.order, picked);
98
+ toast(t("settings.order"), picked === "desc" ? t("order.desc") : t("order.asc"));
99
+ }
100
+ }
101
+ // ── 滚动模式 ──
102
+ if (choice === "scroll") {
103
+ const picked = await context.ui.dialog.select({
104
+ title: t("settings.scroll"),
105
+ options: [
106
+ { title: t("scroll.wheel"), value: "wheel" },
107
+ { title: t("scroll.click"), value: "click" },
108
+ ],
109
+ current: signals.scrollMode(),
110
+ });
111
+ if (picked) {
112
+ signals.setScrollMode(picked);
113
+ kv.set(SETTING_KEYS.scrollMode, picked);
114
+ toast(t("settings.scroll"), picked === "wheel" ? t("scroll.wheel") : t("scroll.click"));
115
+ }
116
+ }
117
+ // ── TTL / 保留期 ──
118
+ if (choice === "ttl") {
119
+ const picked = await context.ui.dialog.select({
120
+ title: t("settings.ttl"),
121
+ options: TTL_VALUES.map((d) => ({ title: ttlTitle(d), value: d })),
122
+ current: readTTLDays(kv),
123
+ });
124
+ if (picked !== undefined) {
125
+ kv.set(SETTING_KEYS.ttlDays, String(picked));
126
+ toast(t("settings.ttl"), ttlTitle(picked));
127
+ }
128
+ }
129
+ // ── 边框 ──
130
+ if (choice === "border") {
131
+ const picked = await context.ui.dialog.select({
132
+ title: t("settings.border"),
133
+ options: [
134
+ { title: t("settings.on"), value: true },
135
+ { title: t("settings.off"), value: false },
136
+ ],
137
+ current: signals.borderVisible(),
138
+ });
139
+ if (picked !== undefined) {
140
+ signals.setBorderVisible(picked);
141
+ kv.set(SETTING_KEYS.border, picked);
142
+ toast(t("settings.border"), onOff(picked));
143
+ }
144
+ }
145
+ // ── 单条目费用 ──
146
+ if (choice === "cost") {
147
+ const picked = await context.ui.dialog.select({
148
+ title: t("settings.showEntryCost"),
149
+ options: [
150
+ { title: t("settings.on"), value: true },
151
+ { title: t("settings.off"), value: false },
152
+ ],
153
+ current: signals.showEntryCost(),
154
+ });
155
+ if (picked !== undefined) {
156
+ signals.setShowEntryCost(picked);
157
+ kv.set(SETTING_KEYS.showEntryCost, picked);
158
+ toast(t("settings.showEntryCost"), onOff(picked));
159
+ }
160
+ }
161
+ // ── 单条目时间 ──
162
+ if (choice === "time") {
163
+ const picked = await context.ui.dialog.select({
164
+ title: t("settings.showEntryTime"),
165
+ options: [
166
+ { title: t("settings.on"), value: true },
167
+ { title: t("settings.off"), value: false },
168
+ ],
169
+ current: signals.showEntryTime(),
170
+ });
171
+ if (picked !== undefined) {
172
+ signals.setShowEntryTime(picked);
173
+ kv.set(SETTING_KEYS.showEntryTime, picked);
174
+ toast(t("settings.showEntryTime"), onOff(picked));
175
+ }
176
+ }
177
+ // ── 时间格式 ──
178
+ if (choice === "timefmt") {
179
+ const picked = await context.ui.dialog.select({
180
+ title: t("settings.timeFormat"),
181
+ options: TIME_FORMATS.map((f) => ({ title: `${f} — ${TIME_FORMAT_SAMPLES[f]}`, value: f })),
182
+ current: signals.timeFormat(),
183
+ });
184
+ if (picked) {
185
+ signals.setTimeFormat(picked);
186
+ kv.set(SETTING_KEYS.timeFormat, picked);
187
+ toast(t("settings.timeFormat"), TIME_FORMAT_SAMPLES[picked]);
188
+ }
189
+ }
190
+ // ── 单条目 token 数 ──
191
+ if (choice === "tokens") {
192
+ const picked = await context.ui.dialog.select({
193
+ title: t("settings.showEntryTokens"),
194
+ options: [
195
+ { title: t("settings.on"), value: true },
196
+ { title: t("settings.off"), value: false },
197
+ ],
198
+ current: signals.showEntryTokens(),
199
+ });
200
+ if (picked !== undefined) {
201
+ signals.setShowEntryTokens(picked);
202
+ kv.set(SETTING_KEYS.showEntryTokens, picked);
203
+ toast(t("settings.showEntryTokens"), onOff(picked));
204
+ }
205
+ }
206
+ // ── 本地数据库补全 ──
207
+ if (choice === "dbsync") {
208
+ const picked = await context.ui.dialog.select({
209
+ title: t("settings.dbSync"),
210
+ options: [
211
+ { title: t("settings.on"), value: true },
212
+ { title: t("settings.off"), value: false },
213
+ ],
214
+ current: signals.dbSync(),
215
+ });
216
+ if (picked !== undefined) {
217
+ signals.setDbSync(picked);
218
+ kv.set(SETTING_KEYS.dbSync, picked);
219
+ toast(t("settings.dbSync"), onOff(picked));
220
+ }
221
+ }
222
+ // 让宿主先关闭编辑器,再重新打开菜单。
223
+ await nextTick();
224
+ }
225
+ }