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/_version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const PLUGIN_VERSION = "1.7.
|
|
1
|
+
export declare const PLUGIN_VERSION = "1.7.1";
|
package/dist/_version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// auto-generated
|
|
2
|
-
export const PLUGIN_VERSION = "1.7.
|
|
2
|
+
export const PLUGIN_VERSION = "1.7.1";
|
package/dist/core/kv.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ export declare const SETTING_KEYS: {
|
|
|
26
26
|
readonly showEntryTime: "subagent_magazine.show_entry_time";
|
|
27
27
|
readonly showEntryTokens: "subagent_magazine.show_entry_tokens";
|
|
28
28
|
readonly timeFormat: "subagent_magazine.time_format";
|
|
29
|
+
readonly showOrigin: "subagent_magazine.show_origin";
|
|
29
30
|
readonly dbSync: "subagent_magazine.db_sync";
|
|
30
31
|
};
|
|
31
32
|
export declare function loadSessionData(kv: KVApi): Record<string, SessionRecord>;
|
package/dist/core/kv.js
CHANGED
|
@@ -13,6 +13,7 @@ export const SETTING_KEYS = {
|
|
|
13
13
|
showEntryTime: `${KV_PREFIX}.show_entry_time`,
|
|
14
14
|
showEntryTokens: `${KV_PREFIX}.show_entry_tokens`,
|
|
15
15
|
timeFormat: `${KV_PREFIX}.time_format`,
|
|
16
|
+
showOrigin: `${KV_PREFIX}.show_origin`,
|
|
16
17
|
dbSync: `${KV_PREFIX}.db_sync`,
|
|
17
18
|
};
|
|
18
19
|
export function loadSessionData(kv) {
|
package/dist/core/types.d.ts
CHANGED
|
@@ -18,6 +18,10 @@ export interface SubEntry {
|
|
|
18
18
|
cancelRequestedAt?: number;
|
|
19
19
|
abortAccepted?: boolean;
|
|
20
20
|
cancelReason?: "manual";
|
|
21
|
+
/** 来源:工具调用条目 / 衍生会话条目(spawn)。 */
|
|
22
|
+
origin?: "tool" | "sub";
|
|
23
|
+
/** 工具输入带 background/run_in_background(后台衍生会话)。 */
|
|
24
|
+
background?: boolean;
|
|
21
25
|
}
|
|
22
26
|
export type Lang = LangCode;
|
|
23
27
|
export type SortOrder = "desc" | "asc";
|
|
@@ -26,6 +30,8 @@ export type ScrollMode = "wheel" | "click";
|
|
|
26
30
|
export type TimeFormat = "decimal" | "short" | "clock" | "compact" | "seconds";
|
|
27
31
|
/** OpenCode built-in tool names that spawn sub-agents or delegate tasks. */
|
|
28
32
|
export declare const SUBAGENT_TOOLS: Set<string>;
|
|
33
|
+
/** 工具输入是否声明后台执行(V1/V2 两种字段名)。 */
|
|
34
|
+
export declare function isBackgroundInput(input: unknown): boolean;
|
|
29
35
|
export interface ChildRecord {
|
|
30
36
|
scroll: number;
|
|
31
37
|
expanded: string;
|
|
@@ -59,6 +65,8 @@ export interface SharedSignals {
|
|
|
59
65
|
setShowEntryTokens: (v: boolean) => void;
|
|
60
66
|
timeFormat: () => TimeFormat;
|
|
61
67
|
setTimeFormat: (f: TimeFormat) => void;
|
|
68
|
+
showOrigin: () => boolean;
|
|
69
|
+
setShowOrigin: (v: boolean) => void;
|
|
62
70
|
dbSync: () => boolean;
|
|
63
71
|
setDbSync: (v: boolean) => void;
|
|
64
72
|
sessionId: string;
|
package/dist/core/types.js
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
/** OpenCode built-in tool names that spawn sub-agents or delegate tasks. */
|
|
2
2
|
export const SUBAGENT_TOOLS = new Set(["task", "subagent", "delegate", "call_omo_agent"]);
|
|
3
|
+
/** 工具输入是否声明后台执行(V1/V2 两种字段名)。 */
|
|
4
|
+
export function isBackgroundInput(input) {
|
|
5
|
+
if (!input || typeof input !== "object")
|
|
6
|
+
return false;
|
|
7
|
+
const i = input;
|
|
8
|
+
return i.background === true || i.run_in_background === true;
|
|
9
|
+
}
|
package/dist/i18n.d.ts
CHANGED
|
@@ -3,6 +3,10 @@ declare const ZH_T: {
|
|
|
3
3
|
readonly "panel.title": "子代理";
|
|
4
4
|
readonly "status.none": "暂无子代理";
|
|
5
5
|
readonly "agent.label": "代理";
|
|
6
|
+
readonly "origin.label": "来源";
|
|
7
|
+
readonly "origin.tool": "子代理调用";
|
|
8
|
+
readonly "origin.sub": "衍生会话";
|
|
9
|
+
readonly "origin.background": "后台";
|
|
6
10
|
readonly "status.label": "状态";
|
|
7
11
|
readonly "time.label": "耗时";
|
|
8
12
|
readonly "tokens.label": "上下文";
|
|
@@ -64,6 +68,7 @@ declare const ZH_T: {
|
|
|
64
68
|
readonly "settings.showEntryTime": "列表中显示耗时";
|
|
65
69
|
readonly "settings.showEntryTokens": "列表中显示 Token 数";
|
|
66
70
|
readonly "settings.timeFormat": "时间格式";
|
|
71
|
+
readonly "settings.showOrigin": "列表显示来源标记";
|
|
67
72
|
readonly "settings.dbSync": "本地数据库补全";
|
|
68
73
|
readonly "settings.on": "开";
|
|
69
74
|
readonly "settings.off": "关";
|
package/dist/i18n.js
CHANGED
|
@@ -7,6 +7,10 @@ const ZH_T = {
|
|
|
7
7
|
"panel.title": "子代理",
|
|
8
8
|
"status.none": "暂无子代理",
|
|
9
9
|
"agent.label": "代理",
|
|
10
|
+
"origin.label": "来源",
|
|
11
|
+
"origin.tool": "子代理调用",
|
|
12
|
+
"origin.sub": "衍生会话",
|
|
13
|
+
"origin.background": "后台",
|
|
10
14
|
"status.label": "状态",
|
|
11
15
|
"time.label": "耗时",
|
|
12
16
|
"tokens.label": "上下文",
|
|
@@ -68,6 +72,7 @@ const ZH_T = {
|
|
|
68
72
|
"settings.showEntryTime": "列表中显示耗时",
|
|
69
73
|
"settings.showEntryTokens": "列表中显示 Token 数",
|
|
70
74
|
"settings.timeFormat": "时间格式",
|
|
75
|
+
"settings.showOrigin": "列表显示来源标记",
|
|
71
76
|
"settings.dbSync": "本地数据库补全",
|
|
72
77
|
"settings.on": "开",
|
|
73
78
|
"settings.off": "关",
|
|
@@ -79,6 +84,10 @@ const EN_T = {
|
|
|
79
84
|
"panel.title": "SubAgent",
|
|
80
85
|
"status.none": "No sub-agents yet",
|
|
81
86
|
"agent.label": "agent",
|
|
87
|
+
"origin.label": "origin",
|
|
88
|
+
"origin.tool": "sub-agent call",
|
|
89
|
+
"origin.sub": "spawned session",
|
|
90
|
+
"origin.background": "background",
|
|
82
91
|
"status.label": "status",
|
|
83
92
|
"time.label": "time",
|
|
84
93
|
"tokens.label": "tokens",
|
|
@@ -139,6 +148,7 @@ const EN_T = {
|
|
|
139
148
|
"settings.showEntryCost": "Cost per sub-agent",
|
|
140
149
|
"settings.showEntryTime": "Elapsed time in list",
|
|
141
150
|
"settings.showEntryTokens": "Token count in list",
|
|
151
|
+
"settings.showOrigin": "Origin indicators in list",
|
|
142
152
|
"settings.timeFormat": "Time format",
|
|
143
153
|
"settings.dbSync": "Local database enrichment",
|
|
144
154
|
"settings.on": "on",
|
|
@@ -151,6 +161,10 @@ const JA_T = {
|
|
|
151
161
|
"panel.title": "サブエージェント",
|
|
152
162
|
"status.none": "サブエージェントなし",
|
|
153
163
|
"agent.label": "エージェント",
|
|
164
|
+
"origin.label": "発生元",
|
|
165
|
+
"origin.tool": "サブエージェント呼び出し",
|
|
166
|
+
"origin.sub": "生成されたセッション",
|
|
167
|
+
"origin.background": "バックグラウンド",
|
|
154
168
|
"status.label": "状態",
|
|
155
169
|
"time.label": "時間",
|
|
156
170
|
"tokens.label": "コンテキスト",
|
|
@@ -211,6 +225,7 @@ const JA_T = {
|
|
|
211
225
|
"settings.showEntryCost": "サブエージェント毎の費用",
|
|
212
226
|
"settings.showEntryTime": "一覧に所要時間を表示",
|
|
213
227
|
"settings.showEntryTokens": "一覧にトークン数を表示",
|
|
228
|
+
"settings.showOrigin": "一覧に由来マークを表示",
|
|
214
229
|
"settings.timeFormat": "時間形式",
|
|
215
230
|
"settings.dbSync": "ローカルDB補完",
|
|
216
231
|
"settings.on": "オン",
|
|
@@ -223,6 +238,10 @@ const KO_T = {
|
|
|
223
238
|
"panel.title": "서브 에이전트",
|
|
224
239
|
"status.none": "서브 에이전트 없음",
|
|
225
240
|
"agent.label": "에이전트",
|
|
241
|
+
"origin.label": "출처",
|
|
242
|
+
"origin.tool": "하위 에이전트 호출",
|
|
243
|
+
"origin.sub": "생성된 세션",
|
|
244
|
+
"origin.background": "백그라운드",
|
|
226
245
|
"status.label": "상태",
|
|
227
246
|
"time.label": "시간",
|
|
228
247
|
"tokens.label": "컨텍스트",
|
|
@@ -283,6 +302,7 @@ const KO_T = {
|
|
|
283
302
|
"settings.showEntryCost": "하위 에이전트별 비용",
|
|
284
303
|
"settings.showEntryTime": "목록에 소요 시간 표시",
|
|
285
304
|
"settings.showEntryTokens": "목록에 토큰 수 표시",
|
|
305
|
+
"settings.showOrigin": "목록에 출처 아이콘 표시",
|
|
286
306
|
"settings.timeFormat": "시간 형식",
|
|
287
307
|
"settings.dbSync": "로컬 DB 보완",
|
|
288
308
|
"settings.on": "켬",
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ function createSidebarSlot(api, panelApi, sig) {
|
|
|
14
14
|
slots: {
|
|
15
15
|
sidebar_content(ctx, input) {
|
|
16
16
|
sig.sessionId = input.session_id;
|
|
17
|
-
return (_jsx(SubAgentPanel, { api: panelApi, theme: ctx.theme.current, lang: sig.lang, maxEntries: sig.maxEntries, sortOrder: sig.sortOrder, scrollMode: sig.scrollMode, borderVisible: sig.borderVisible, showEntryCost: sig.showEntryCost, showEntryTime: sig.showEntryTime, showEntryTokens: sig.showEntryTokens, timeFormat: sig.timeFormat, sessionId: input.session_id }));
|
|
17
|
+
return (_jsx(SubAgentPanel, { api: panelApi, theme: ctx.theme.current, lang: sig.lang, maxEntries: sig.maxEntries, sortOrder: sig.sortOrder, scrollMode: sig.scrollMode, borderVisible: sig.borderVisible, showEntryCost: sig.showEntryCost, showEntryTime: sig.showEntryTime, showEntryTokens: sig.showEntryTokens, timeFormat: sig.timeFormat, showOrigin: sig.showOrigin, sessionId: input.session_id }));
|
|
18
18
|
},
|
|
19
19
|
},
|
|
20
20
|
};
|
|
@@ -33,8 +33,9 @@ const tui = async (api) => {
|
|
|
33
33
|
const [showEntryTokens, setShowEntryTokens] = createSignal(api.kv.get(`${KV_PREFIX}.show_entry_tokens`, true) !== false);
|
|
34
34
|
const storedTimeFormat = String(api.kv.get(`${KV_PREFIX}.time_format`, "short"));
|
|
35
35
|
const [timeFormat, setTimeFormat] = createSignal(TIME_FORMATS.includes(storedTimeFormat) ? storedTimeFormat : "short");
|
|
36
|
+
const [showOrigin, setShowOrigin] = createSignal(api.kv.get(`${KV_PREFIX}.show_origin`, false) === true);
|
|
36
37
|
const [dbSync, setDbSync] = createSignal(api.kv.get(`${KV_PREFIX}.db_sync`, true) !== false);
|
|
37
|
-
const signals = { lang, setLang, maxEntries, setMaxEntries, sortOrder, setSortOrder, scrollMode, setScrollMode, borderVisible, setBorderVisible, showEntryCost, setShowEntryCost, showEntryTime, setShowEntryTime, showEntryTokens, setShowEntryTokens, timeFormat, setTimeFormat, dbSync, setDbSync, sessionId: "" };
|
|
38
|
+
const signals = { lang, setLang, maxEntries, setMaxEntries, sortOrder, setSortOrder, scrollMode, setScrollMode, borderVisible, setBorderVisible, showEntryCost, setShowEntryCost, showEntryTime, setShowEntryTime, showEntryTokens, setShowEntryTokens, timeFormat, setTimeFormat, showOrigin, setShowOrigin, dbSync, setDbSync, sessionId: "" };
|
|
38
39
|
// ── V1 PanelApi adapter: wraps the V1 host API into the shared panel contract ──
|
|
39
40
|
const v1Api = {
|
|
40
41
|
kv: api.kv,
|
|
@@ -502,6 +503,22 @@ const tui = async (api) => {
|
|
|
502
503
|
} })));
|
|
503
504
|
},
|
|
504
505
|
},
|
|
506
|
+
{
|
|
507
|
+
title: "SubAgent Magazine: Origin Indicators",
|
|
508
|
+
value: "subagent-origin",
|
|
509
|
+
description: "Show or hide origin markers (⇢/↳) in the sidebar list",
|
|
510
|
+
slash: { name: "subagent-origin" },
|
|
511
|
+
onSelect: (dialog) => {
|
|
512
|
+
const t = createT(() => signals.lang());
|
|
513
|
+
const cur = Boolean(api.kv.get(`${KV_PREFIX}.show_origin`, false));
|
|
514
|
+
api.kv.set(`${KV_PREFIX}.show_origin`, !cur);
|
|
515
|
+
signals.setShowOrigin(!cur);
|
|
516
|
+
api.ui.toast({
|
|
517
|
+
message: `${t("settings.showOrigin")}: ${!cur ? t("settings.on") : t("settings.off")}`,
|
|
518
|
+
});
|
|
519
|
+
dialog?.clear();
|
|
520
|
+
},
|
|
521
|
+
},
|
|
505
522
|
]);
|
|
506
523
|
};
|
|
507
524
|
const mod = {
|
|
@@ -3,7 +3,7 @@ import { createMemo, createSignal, createEffect, onMount, onCleanup, untrack, Sh
|
|
|
3
3
|
import { PLUGIN_VERSION } from "../_version";
|
|
4
4
|
import { copyText } from "../clipboard";
|
|
5
5
|
import { createT } from "../i18n";
|
|
6
|
-
import { SUBAGENT_TOOLS } from "../core/types";
|
|
6
|
+
import { SUBAGENT_TOOLS, isBackgroundInput } from "../core/types";
|
|
7
7
|
import { visualWidth, truncate, fmtDuration, fmtTokens, safeErrorMsg } from "../core/format";
|
|
8
8
|
import { rgb, desaturateTo, dimColor, FALLBACK, MAX_SAT } from "../core/color";
|
|
9
9
|
import { KV_PREFIX, updateSessionData } from "../core/kv";
|
|
@@ -362,7 +362,7 @@ export function SubAgentPanel(props) {
|
|
|
362
362
|
const subSid = part.sessionID !== undefined ? String(part.sessionID) : undefined;
|
|
363
363
|
const partModel = part.model;
|
|
364
364
|
const modelId = partModel?.modelID ? String(partModel.modelID) : undefined;
|
|
365
|
-
upsertEntry({ id, title, agent, prompt, sessionId: subSid, status: "running", model: modelId });
|
|
365
|
+
upsertEntry({ id, title, agent, prompt, sessionId: subSid, status: "running", model: modelId, origin: "sub" });
|
|
366
366
|
}
|
|
367
367
|
// ToolPart
|
|
368
368
|
if (part.type === "tool") {
|
|
@@ -396,6 +396,8 @@ export function SubAgentPanel(props) {
|
|
|
396
396
|
prompt: existing.prompt,
|
|
397
397
|
sessionId: existing.sessionId ?? errorSid,
|
|
398
398
|
status: "error",
|
|
399
|
+
origin: "tool",
|
|
400
|
+
background: isBackgroundInput(st?.input),
|
|
399
401
|
});
|
|
400
402
|
}
|
|
401
403
|
return;
|
|
@@ -408,7 +410,7 @@ export function SubAgentPanel(props) {
|
|
|
408
410
|
// Background tasks: tool completion ≠ agent completion — keep running until session.idle
|
|
409
411
|
// Only keep running if state metadata confirms a child session was spawned;
|
|
410
412
|
// otherwise (failed spawn, invalid agent) mark as done so the entry isn't stuck forever.
|
|
411
|
-
if ((input
|
|
413
|
+
if (isBackgroundInput(input) && status === "done") {
|
|
412
414
|
const stMetaCheck = partMetadata(part, st);
|
|
413
415
|
const hasChild = stMetaCheck?.session_id !== undefined || stMetaCheck?.sessionId !== undefined;
|
|
414
416
|
if (hasChild)
|
|
@@ -425,7 +427,7 @@ export function SubAgentPanel(props) {
|
|
|
425
427
|
const subSid = stMeta?.session_id !== undefined ? String(stMeta.session_id)
|
|
426
428
|
: stMeta?.sessionId !== undefined ? String(stMeta.sessionId)
|
|
427
429
|
: undefined;
|
|
428
|
-
upsertEntry({ id, title, agent, prompt, sessionId: subSid, status });
|
|
430
|
+
upsertEntry({ id, title, agent, prompt, sessionId: subSid, status, origin: "tool", background: isBackgroundInput(input) });
|
|
429
431
|
}
|
|
430
432
|
};
|
|
431
433
|
const handleSessionEnd = (event, status) => {
|
|
@@ -826,7 +828,7 @@ export function SubAgentPanel(props) {
|
|
|
826
828
|
status = "done";
|
|
827
829
|
// Background tasks: tool completion ≠ agent completion — keep running until session.idle
|
|
828
830
|
// 仅在 metadata 确认已派生子会话时保持 running。
|
|
829
|
-
if ((scanInput
|
|
831
|
+
if (isBackgroundInput(scanInput) && status === "done") {
|
|
830
832
|
if (scanSubSid !== undefined)
|
|
831
833
|
status = "running";
|
|
832
834
|
}
|
|
@@ -891,6 +893,8 @@ export function SubAgentPanel(props) {
|
|
|
891
893
|
status,
|
|
892
894
|
startedAt: exists?.startedAt || Date.now(),
|
|
893
895
|
endedAt: exists?.endedAt ?? (status === "done" ? Date.now() : undefined),
|
|
896
|
+
origin: "tool",
|
|
897
|
+
background: isBackgroundInput(scanInput) || exists?.background === true,
|
|
894
898
|
});
|
|
895
899
|
if (entryKey !== id)
|
|
896
900
|
next.delete(id);
|
|
@@ -954,7 +958,7 @@ export function SubAgentPanel(props) {
|
|
|
954
958
|
// 非后台 tool 已完成、条目却仍停在 running:补一次 scan 收尾
|
|
955
959
|
// (success 事件可能因缺少 metadata 被适配层丢弃)。
|
|
956
960
|
const input = st?.input;
|
|
957
|
-
const isBackground = input
|
|
961
|
+
const isBackground = isBackgroundInput(input);
|
|
958
962
|
const stillRunning = entry.status === "running" || entry.status === "cancel_requested";
|
|
959
963
|
if (rawStatus === "completed" && !isBackground && stillRunning)
|
|
960
964
|
return true;
|
|
@@ -996,6 +1000,65 @@ export function SubAgentPanel(props) {
|
|
|
996
1000
|
setEntryMapRaw((prev) => {
|
|
997
1001
|
let changed = false;
|
|
998
1002
|
const next = new Map(prev);
|
|
1003
|
+
// 每 ~2s 从本地库同步衍生会话(spawn):没有对应工具条目的
|
|
1004
|
+
// 子会话补成 sub: 条目(标题/agent/用量/终态都来自数据库)。
|
|
1005
|
+
if (tick % 4 === 0) {
|
|
1006
|
+
try {
|
|
1007
|
+
const children = props.api.session.listChildren?.(props.sessionId);
|
|
1008
|
+
if (children) {
|
|
1009
|
+
const linked = new Set();
|
|
1010
|
+
for (const e of next.values())
|
|
1011
|
+
if (e.sessionId)
|
|
1012
|
+
linked.add(e.sessionId);
|
|
1013
|
+
const { parentSid, isChild } = resolveParent(props.sessionId);
|
|
1014
|
+
const rec = loadSessionData()[parentSid];
|
|
1015
|
+
const cleared = new Set(isChild ? rec?.children?.[props.sessionId]?.clearedIds : rec?.clearedIds);
|
|
1016
|
+
for (const child of children) {
|
|
1017
|
+
if (!child.id || linked.has(child.id))
|
|
1018
|
+
continue;
|
|
1019
|
+
const id = `sub:${child.id}`;
|
|
1020
|
+
if (cleared.has(id))
|
|
1021
|
+
continue;
|
|
1022
|
+
const finished = child.timeIdle != null && child.active !== true;
|
|
1023
|
+
const status = finished
|
|
1024
|
+
? (child.idleOutcome === "interrupted" ? "cancelled" : "done")
|
|
1025
|
+
: "running";
|
|
1026
|
+
next.set(id, {
|
|
1027
|
+
id,
|
|
1028
|
+
title: child.title ?? child.agent ?? "",
|
|
1029
|
+
agent: child.agent ?? "?",
|
|
1030
|
+
prompt: "",
|
|
1031
|
+
status,
|
|
1032
|
+
sessionId: child.id,
|
|
1033
|
+
startedAt: child.timeCreated ?? Date.now(),
|
|
1034
|
+
endedAt: finished ? child.timeIdle : undefined,
|
|
1035
|
+
tokens: child.tokens,
|
|
1036
|
+
cost: child.cost,
|
|
1037
|
+
model: child.model,
|
|
1038
|
+
origin: "sub",
|
|
1039
|
+
});
|
|
1040
|
+
changed = true;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
catch { }
|
|
1045
|
+
// 恢复运行:终态条目若子会话被 resume(宿主或本地库回报 busy),
|
|
1046
|
+
// 回到 running,等下一次真实 idle 再落定。
|
|
1047
|
+
for (const [id, entry] of next) {
|
|
1048
|
+
if (entry.status !== "done" && entry.status !== "error")
|
|
1049
|
+
continue;
|
|
1050
|
+
const sid = entry.sessionId;
|
|
1051
|
+
if (!sid)
|
|
1052
|
+
continue;
|
|
1053
|
+
try {
|
|
1054
|
+
if (props.api.session.status(sid)?.type === "busy") {
|
|
1055
|
+
next.set(id, { ...entry, status: "running", endedAt: undefined });
|
|
1056
|
+
changed = true;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
catch { }
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
999
1062
|
// 候选:仍缺 usage 数据的条目。以 round-robin 遍历,
|
|
1000
1063
|
// 避免一串读不到数据的会话(如 free-model 子会话)
|
|
1001
1064
|
// 饿死列表其余部分;每个 tick 只读取少数会话。
|
|
@@ -1476,6 +1539,15 @@ export function SubAgentPanel(props) {
|
|
|
1476
1539
|
const timeText = () => !isExpanded() && props.showEntryTime() && (elapsed() >= 2000 || entry.endedAt !== undefined)
|
|
1477
1540
|
? fmtDuration(elapsed(), isActiveRunning, props.timeFormat())
|
|
1478
1541
|
: "";
|
|
1542
|
+
// 来源标记:⇢ = 后台衍生(工具输入 background),↳ = 衍生会话(spawn)。
|
|
1543
|
+
const originMark = () => {
|
|
1544
|
+
if (!props.showOrigin())
|
|
1545
|
+
return "";
|
|
1546
|
+
if (entry.origin === "sub")
|
|
1547
|
+
return "\u21b3";
|
|
1548
|
+
return entry.background === true ? "\u21e2" : "";
|
|
1549
|
+
};
|
|
1550
|
+
const originW = () => (originMark() ? 2 : 0);
|
|
1479
1551
|
const suffixW = () => {
|
|
1480
1552
|
let w = 0;
|
|
1481
1553
|
const t = timeText();
|
|
@@ -1489,7 +1561,7 @@ export function SubAgentPanel(props) {
|
|
|
1489
1561
|
w += visualWidth(c);
|
|
1490
1562
|
return w;
|
|
1491
1563
|
};
|
|
1492
|
-
const labelAvail = () => Math.max(6, panelWidth() - gutter() - LEFT_PAD - suffixW());
|
|
1564
|
+
const labelAvail = () => Math.max(6, panelWidth() - gutter() - LEFT_PAD - originW() - suffixW());
|
|
1493
1565
|
const labelText = () => {
|
|
1494
1566
|
const max = labelAvail();
|
|
1495
1567
|
const text = entry.title || entry.agent;
|
|
@@ -1497,7 +1569,7 @@ export function SubAgentPanel(props) {
|
|
|
1497
1569
|
const pad = Math.max(0, max - visualWidth(truncated));
|
|
1498
1570
|
return truncated + " ".repeat(pad);
|
|
1499
1571
|
};
|
|
1500
|
-
return (_jsxs(_Fragment, { children: [_jsxs("text", { onMouseUp: () => toggleExpand(entry.id), children: [_jsx("span", { style: { fg: pal().muted }, children: isExpanded() ? "\u25bc" : "\u25b6" }), " ", _jsx("span", { style: { fg: statusColor() }, children: statusDot() }), " ", _jsx("span", { style: { fg: pal().text }, children: labelText() }), timeText() ? (_jsxs(_Fragment, { children: [" ", _jsx("span", { style: { fg: timeColor() }, children: timeText() })] })) : null, tokenText() ? (_jsx("span", { style: { fg: pal().muted }, children: tokenText() })) : null, costText() ? (_jsx("span", { style: { fg: pal().warning }, children: costText() })) : null] }), _jsxs(Show, { when: isExpanded(), children: [_jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("agent.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("agent.label"))) }), _jsx("span", { style: { fg: pal().muted }, children: entry.agent })] }), _jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("status.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("status.label"))) }), _jsx("span", { style: { fg: isActiveRunning ? pal().warning : isCancelled ? pal().muted : isError ? pal().error : pal().success }, children: isActiveRunning ? t("status.running") : isCancelled ? t("status.cancelled") : isError ? t("status.error") : t("status.done") })] }), _jsx(Show, { when: elapsed() >= 2000 || entry.endedAt !== undefined, children: _jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("time.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("time.label"))) }), _jsx("span", { style: { fg: pal().muted }, children: fmtDuration(elapsed(), isActiveRunning, props.timeFormat()) })] }) }), _jsx(Show, { when: entry.tokens !== undefined, children: _jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("tokens.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("tokens.label"))) }), _jsx("span", { style: { fg: pal().muted }, children: fmtTokens(entry.tokens) })] }) }), _jsx(Show, { when: entry.error, children: _jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().error }, children: [t("error.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("error.label"))) }), _jsx("span", { style: { fg: pal().error }, children: truncate(String(entry.error), expandedValAvail()) })] }) }), _jsx(Show, { when: entry.cost !== undefined, children: (() => {
|
|
1572
|
+
return (_jsxs(_Fragment, { children: [_jsxs("text", { onMouseUp: () => toggleExpand(entry.id), children: [_jsx("span", { style: { fg: pal().muted }, children: isExpanded() ? "\u25bc" : "\u25b6" }), " ", _jsx("span", { style: { fg: statusColor() }, children: statusDot() }), " ", originMark() ? _jsx("span", { style: { fg: pal().muted }, children: originMark() + " " }) : null, _jsx("span", { style: { fg: pal().text }, children: labelText() }), timeText() ? (_jsxs(_Fragment, { children: [" ", _jsx("span", { style: { fg: timeColor() }, children: timeText() })] })) : null, tokenText() ? (_jsx("span", { style: { fg: pal().muted }, children: tokenText() })) : null, costText() ? (_jsx("span", { style: { fg: pal().warning }, children: costText() })) : null] }), _jsxs(Show, { when: isExpanded(), children: [_jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("agent.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("agent.label"))) }), _jsx("span", { style: { fg: pal().muted }, children: entry.agent })] }), _jsx(Show, { when: props.showOrigin(), children: _jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("origin.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("origin.label"))) }), _jsxs("span", { style: { fg: pal().muted }, children: [entry.origin === "sub" ? t("origin.sub") : t("origin.tool"), entry.background === true ? ` (${t("origin.background")})` : ""] })] }) }), _jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("status.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("status.label"))) }), _jsx("span", { style: { fg: isActiveRunning ? pal().warning : isCancelled ? pal().muted : isError ? pal().error : pal().success }, children: isActiveRunning ? t("status.running") : isCancelled ? t("status.cancelled") : isError ? t("status.error") : t("status.done") })] }), _jsx(Show, { when: elapsed() >= 2000 || entry.endedAt !== undefined, children: _jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("time.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("time.label"))) }), _jsx("span", { style: { fg: pal().muted }, children: fmtDuration(elapsed(), isActiveRunning, props.timeFormat()) })] }) }), _jsx(Show, { when: entry.tokens !== undefined, children: _jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("tokens.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("tokens.label"))) }), _jsx("span", { style: { fg: pal().muted }, children: fmtTokens(entry.tokens) })] }) }), _jsx(Show, { when: entry.error, children: _jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().error }, children: [t("error.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("error.label"))) }), _jsx("span", { style: { fg: pal().error }, children: truncate(String(entry.error), expandedValAvail()) })] }) }), _jsx(Show, { when: entry.cost !== undefined, children: (() => {
|
|
1501
1573
|
const cost = entry.cost;
|
|
1502
1574
|
return (_jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("cost.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("cost.label"))) }), _jsxs("span", { style: { fg: pal().muted }, children: ["$", cost.toFixed(4)] })] }));
|
|
1503
1575
|
})() }), _jsx(Show, { when: validModel(entry.model) ? entry.model : undefined, children: _jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("model.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("model.label"))) }), _jsx("span", { style: { fg: pal().muted }, children: truncate(entry.model, expandedValAvail()) })] }) }), _jsx(Show, { when: entry.todoTotal !== undefined, children: _jsxs("text", { children: [" ", _jsxs("span", { style: { fg: pal().primary }, children: [t("todo.label"), ": "] }), _jsx("span", { style: { fg: pal().muted }, children: " ".repeat(expandedPad(t("todo.label"))) }), _jsxs("span", { style: { fg: pal().muted }, children: [entry.todoDone, "/", entry.todoTotal] })] }) }), _jsx(Show, { when: entry.sessionId, children: _jsxs("text", { onMouseUp: async () => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SubEntry } from "../core/types";
|
|
2
2
|
export type SubEntryPatch = Omit<SubEntry, "startedAt" | "endedAt"> & {
|
|
3
3
|
startedAt?: number;
|
|
4
|
+
endedAt?: number;
|
|
4
5
|
};
|
|
5
6
|
export declare function findSubEntryKey(entries: ReadonlyMap<string, SubEntry>, id: string, sessionId?: string): string | undefined;
|
|
6
7
|
export declare function upsertSubEntry(entries: ReadonlyMap<string, SubEntry>, partial: SubEntryPatch, now?: number): Map<string, SubEntry>;
|
package/dist/panel/entry-map.js
CHANGED
|
@@ -19,7 +19,7 @@ export function upsertSubEntry(entries, partial, now = Date.now()) {
|
|
|
19
19
|
...partial,
|
|
20
20
|
id: existing?.id ?? partial.id,
|
|
21
21
|
startedAt: existing?.startedAt || partial.startedAt || now,
|
|
22
|
-
endedAt: ended ? (existing?.endedAt
|
|
22
|
+
endedAt: ended ? (existing?.endedAt ?? partial.endedAt ?? now) : undefined,
|
|
23
23
|
});
|
|
24
24
|
return next;
|
|
25
25
|
}
|
|
@@ -8,6 +8,20 @@ export interface SessionLike {
|
|
|
8
8
|
agent?: string;
|
|
9
9
|
cost?: number;
|
|
10
10
|
}
|
|
11
|
+
/** Spawned child session summary (V2 reads it from the local database). */
|
|
12
|
+
export interface ChildSessionLike {
|
|
13
|
+
id: string;
|
|
14
|
+
agent?: string;
|
|
15
|
+
title?: string;
|
|
16
|
+
model?: string;
|
|
17
|
+
cost?: number;
|
|
18
|
+
tokens?: number;
|
|
19
|
+
timeCreated?: number;
|
|
20
|
+
timeIdle?: number;
|
|
21
|
+
idleOutcome?: string;
|
|
22
|
+
/** Still running: last message is newer than time_idle (resume keeps the old value). */
|
|
23
|
+
active?: boolean;
|
|
24
|
+
}
|
|
11
25
|
export interface SessionStatusLike {
|
|
12
26
|
type: string;
|
|
13
27
|
}
|
|
@@ -53,6 +67,8 @@ export interface PanelApi {
|
|
|
53
67
|
agent?: string;
|
|
54
68
|
startedAt?: number;
|
|
55
69
|
}): string | undefined;
|
|
70
|
+
/** Optional host-side listing of spawned child sessions. */
|
|
71
|
+
listChildren?(parentId: string): ChildSessionLike[] | undefined;
|
|
56
72
|
};
|
|
57
73
|
event: {
|
|
58
74
|
on(type: PanelEventType, cb: (e: PanelEvent) => void): () => void;
|