opencode-subagent-magazine 1.7.0 → 1.7.1
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/_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 +8 -0
- package/dist/core/types.js +7 -0
- package/dist/i18n.d.ts +5 -0
- package/dist/i18n.js +20 -0
- package/dist/index.js +19 -2
- package/dist/panel/SubAgentPanel.d.ts +1 -0
- package/dist/panel/SubAgentPanel.js +80 -8
- package/dist/panel/entry-map.d.ts +1 -0
- package/dist/panel/entry-map.js +1 -1
- package/dist/panel/panel-api.d.ts +16 -0
- package/dist/tui.js +385 -218
- package/dist/v2/commands.js +14 -0
- package/dist/v2/db.d.ts +14 -1
- package/dist/v2/db.js +78 -4
- package/dist/v2/index.js +3 -1
- package/dist/v2/settings-menu.js +17 -0
- package/dist/v2/v2-panel-api.js +13 -2
- package/dist/v2.js +473 -223
- package/package.json +1 -1
- package/src/_version.ts +1 -1
- package/src/core/kv.ts +1 -0
- package/src/core/types.ts +13 -0
- package/src/i18n.ts +20 -0
- package/src/index.tsx +21 -1
- package/src/panel/SubAgentPanel.tsx +82 -7
- package/src/panel/entry-map.ts +2 -1
- package/src/panel/panel-api.ts +17 -0
- package/src/v2/commands.ts +14 -0
- package/src/v2/db.ts +76 -4
- package/src/v2/index.tsx +5 -0
- package/src/v2/settings-menu.ts +18 -0
- package/src/v2/v2-panel-api.ts +7 -2
package/dist/v2/commands.js
CHANGED
|
@@ -121,6 +121,7 @@ export function makeCommands(context, api, signals) {
|
|
|
121
121
|
`${t("settings.showEntryCost")}: ${onOff(signals.showEntryCost())}`,
|
|
122
122
|
`${t("settings.showEntryTime")}: ${onOff(signals.showEntryTime())}`,
|
|
123
123
|
`${t("settings.showEntryTokens")}: ${onOff(signals.showEntryTokens())}`,
|
|
124
|
+
`${t("settings.showOrigin")}: ${onOff(signals.showOrigin())}`,
|
|
124
125
|
`${t("settings.timeFormat")}: ${TIME_FORMAT_SAMPLES[signals.timeFormat()]}`,
|
|
125
126
|
`${t("settings.dbSync")}: ${onOff(signals.dbSync())}`,
|
|
126
127
|
].join("\n");
|
|
@@ -195,6 +196,19 @@ export function makeCommands(context, api, signals) {
|
|
|
195
196
|
api.ui.toast(`${t("settings.showEntryTokens")}: ${onOff(v)}`);
|
|
196
197
|
},
|
|
197
198
|
},
|
|
199
|
+
{
|
|
200
|
+
id: "opencode-subagent-magazine.subagent.origin",
|
|
201
|
+
title: "SubAgent Magazine: Toggle Origin Indicators",
|
|
202
|
+
description: "Show or hide origin markers (⇢ background / ↳ spawned) in the sidebar list",
|
|
203
|
+
slash: { name: "subagent-origin" },
|
|
204
|
+
palette: true,
|
|
205
|
+
run: () => {
|
|
206
|
+
const v = !signals.showOrigin();
|
|
207
|
+
signals.setShowOrigin(v);
|
|
208
|
+
kv.set(SETTING_KEYS.showOrigin, v);
|
|
209
|
+
api.ui.toast(`${t("settings.showOrigin")}: ${onOff(v)}`);
|
|
210
|
+
},
|
|
211
|
+
},
|
|
198
212
|
{
|
|
199
213
|
id: "opencode-subagent-magazine.subagent.db",
|
|
200
214
|
title: "SubAgent Magazine: Toggle Local DB",
|
package/dist/v2/db.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ export interface ChildSessionInfo {
|
|
|
22
22
|
timeCreated?: number;
|
|
23
23
|
timeIdle?: number;
|
|
24
24
|
idleOutcome?: string;
|
|
25
|
+
/** 仍在运行:最后一条消息晚于 time_idle(会话被恢复时 time_idle 不会被清空)。 */
|
|
26
|
+
active?: boolean;
|
|
25
27
|
}
|
|
26
28
|
export interface SessionDbIndex {
|
|
27
29
|
/** 数据库可用且设置项开启时为 true。 */
|
|
@@ -32,6 +34,8 @@ export interface SessionDbIndex {
|
|
|
32
34
|
matchChild(parentId: string, agent: string | undefined, startedAt: number | undefined): string | undefined;
|
|
33
35
|
/** 子会话汇总(model/tokens/cost/time_idle)。 */
|
|
34
36
|
info(sid: string): ChildSessionInfo | undefined;
|
|
37
|
+
/** 该会话派生的子会话(spawn),用于补全没有工具条目的衍生会话。 */
|
|
38
|
+
children(parentId: string): ChildSessionInfo[] | undefined;
|
|
35
39
|
}
|
|
36
40
|
/** 从宿主 model 字段提取模型 id。
|
|
37
41
|
* 兼容三种形态:名字字符串、{ id } 对象,以及序列化后的 JSON 字符串
|
|
@@ -39,7 +43,16 @@ export interface SessionDbIndex {
|
|
|
39
43
|
export declare function modelIdOf(value: unknown): string | undefined;
|
|
40
44
|
/** 宿主 readSessionTokens 同语义:最后一条 assistant 消息的上下文规模。 */
|
|
41
45
|
export declare function contextTokens(tokens: unknown): number | undefined;
|
|
42
|
-
/**
|
|
46
|
+
/** 子会话是否仍在运行。
|
|
47
|
+
* 恢复过的会话会保留旧的 time_idle(host 不清空),因此以
|
|
48
|
+
* 「最后一条消息是否晚于 time_idle」为准;最后一条是 idle
|
|
49
|
+
* 消息则视为已停止。 */
|
|
50
|
+
export declare function isSessionActive(input: {
|
|
51
|
+
timeIdle?: number;
|
|
52
|
+
lastMessageType?: string;
|
|
53
|
+
lastMessageAt?: number;
|
|
54
|
+
}): boolean;
|
|
55
|
+
/** 子会话终态:time_idle 存在且没有恢复活动才算结束;interrupted 视为取消。
|
|
43
56
|
* 没有 time_idle 时不作判断(交给宿主实时状态)——避免时间阈值猜测。 */
|
|
44
57
|
export declare function statusOfChild(info: ChildSessionInfo | undefined): "done" | "cancelled" | undefined;
|
|
45
58
|
/** 定位宿主数据库(可用 OPENCODE_DB 覆盖,遵循 XDG_DATA_HOME)。 */
|
package/dist/v2/db.js
CHANGED
|
@@ -58,10 +58,28 @@ export function contextTokens(tokens) {
|
|
|
58
58
|
(Number(cache.write) || 0);
|
|
59
59
|
return sum > 0 ? sum : undefined;
|
|
60
60
|
}
|
|
61
|
-
/**
|
|
61
|
+
/** 子会话是否仍在运行。
|
|
62
|
+
* 恢复过的会话会保留旧的 time_idle(host 不清空),因此以
|
|
63
|
+
* 「最后一条消息是否晚于 time_idle」为准;最后一条是 idle
|
|
64
|
+
* 消息则视为已停止。 */
|
|
65
|
+
export function isSessionActive(input) {
|
|
66
|
+
const { timeIdle, lastMessageType, lastMessageAt } = input;
|
|
67
|
+
if (lastMessageType === "idle")
|
|
68
|
+
return false;
|
|
69
|
+
if (lastMessageAt === undefined)
|
|
70
|
+
return false;
|
|
71
|
+
if (timeIdle === undefined)
|
|
72
|
+
return true;
|
|
73
|
+
return lastMessageAt > timeIdle;
|
|
74
|
+
}
|
|
75
|
+
/** 子会话终态:time_idle 存在且没有恢复活动才算结束;interrupted 视为取消。
|
|
62
76
|
* 没有 time_idle 时不作判断(交给宿主实时状态)——避免时间阈值猜测。 */
|
|
63
77
|
export function statusOfChild(info) {
|
|
64
|
-
if (!info
|
|
78
|
+
if (!info)
|
|
79
|
+
return undefined;
|
|
80
|
+
if (info.active === true)
|
|
81
|
+
return undefined;
|
|
82
|
+
if (info.timeIdle == null)
|
|
65
83
|
return undefined;
|
|
66
84
|
return info.idleOutcome === "interrupted" ? "cancelled" : "done";
|
|
67
85
|
}
|
|
@@ -104,11 +122,19 @@ const LAST_ASSISTANT_SQL = `
|
|
|
104
122
|
FROM session_message
|
|
105
123
|
WHERE session_id = ? AND type = 'assistant' AND json_extract(data, '$.tokens.output') > 0
|
|
106
124
|
ORDER BY seq DESC LIMIT 1`;
|
|
125
|
+
const LAST_MESSAGE_SQL = `
|
|
126
|
+
SELECT type, COALESCE(time_updated, time_created) AS at
|
|
127
|
+
FROM session_message WHERE session_id = ? ORDER BY seq DESC LIMIT 1`;
|
|
107
128
|
const ASSISTANT_COST_SQL = `
|
|
108
129
|
SELECT COUNT(*) AS n, SUM(COALESCE(json_extract(data, '$.cost'), 0)) AS total
|
|
109
130
|
FROM session_message WHERE session_id = ? AND type = 'assistant'`;
|
|
110
131
|
const CHILDREN_SQL = `
|
|
111
|
-
SELECT id, agent,
|
|
132
|
+
SELECT id, agent, title, model, cost,
|
|
133
|
+
tokens_input, tokens_output, tokens_reasoning, tokens_cache_read, tokens_cache_write,
|
|
134
|
+
time_created, time_idle, idle_outcome
|
|
135
|
+
FROM session_v2
|
|
136
|
+
WHERE parent_id = ? AND (fork_session_id IS NULL OR fork_session_id = '')
|
|
137
|
+
ORDER BY time_created`;
|
|
112
138
|
const num = (v) => {
|
|
113
139
|
const n = Number(v);
|
|
114
140
|
return Number.isFinite(n) ? n : undefined;
|
|
@@ -225,6 +251,7 @@ export function createSessionDbIndex(enabled) {
|
|
|
225
251
|
const row = db.query(CHILD_INFO_SQL).get(sid);
|
|
226
252
|
if (row) {
|
|
227
253
|
const last = db.query(LAST_ASSISTANT_SQL).get(sid);
|
|
254
|
+
const lastMsg = db.query(LAST_MESSAGE_SQL).get(sid);
|
|
228
255
|
let tokens = contextTokens(last ? {
|
|
229
256
|
input: last.input, output: last.output, reasoning: last.reasoning,
|
|
230
257
|
cache: { read: last.cache_read, write: last.cache_write },
|
|
@@ -254,6 +281,11 @@ export function createSessionDbIndex(enabled) {
|
|
|
254
281
|
timeCreated: num(row.time_created),
|
|
255
282
|
timeIdle: num(row.time_idle),
|
|
256
283
|
idleOutcome: str(row.idle_outcome),
|
|
284
|
+
active: isSessionActive({
|
|
285
|
+
timeIdle: num(row.time_idle),
|
|
286
|
+
lastMessageType: str(lastMsg?.type),
|
|
287
|
+
lastMessageAt: num(lastMsg?.at),
|
|
288
|
+
}),
|
|
257
289
|
};
|
|
258
290
|
}
|
|
259
291
|
}
|
|
@@ -263,5 +295,47 @@ export function createSessionDbIndex(enabled) {
|
|
|
263
295
|
infos.set(sid, { info: result, at: Date.now() });
|
|
264
296
|
return result;
|
|
265
297
|
};
|
|
266
|
-
|
|
298
|
+
const children = (parentId) => {
|
|
299
|
+
if (!ready() || !parentId)
|
|
300
|
+
return undefined;
|
|
301
|
+
const now = Date.now();
|
|
302
|
+
let cached = childrenCache.get(parentId);
|
|
303
|
+
if (!cached || now - cached.at > CALL_MAP_TTL_MS) {
|
|
304
|
+
let list = [];
|
|
305
|
+
try {
|
|
306
|
+
list = db.query(CHILDREN_SQL).all(parentId);
|
|
307
|
+
}
|
|
308
|
+
catch { }
|
|
309
|
+
cached = { list, at: now };
|
|
310
|
+
childrenCache.set(parentId, cached);
|
|
311
|
+
}
|
|
312
|
+
const out = [];
|
|
313
|
+
for (const row of cached.list) {
|
|
314
|
+
const sid = str(row.id);
|
|
315
|
+
if (!sid)
|
|
316
|
+
continue;
|
|
317
|
+
const full = info(sid);
|
|
318
|
+
if (full) {
|
|
319
|
+
out.push(full);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
// info 缓存未命中时不丢字段:用列表行自身的汇总兜底。
|
|
323
|
+
const agg = (num(row.tokens_input) || 0) + (num(row.tokens_output) || 0) + (num(row.tokens_reasoning) || 0) +
|
|
324
|
+
(num(row.tokens_cache_read) || 0) + (num(row.tokens_cache_write) || 0);
|
|
325
|
+
out.push({
|
|
326
|
+
id: sid,
|
|
327
|
+
parentId,
|
|
328
|
+
agent: str(row.agent),
|
|
329
|
+
title: str(row.title),
|
|
330
|
+
model: modelIdOf(row.model),
|
|
331
|
+
cost: num(row.cost),
|
|
332
|
+
tokens: agg > 0 ? agg : undefined,
|
|
333
|
+
timeCreated: num(row.time_created),
|
|
334
|
+
timeIdle: num(row.time_idle),
|
|
335
|
+
idleOutcome: str(row.idle_outcome),
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
return out;
|
|
339
|
+
};
|
|
340
|
+
return { enabled: ready, resolveCall, matchChild, info, children };
|
|
267
341
|
}
|
package/dist/v2/index.js
CHANGED
|
@@ -18,7 +18,7 @@ function CommandRoot(props) {
|
|
|
18
18
|
}
|
|
19
19
|
/** 面板根组件:渲染共享 SubAgentPanel(命令层见 CommandRoot)。 */
|
|
20
20
|
function PluginRoot(props) {
|
|
21
|
-
return (_jsx(SubAgentPanel, { api: props.api, theme: mapTheme(props.context.theme), lang: props.signals.lang, maxEntries: props.signals.maxEntries, sortOrder: props.signals.sortOrder, scrollMode: props.signals.scrollMode, borderVisible: props.signals.borderVisible, showEntryCost: props.signals.showEntryCost, showEntryTime: props.signals.showEntryTime, showEntryTokens: props.signals.showEntryTokens, timeFormat: props.signals.timeFormat, sessionId: props.sessionID }));
|
|
21
|
+
return (_jsx(SubAgentPanel, { api: props.api, theme: mapTheme(props.context.theme), lang: props.signals.lang, maxEntries: props.signals.maxEntries, sortOrder: props.signals.sortOrder, scrollMode: props.signals.scrollMode, borderVisible: props.signals.borderVisible, showEntryCost: props.signals.showEntryCost, showEntryTime: props.signals.showEntryTime, showEntryTokens: props.signals.showEntryTokens, timeFormat: props.signals.timeFormat, showOrigin: props.signals.showOrigin, sessionId: props.sessionID }));
|
|
22
22
|
}
|
|
23
23
|
/** V2 入口:setup 创建共享信号 + PanelApi + 命令 layer + 侧边栏槽位。
|
|
24
24
|
* 行为对齐 V1 tui()(信号初始值从 KV 恢复;侧边栏渲染共享 SubAgentPanel)。 */
|
|
@@ -49,6 +49,7 @@ const mod = {
|
|
|
49
49
|
const [showEntryTokensSignal, setShowEntryTokens] = createSignal(api.kv.get(SETTING_KEYS.showEntryTokens, true) !== false);
|
|
50
50
|
const storedTimeFormat = String(api.kv.get(SETTING_KEYS.timeFormat, "short"));
|
|
51
51
|
const [timeFormatSignal, setTimeFormat] = createSignal(TIME_FORMATS.includes(storedTimeFormat) ? storedTimeFormat : "short");
|
|
52
|
+
const [showOriginSignal, setShowOrigin] = createSignal(api.kv.get(SETTING_KEYS.showOrigin, false) === true);
|
|
52
53
|
const [dbSyncSignal, setDbSync] = createSignal(api.kv.get(SETTING_KEYS.dbSync, true) !== false);
|
|
53
54
|
lang = langSignal;
|
|
54
55
|
maxEntries = maxSignal;
|
|
@@ -60,6 +61,7 @@ const mod = {
|
|
|
60
61
|
showEntryTime: showEntryTimeSignal, setShowEntryTime,
|
|
61
62
|
showEntryTokens: showEntryTokensSignal, setShowEntryTokens,
|
|
62
63
|
timeFormat: timeFormatSignal, setTimeFormat,
|
|
64
|
+
showOrigin: showOriginSignal, setShowOrigin,
|
|
63
65
|
dbSync: dbSyncSignal, setDbSync,
|
|
64
66
|
sessionId: "",
|
|
65
67
|
};
|
package/dist/v2/settings-menu.js
CHANGED
|
@@ -43,6 +43,7 @@ async function runSettingsMenu(context, api, signals) {
|
|
|
43
43
|
{ title: t("settings.showEntryTime"), value: "time", description: onOff(signals.showEntryTime()), category },
|
|
44
44
|
{ title: t("settings.timeFormat"), value: "timefmt", description: TIME_FORMAT_SAMPLES[signals.timeFormat()], category },
|
|
45
45
|
{ title: t("settings.showEntryTokens"), value: "tokens", description: onOff(signals.showEntryTokens()), category },
|
|
46
|
+
{ title: t("settings.showOrigin"), value: "origin", description: onOff(signals.showOrigin()), category },
|
|
46
47
|
{ title: t("settings.dbSync"), value: "dbsync", description: onOff(signals.dbSync()), category },
|
|
47
48
|
{ title: t("settings.close"), value: "close" },
|
|
48
49
|
],
|
|
@@ -203,6 +204,22 @@ async function runSettingsMenu(context, api, signals) {
|
|
|
203
204
|
toast(t("settings.showEntryTokens"), onOff(picked));
|
|
204
205
|
}
|
|
205
206
|
}
|
|
207
|
+
// ── Origin indicators ──
|
|
208
|
+
if (choice === "origin") {
|
|
209
|
+
const picked = await context.ui.dialog.select({
|
|
210
|
+
title: t("settings.showOrigin"),
|
|
211
|
+
options: [
|
|
212
|
+
{ title: t("settings.on"), value: true },
|
|
213
|
+
{ title: t("settings.off"), value: false },
|
|
214
|
+
],
|
|
215
|
+
current: signals.showOrigin(),
|
|
216
|
+
});
|
|
217
|
+
if (picked !== undefined) {
|
|
218
|
+
signals.setShowOrigin(picked);
|
|
219
|
+
kv.set(SETTING_KEYS.showOrigin, picked);
|
|
220
|
+
toast(t("settings.showOrigin"), onOff(picked));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
206
223
|
// ── 本地数据库补全 ──
|
|
207
224
|
if (choice === "dbsync") {
|
|
208
225
|
const picked = await context.ui.dialog.select({
|
package/dist/v2/v2-panel-api.js
CHANGED
|
@@ -502,9 +502,12 @@ export function createPanelApi(context, settings) {
|
|
|
502
502
|
}
|
|
503
503
|
catch { }
|
|
504
504
|
// 宿主没有该子会话的实时状态(未加载缓存)时用本地库终态兜底:
|
|
505
|
-
// time_idle
|
|
505
|
+
// time_idle 存在且没有恢复活动即已结束;仍在运行则回报 busy。
|
|
506
506
|
try {
|
|
507
|
-
|
|
507
|
+
const info = dbIndex?.info(sid);
|
|
508
|
+
if (info?.active === true)
|
|
509
|
+
return { type: "busy" };
|
|
510
|
+
if (statusOfChild(info) !== undefined)
|
|
508
511
|
return { type: "idle" };
|
|
509
512
|
}
|
|
510
513
|
catch { }
|
|
@@ -539,6 +542,14 @@ export function createPanelApi(context, settings) {
|
|
|
539
542
|
return undefined;
|
|
540
543
|
}
|
|
541
544
|
},
|
|
545
|
+
listChildren: (parentId) => {
|
|
546
|
+
try {
|
|
547
|
+
return dbIndex?.enabled() ? dbIndex.children(parentId) : undefined;
|
|
548
|
+
}
|
|
549
|
+
catch {
|
|
550
|
+
return undefined;
|
|
551
|
+
}
|
|
552
|
+
},
|
|
542
553
|
},
|
|
543
554
|
event: {
|
|
544
555
|
on: (type, cb) => {
|