opencode-subagent-magazine 1.6.1 → 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/v2.js CHANGED
@@ -2,6 +2,336 @@
2
2
  import { createComponent as _$createComponent2 } from "@opentui/solid";
3
3
  import { createSignal as createSignal3 } from "solid-js";
4
4
 
5
+ // src/core/kv.ts
6
+ var KV_PREFIX = "subagent_magazine";
7
+ var SESSION_DATA_KEY = `${KV_PREFIX}.session_data`;
8
+ var SETTING_KEYS = {
9
+ lang: `${KV_PREFIX}.lang`,
10
+ maxEntries: `${KV_PREFIX}.max_entries`,
11
+ order: `${KV_PREFIX}.order`,
12
+ scrollMode: `${KV_PREFIX}.scroll_mode`,
13
+ open: `${KV_PREFIX}.open`,
14
+ ttlDays: `${KV_PREFIX}.ttl_days`,
15
+ border: `${KV_PREFIX}.border`,
16
+ showEntryCost: `${KV_PREFIX}.show_entry_cost`,
17
+ showEntryTime: `${KV_PREFIX}.show_entry_time`,
18
+ showEntryTokens: `${KV_PREFIX}.show_entry_tokens`,
19
+ timeFormat: `${KV_PREFIX}.time_format`,
20
+ showOrigin: `${KV_PREFIX}.show_origin`,
21
+ dbSync: `${KV_PREFIX}.db_sync`
22
+ };
23
+ function loadSessionData(kv) {
24
+ try {
25
+ const raw = kv.get(SESSION_DATA_KEY, "{}");
26
+ return JSON.parse(String(raw));
27
+ } catch {
28
+ return {};
29
+ }
30
+ }
31
+ function updateSessionData(kv, mutator) {
32
+ try {
33
+ if (typeof kv.update === "function") {
34
+ return kv.update(SESSION_DATA_KEY, (raw) => {
35
+ const data2 = parseSessionData(raw);
36
+ mutator(data2);
37
+ return JSON.stringify(data2);
38
+ });
39
+ }
40
+ const data = loadSessionData(kv);
41
+ mutator(data);
42
+ kv.set(SESSION_DATA_KEY, JSON.stringify(data));
43
+ } catch {
44
+ }
45
+ }
46
+ function parseSessionData(raw) {
47
+ try {
48
+ return JSON.parse(String(raw ?? "{}"));
49
+ } catch {
50
+ return {};
51
+ }
52
+ }
53
+ function readTTLDays(kv) {
54
+ const ttlDaysRaw = parseInt(String(kv.get(SETTING_KEYS.ttlDays, "3")), 10);
55
+ return Number.isNaN(ttlDaysRaw) ? 3 : ttlDaysRaw;
56
+ }
57
+
58
+ // src/v2/db.ts
59
+ import { homedir } from "node:os";
60
+ var BUN_SQLITE = "bun:sqlite";
61
+ function modelIdOf(value) {
62
+ if (typeof value === "string") {
63
+ const s = value.trim();
64
+ if (s.length === 0) return void 0;
65
+ if (s.startsWith("{")) {
66
+ try {
67
+ return modelIdOf(JSON.parse(s));
68
+ } catch {
69
+ return void 0;
70
+ }
71
+ }
72
+ return s;
73
+ }
74
+ if (value && typeof value === "object") {
75
+ const id = value.id;
76
+ if (id === void 0) return void 0;
77
+ const direct = modelIdOf(id);
78
+ if (direct !== void 0) return direct;
79
+ const s = String(id);
80
+ return s.length > 0 ? s : void 0;
81
+ }
82
+ return void 0;
83
+ }
84
+ function contextTokens(tokens) {
85
+ if (!tokens || typeof tokens !== "object") return void 0;
86
+ const t = tokens;
87
+ const cache = t.cache && typeof t.cache === "object" ? t.cache : {};
88
+ const sum = (Number(t.input) || 0) + (Number(t.output) || 0) + (Number(t.reasoning) || 0) + (Number(cache.read) || 0) + (Number(cache.write) || 0);
89
+ return sum > 0 ? sum : void 0;
90
+ }
91
+ function isSessionActive(input) {
92
+ const { timeIdle, lastMessageType, lastMessageAt } = input;
93
+ if (lastMessageType === "idle") return false;
94
+ if (lastMessageAt === void 0) return false;
95
+ if (timeIdle === void 0) return true;
96
+ return lastMessageAt > timeIdle;
97
+ }
98
+ function statusOfChild(info) {
99
+ if (!info) return void 0;
100
+ if (info.active === true) return void 0;
101
+ if (info.timeIdle == null) return void 0;
102
+ return info.idleOutcome === "interrupted" ? "cancelled" : "done";
103
+ }
104
+ function findDbPath(env = process.env, home = safeHomedir()) {
105
+ if (env.OPENCODE_DB) return env.OPENCODE_DB;
106
+ const base = env.XDG_DATA_HOME && env.XDG_DATA_HOME.length > 0 ? env.XDG_DATA_HOME : `${home}/.local/share`;
107
+ if (!base || base === "/.local/share") return void 0;
108
+ return `${base}/opencode/opencode.db`;
109
+ }
110
+ function safeHomedir() {
111
+ try {
112
+ return homedir();
113
+ } catch {
114
+ return "";
115
+ }
116
+ }
117
+ var CALL_MAP_SQL = `
118
+ SELECT json_extract(p.value, '$.id') AS call_id,
119
+ json_extract(p.value, '$.state.metadata.sessionID') AS child_sid
120
+ FROM session_message m, json_each(m.data, '$.content') p
121
+ WHERE m.session_id = ? AND m.type = 'assistant'
122
+ AND json_extract(p.value, '$.id') LIKE 'call_%'
123
+ AND json_extract(p.value, '$.state.metadata.sessionID') IS NOT NULL`;
124
+ var CHILD_INFO_SQL = `
125
+ SELECT parent_id, agent, title, model, cost,
126
+ tokens_input, tokens_output, tokens_reasoning, tokens_cache_read, tokens_cache_write,
127
+ time_created, time_idle, idle_outcome
128
+ FROM session_v2 WHERE id = ?`;
129
+ var LAST_ASSISTANT_SQL = `
130
+ SELECT json_extract(data, '$.tokens.input') AS input,
131
+ json_extract(data, '$.tokens.output') AS output,
132
+ json_extract(data, '$.tokens.reasoning') AS reasoning,
133
+ json_extract(data, '$.tokens.cache.read') AS cache_read,
134
+ json_extract(data, '$.tokens.cache.write') AS cache_write,
135
+ json_extract(data, '$.model.id') AS model_id
136
+ FROM session_message
137
+ WHERE session_id = ? AND type = 'assistant' AND json_extract(data, '$.tokens.output') > 0
138
+ ORDER BY seq DESC LIMIT 1`;
139
+ var LAST_MESSAGE_SQL = `
140
+ SELECT type, COALESCE(time_updated, time_created) AS at
141
+ FROM session_message WHERE session_id = ? ORDER BY seq DESC LIMIT 1`;
142
+ var ASSISTANT_COST_SQL = `
143
+ SELECT COUNT(*) AS n, SUM(COALESCE(json_extract(data, '$.cost'), 0)) AS total
144
+ FROM session_message WHERE session_id = ? AND type = 'assistant'`;
145
+ var CHILDREN_SQL = `
146
+ SELECT id, agent, title, model, cost,
147
+ tokens_input, tokens_output, tokens_reasoning, tokens_cache_read, tokens_cache_write,
148
+ time_created, time_idle, idle_outcome
149
+ FROM session_v2
150
+ WHERE parent_id = ? AND (fork_session_id IS NULL OR fork_session_id = '')
151
+ ORDER BY time_created`;
152
+ var num = (v) => {
153
+ const n = Number(v);
154
+ return Number.isFinite(n) ? n : void 0;
155
+ };
156
+ var str = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
157
+ function createSessionDbIndex(enabled) {
158
+ const dbPath = findDbPath();
159
+ if (!dbPath) return void 0;
160
+ let db;
161
+ let failed = false;
162
+ void (async () => {
163
+ try {
164
+ const mod2 = await import(BUN_SQLITE);
165
+ const Database = mod2?.Database;
166
+ if (!Database) {
167
+ failed = true;
168
+ return;
169
+ }
170
+ db = new Database(dbPath, { readonly: true });
171
+ try {
172
+ db.query("pragma query_only = on").all();
173
+ } catch {
174
+ }
175
+ } catch {
176
+ failed = true;
177
+ db = void 0;
178
+ }
179
+ })();
180
+ const CALL_MAP_TTL_MS = 15e3;
181
+ const CALL_MAP_RETRY_MS = 2e3;
182
+ const INFO_TTL_MS = 5e3;
183
+ const callMaps = /* @__PURE__ */ new Map();
184
+ const infos = /* @__PURE__ */ new Map();
185
+ const childrenCache = /* @__PURE__ */ new Map();
186
+ const ready = () => {
187
+ if (failed || db === void 0) return false;
188
+ try {
189
+ return enabled();
190
+ } catch {
191
+ return false;
192
+ }
193
+ };
194
+ const buildCallMap = (parentId) => {
195
+ const now = Date.now();
196
+ const prev = callMaps.get(parentId);
197
+ if (prev && now - prev.at < CALL_MAP_RETRY_MS) return prev.map;
198
+ const map = /* @__PURE__ */ new Map();
199
+ try {
200
+ for (const row of db.query(CALL_MAP_SQL).all(parentId)) {
201
+ const id = str(row.call_id);
202
+ const sid = str(row.child_sid);
203
+ if (id && sid) map.set(id, sid);
204
+ }
205
+ } catch {
206
+ }
207
+ callMaps.set(parentId, { map, at: now });
208
+ return map;
209
+ };
210
+ const resolveCall = (parentId, callId) => {
211
+ if (!ready() || !parentId || !callId) return void 0;
212
+ const prev = callMaps.get(parentId);
213
+ const map = prev && Date.now() - prev.at < CALL_MAP_TTL_MS ? prev.map : buildCallMap(parentId);
214
+ return map.get(callId) ?? buildCallMap(parentId).get(callId);
215
+ };
216
+ const matchChild = (parentId, agent, startedAt) => {
217
+ if (!ready() || !parentId || startedAt === void 0) return void 0;
218
+ const now = Date.now();
219
+ let cached = childrenCache.get(parentId);
220
+ if (!cached || now - cached.at > CALL_MAP_TTL_MS) {
221
+ let list = [];
222
+ try {
223
+ list = db.query(CHILDREN_SQL).all(parentId);
224
+ } catch {
225
+ }
226
+ cached = { list, at: now };
227
+ childrenCache.set(parentId, cached);
228
+ }
229
+ let best;
230
+ let bestAt = Number.POSITIVE_INFINITY;
231
+ for (const row of cached.list) {
232
+ const sid = str(row.id);
233
+ const t = num(row.time_created);
234
+ if (!sid || t === void 0) continue;
235
+ if (t < startedAt - 2e3) continue;
236
+ if (agent !== void 0 && str(row.agent) !== agent) continue;
237
+ if (t < bestAt) {
238
+ bestAt = t;
239
+ best = sid;
240
+ }
241
+ }
242
+ return best;
243
+ };
244
+ const info = (sid) => {
245
+ if (!sid) return void 0;
246
+ const hit = infos.get(sid);
247
+ if (hit && Date.now() - hit.at < INFO_TTL_MS) return hit.info;
248
+ if (!ready()) return void 0;
249
+ let result;
250
+ try {
251
+ const row = db.query(CHILD_INFO_SQL).get(sid);
252
+ if (row) {
253
+ const last = db.query(LAST_ASSISTANT_SQL).get(sid);
254
+ const lastMsg = db.query(LAST_MESSAGE_SQL).get(sid);
255
+ let tokens = contextTokens(last ? {
256
+ input: last.input,
257
+ output: last.output,
258
+ reasoning: last.reasoning,
259
+ cache: { read: last.cache_read, write: last.cache_write }
260
+ } : void 0);
261
+ if (tokens === void 0) {
262
+ const agg = (num(row.tokens_input) || 0) + (num(row.tokens_output) || 0) + (num(row.tokens_reasoning) || 0) + (num(row.tokens_cache_read) || 0) + (num(row.tokens_cache_write) || 0);
263
+ if (agg > 0) tokens = agg;
264
+ }
265
+ let cost = num(row.cost);
266
+ if (!(typeof cost === "number" && cost > 0)) {
267
+ const aggCost = db.query(ASSISTANT_COST_SQL).get(sid);
268
+ if (aggCost && Number(aggCost.n) > 0) cost = Number(aggCost.total) || 0;
269
+ }
270
+ result = {
271
+ id: sid,
272
+ parentId: str(row.parent_id),
273
+ agent: str(row.agent),
274
+ title: str(row.title),
275
+ model: modelIdOf(last?.model_id) ?? modelIdOf(row.model),
276
+ cost,
277
+ tokens,
278
+ timeCreated: num(row.time_created),
279
+ timeIdle: num(row.time_idle),
280
+ idleOutcome: str(row.idle_outcome),
281
+ active: isSessionActive({
282
+ timeIdle: num(row.time_idle),
283
+ lastMessageType: str(lastMsg?.type),
284
+ lastMessageAt: num(lastMsg?.at)
285
+ })
286
+ };
287
+ }
288
+ } catch {
289
+ result = void 0;
290
+ }
291
+ infos.set(sid, { info: result, at: Date.now() });
292
+ return result;
293
+ };
294
+ const children = (parentId) => {
295
+ if (!ready() || !parentId) return void 0;
296
+ const now = Date.now();
297
+ let cached = childrenCache.get(parentId);
298
+ if (!cached || now - cached.at > CALL_MAP_TTL_MS) {
299
+ let list = [];
300
+ try {
301
+ list = db.query(CHILDREN_SQL).all(parentId);
302
+ } catch {
303
+ }
304
+ cached = { list, at: now };
305
+ childrenCache.set(parentId, cached);
306
+ }
307
+ const out = [];
308
+ for (const row of cached.list) {
309
+ const sid = str(row.id);
310
+ if (!sid) continue;
311
+ const full = info(sid);
312
+ if (full) {
313
+ out.push(full);
314
+ continue;
315
+ }
316
+ const agg = (num(row.tokens_input) || 0) + (num(row.tokens_output) || 0) + (num(row.tokens_reasoning) || 0) + (num(row.tokens_cache_read) || 0) + (num(row.tokens_cache_write) || 0);
317
+ out.push({
318
+ id: sid,
319
+ parentId,
320
+ agent: str(row.agent),
321
+ title: str(row.title),
322
+ model: modelIdOf(row.model),
323
+ cost: num(row.cost),
324
+ tokens: agg > 0 ? agg : void 0,
325
+ timeCreated: num(row.time_created),
326
+ timeIdle: num(row.time_idle),
327
+ idleOutcome: str(row.idle_outcome)
328
+ });
329
+ }
330
+ return out;
331
+ };
332
+ return { enabled: ready, resolveCall, matchChild, info, children };
333
+ }
334
+
5
335
  // src/v2/v2-panel-api.ts
6
336
  var SUBAGENT_TOOL_V2 = "subagent";
7
337
  function normalizeMeta(meta) {
@@ -18,7 +348,11 @@ function toV1Part(p) {
18
348
  if (st.output === void 0 && Array.isArray(st.content)) {
19
349
  st.output = st.content.map((c) => c.type === "text" ? c.text : c.type === "file" ? String(c.path ?? c.filename ?? "") : JSON.stringify(c)).filter((t) => typeof t === "string" && t.length > 0).join("\n");
20
350
  }
21
- return { type: "tool", tool: p.tool ?? p.name, state: st, subagent_type: p.subagent_type, metadata: normalizeMeta(p.metadata) };
351
+ const meta = normalizeMeta({
352
+ ...st.metadata ?? {},
353
+ ...p.metadata ?? {}
354
+ });
355
+ return { type: "tool", id: p.id, tool: p.tool ?? p.name, state: st, subagent_type: p.subagent_type, metadata: meta };
22
356
  }
23
357
  if (p.type === "text") return { type: "text", text: String(p.text ?? "") };
24
358
  if (p.type === "file") return { type: "file", source: p.source ?? {} };
@@ -55,7 +389,60 @@ function createPanelApi(context, settings) {
55
389
  d.value = value;
56
390
  });
57
391
  };
392
+ const kvUpdate = (key, updater) => {
393
+ let entry = kvStore.get(key);
394
+ if (!entry) {
395
+ const created = context.storage.store(`subagent_magazine.${key}`, {
396
+ initial: { value: void 0 }
397
+ });
398
+ entry = created;
399
+ kvStore.set(key, entry);
400
+ }
401
+ const [, mutate] = entry;
402
+ return mutate((d) => {
403
+ d.value = updater(d.value);
404
+ });
405
+ };
406
+ const dbIndex = createSessionDbIndex(() => kvGet(SETTING_KEYS.dbSync, true) !== false);
407
+ const withResolvedSid = (data, meta) => {
408
+ if (meta.sessionID !== void 0) return meta;
409
+ try {
410
+ const parent = data.sessionID !== void 0 ? String(data.sessionID) : "";
411
+ const callId = data.id !== void 0 ? String(data.id) : "";
412
+ const sid = parent && callId ? dbIndex?.resolveCall(parent, callId) : void 0;
413
+ if (sid) return normalizeMeta({ ...meta, sessionID: sid });
414
+ } catch {
415
+ }
416
+ return meta;
417
+ };
58
418
  const isSubagentTool = (name) => name === SUBAGENT_TOOL_V2 || name === "task" || name === "delegate" || name === "call_omo_agent";
419
+ const findToolPart = (data) => {
420
+ const sid = data.sessionID;
421
+ const msgID = data.assistantMessageID;
422
+ const partID = data.id;
423
+ if (sid === void 0 || msgID === void 0 || partID === void 0) return void 0;
424
+ try {
425
+ const raw = context.data.session.message.list(String(sid));
426
+ if (!raw) return void 0;
427
+ for (let i = raw.length - 1; i >= 0; i--) {
428
+ const m = raw[i];
429
+ if (!m || String(m.id) !== String(msgID)) continue;
430
+ const content = Array.isArray(m.content) ? m.content : [];
431
+ for (const p of content) {
432
+ const pp = p;
433
+ if (pp && pp.type === "tool" && String(pp.id) === String(partID)) {
434
+ const st = pp.state ?? {};
435
+ const input = st.input && typeof st.input === "object" ? st.input : void 0;
436
+ const name = pp.name ?? pp.tool;
437
+ return { name: name !== void 0 ? String(name) : void 0, input };
438
+ }
439
+ }
440
+ break;
441
+ }
442
+ } catch {
443
+ }
444
+ return void 0;
445
+ };
59
446
  const toolEventToPart = (event) => {
60
447
  const type = event.type;
61
448
  const data = event.data;
@@ -73,20 +460,29 @@ function createPanelApi(context, settings) {
73
460
  return { type: "tool", tool: normName(String(data.name)), id: String(data.id), state: { status: "pending", input: {} } };
74
461
  }
75
462
  if (type === "session.tool.called") {
76
- const name = data.name ?? info?.name;
463
+ let name = data.name ?? info?.name;
464
+ let input = data.input ?? info?.input;
465
+ if (!name) {
466
+ const resolved = findToolPart(data);
467
+ if (resolved?.name) {
468
+ name = resolved.name;
469
+ if (input === void 0) input = resolved.input;
470
+ }
471
+ }
77
472
  if (name && !isSubagentTool(name)) return void 0;
78
473
  if (!name) return void 0;
79
- if (key) toolInfo.set(key, { name: String(name), input: data.input ?? {} });
474
+ if (key) toolInfo.set(key, { name: String(name), input: input ?? {} });
80
475
  return {
81
476
  type: "tool",
82
477
  tool: normName(String(name)),
83
478
  id: String(data.id),
84
- subagent_type: agentOf(data.input),
85
- state: { status: "running", input: data.input ?? {}, metadata: {} }
479
+ subagent_type: agentOf(input),
480
+ state: { status: "running", input: input ?? {}, metadata: withResolvedSid(data, {}) }
86
481
  };
87
482
  }
88
483
  if (type === "session.tool.progress") {
89
- const name = data.name ?? info?.name;
484
+ let name = data.name ?? info?.name;
485
+ if (!name) name = findToolPart(data)?.name;
90
486
  if (name && !isSubagentTool(name)) return void 0;
91
487
  if (!name) return void 0;
92
488
  return {
@@ -94,24 +490,42 @@ function createPanelApi(context, settings) {
94
490
  tool: normName(String(name)),
95
491
  id: String(data.id),
96
492
  subagent_type: agentOf(info?.input),
97
- state: { status: "running", input: info?.input ?? {}, metadata: normalizeMeta(data.metadata) }
493
+ state: { status: "running", input: info?.input ?? {}, metadata: withResolvedSid(data, normalizeMeta(data.metadata)) }
98
494
  };
99
495
  }
100
496
  if (type === "session.tool.success" || type === "session.tool.failed") {
101
- const meta = normalizeMeta(data.metadata);
102
- if (meta.sessionID === void 0) return void 0;
497
+ const eventMeta = normalizeMeta(data.metadata);
498
+ const meta = withResolvedSid(data, eventMeta);
103
499
  if (type === "session.tool.failed" && meta.status === "running") return void 0;
104
- const name = data.name ?? info?.name;
500
+ let name = data.name ?? info?.name;
501
+ let input = info?.input ?? data.input;
502
+ if (!name || input === void 0) {
503
+ const resolved = findToolPart(data);
504
+ if (!name && resolved?.name) name = resolved.name;
505
+ if (input === void 0 && resolved?.input) input = resolved.input;
506
+ }
105
507
  if (name && !isSubagentTool(name)) return void 0;
106
- const input = info?.input ?? data.input ?? {};
508
+ if (key && name) toolInfo.set(key, { name: String(name), input: input ?? info?.input ?? {} });
509
+ const finalInput = input ?? {};
510
+ if (meta.sessionID === void 0 && (finalInput.background === true || finalInput.run_in_background === true)) {
511
+ return void 0;
512
+ }
513
+ let status = type === "session.tool.failed" ? "error" : "completed";
514
+ if (meta.sessionID !== void 0 && meta.sessionID !== eventMeta.sessionID) {
515
+ try {
516
+ const childInfo = dbIndex?.info(String(meta.sessionID));
517
+ if (childInfo && statusOfChild(childInfo) === void 0) status = "running";
518
+ } catch {
519
+ }
520
+ }
107
521
  return {
108
522
  type: "tool",
109
523
  tool: normName(String(name ?? "task")),
110
524
  id: String(data.id),
111
- subagent_type: agentOf(input),
525
+ subagent_type: agentOf(finalInput),
112
526
  state: {
113
- status: type === "session.tool.failed" ? "error" : "completed",
114
- input,
527
+ status,
528
+ input: finalInput,
115
529
  metadata: meta
116
530
  }
117
531
  };
@@ -133,14 +547,55 @@ function createPanelApi(context, settings) {
133
547
  }
134
548
  return out;
135
549
  };
550
+ const usageCache = /* @__PURE__ */ new Map();
551
+ const syncRequestedAt = /* @__PURE__ */ new Map();
552
+ const USAGE_CACHE_TTL_MS = 5e3;
553
+ const SYNC_THROTTLE_MS = 3e4;
554
+ const rememberUsage = (sid, patch) => {
555
+ const prev = usageCache.get(sid);
556
+ usageCache.set(sid, { ...prev, ...patch, at: Date.now() });
557
+ };
558
+ const cachedUsage = (sid) => {
559
+ const hit = usageCache.get(sid);
560
+ if (!hit) return void 0;
561
+ if (Date.now() - hit.at > USAGE_CACHE_TTL_MS) return void 0;
562
+ return hit;
563
+ };
564
+ const requestMessageSync = (sid) => {
565
+ const last = syncRequestedAt.get(sid) ?? 0;
566
+ if (Date.now() - last < SYNC_THROTTLE_MS) return;
567
+ syncRequestedAt.set(sid, Date.now());
568
+ try {
569
+ const sync = context.data.session.message.sync;
570
+ if (typeof sync === "function") void sync.call(context.data.session.message, sid).catch(() => {
571
+ });
572
+ } catch {
573
+ }
574
+ };
575
+ const listedSession = (sid) => {
576
+ try {
577
+ return context.data.session.list()?.find((s) => s.id === sid);
578
+ } catch {
579
+ return void 0;
580
+ }
581
+ };
136
582
  return {
137
583
  kv: {
138
584
  get: kvGet,
139
- set: kvSet
585
+ set: kvSet,
586
+ update: kvUpdate
140
587
  },
141
588
  usage: {
142
589
  readSessionTokens: (sid) => {
143
590
  if (!sid) return void 0;
591
+ try {
592
+ const dbTokens = dbIndex?.info(sid)?.tokens;
593
+ if (dbTokens !== void 0) {
594
+ rememberUsage(sid, { tokens: dbTokens });
595
+ return dbTokens;
596
+ }
597
+ } catch {
598
+ }
144
599
  try {
145
600
  const msgs = context.data.session.message.list(sid);
146
601
  if (msgs) {
@@ -150,9 +605,15 @@ function createPanelApi(context, settings) {
150
605
  const t = m.tokens;
151
606
  if (!t || !(Number(t.output) > 0)) continue;
152
607
  const ctx = (Number(t.input) || 0) + (Number(t.output) || 0) + (Number(t.reasoning) || 0) + (Number(t.cache?.read) || 0) + (Number(t.cache?.write) || 0);
153
- if (ctx > 0) return ctx;
608
+ if (ctx > 0) {
609
+ rememberUsage(sid, { tokens: ctx });
610
+ return ctx;
611
+ }
154
612
  }
155
613
  }
614
+ const cached = cachedUsage(sid)?.tokens;
615
+ if (cached !== void 0) return cached;
616
+ requestMessageSync(sid);
156
617
  return void 0;
157
618
  } catch {
158
619
  return void 0;
@@ -160,33 +621,91 @@ function createPanelApi(context, settings) {
160
621
  },
161
622
  readSessionCost: (sid) => {
162
623
  if (!sid) return void 0;
624
+ try {
625
+ const dbCost = dbIndex?.info(sid)?.cost;
626
+ if (dbCost !== void 0) {
627
+ rememberUsage(sid, { cost: dbCost });
628
+ return dbCost;
629
+ }
630
+ } catch {
631
+ }
632
+ try {
633
+ const direct = context.data.session.cost(sid);
634
+ if (typeof direct === "number" && direct > 0) {
635
+ rememberUsage(sid, { cost: direct });
636
+ return direct;
637
+ }
638
+ } catch {
639
+ }
163
640
  try {
164
641
  const session = context.data.session.get(sid);
165
- if (session?.cost != null && session.cost > 0) return session.cost;
642
+ if (session?.cost != null && session.cost > 0) {
643
+ rememberUsage(sid, { cost: session.cost });
644
+ return session.cost;
645
+ }
646
+ const listed = listedSession(sid);
647
+ if (listed?.cost != null && listed.cost > 0) {
648
+ rememberUsage(sid, { cost: listed.cost });
649
+ return listed.cost;
650
+ }
166
651
  const msgs = context.data.session.message.list(sid);
167
- if (!msgs) return void 0;
168
- let total = 0;
169
- for (const m of msgs) {
170
- if (m.type === "assistant" && typeof m.cost === "number") total += m.cost;
652
+ if (Array.isArray(msgs) && msgs.length > 0) {
653
+ let total = 0;
654
+ let sawAssistant = false;
655
+ for (const m of msgs) {
656
+ if (m.type !== "assistant") continue;
657
+ sawAssistant = true;
658
+ if (typeof m.cost === "number") total += m.cost;
659
+ }
660
+ if (sawAssistant) {
661
+ rememberUsage(sid, { cost: total });
662
+ return total;
663
+ }
171
664
  }
172
- return total > 0 ? total : void 0;
665
+ const cached = cachedUsage(sid)?.cost;
666
+ if (cached !== void 0) return cached;
667
+ requestMessageSync(sid);
668
+ return void 0;
173
669
  } catch {
174
670
  return void 0;
175
671
  }
176
672
  },
177
673
  readSessionModel: (sid) => {
178
674
  if (!sid) return void 0;
675
+ try {
676
+ const dbModel = dbIndex?.info(sid)?.model;
677
+ if (dbModel !== void 0) {
678
+ rememberUsage(sid, { model: dbModel });
679
+ return dbModel;
680
+ }
681
+ } catch {
682
+ }
179
683
  try {
180
684
  const msgs = context.data.session.message.list(sid);
181
685
  if (msgs) {
182
686
  for (let i = msgs.length - 1; i >= 0; i--) {
183
687
  const m = msgs[i];
184
688
  if (m.type !== "assistant") continue;
185
- const model = m.model;
186
- if (typeof model === "string") return model;
187
- if (model?.id) return String(model.id);
689
+ const id = modelIdOf(m.model);
690
+ if (id) {
691
+ rememberUsage(sid, { model: id });
692
+ return id;
693
+ }
188
694
  }
189
695
  }
696
+ const sessionModel = modelIdOf(context.data.session.get(sid)?.model);
697
+ if (sessionModel) {
698
+ rememberUsage(sid, { model: sessionModel });
699
+ return sessionModel;
700
+ }
701
+ const listedModel = modelIdOf(listedSession(sid)?.model);
702
+ if (listedModel) {
703
+ rememberUsage(sid, { model: listedModel });
704
+ return listedModel;
705
+ }
706
+ const cached = cachedUsage(sid)?.model;
707
+ if (cached !== void 0) return cached;
708
+ requestMessageSync(sid);
190
709
  return void 0;
191
710
  } catch {
192
711
  return void 0;
@@ -198,7 +717,14 @@ function createPanelApi(context, settings) {
198
717
  session: {
199
718
  get: (sid) => {
200
719
  try {
201
- return context.data.session.get(sid);
720
+ const direct = context.data.session.get(sid);
721
+ if (direct) return direct;
722
+ const list = context.data.session.list?.();
723
+ if (Array.isArray(list)) {
724
+ const found = list.find((s) => s?.id === sid);
725
+ if (found) return found;
726
+ }
727
+ return void 0;
202
728
  } catch {
203
729
  return void 0;
204
730
  }
@@ -206,11 +732,19 @@ function createPanelApi(context, settings) {
206
732
  status: (sid) => {
207
733
  try {
208
734
  const st = context.data.session.status(sid);
209
- const type = st === "running" ? "busy" : st ?? "";
210
- return { type };
735
+ if (st) {
736
+ const type = st === "running" ? "busy" : st;
737
+ return { type };
738
+ }
739
+ } catch {
740
+ }
741
+ try {
742
+ const info = dbIndex?.info(sid);
743
+ if (info?.active === true) return { type: "busy" };
744
+ if (statusOfChild(info) !== void 0) return { type: "idle" };
211
745
  } catch {
212
- return void 0;
213
746
  }
747
+ return void 0;
214
748
  },
215
749
  messages: (sid) => {
216
750
  try {
@@ -229,6 +763,22 @@ function createPanelApi(context, settings) {
229
763
  return [];
230
764
  }
231
765
  return msg.content.map((p) => toV1Part(p));
766
+ },
767
+ resolveChild: (input) => {
768
+ try {
769
+ const sid = input.callId ? dbIndex?.resolveCall(input.parentId, input.callId) : void 0;
770
+ if (sid) return sid;
771
+ return dbIndex?.matchChild(input.parentId, input.agent, input.startedAt);
772
+ } catch {
773
+ return void 0;
774
+ }
775
+ },
776
+ listChildren: (parentId) => {
777
+ try {
778
+ return dbIndex?.enabled() ? dbIndex.children(parentId) : void 0;
779
+ } catch {
780
+ return void 0;
781
+ }
232
782
  }
233
783
  },
234
784
  event: {
@@ -239,9 +789,11 @@ function createPanelApi(context, settings) {
239
789
  for (const evt of ["session.tool.input.started", "session.tool.called", "session.tool.progress", "session.tool.success", "session.tool.failed"]) {
240
790
  unsubs.push(context.data.on(evt, (e) => {
241
791
  const part = toolEventToPart(e);
242
- if (!part) return;
243
- const evt2 = e.data;
244
- const sid = evt2?.sessionID !== void 0 ? String(evt2.sessionID) : void 0;
792
+ const evtData = e.data;
793
+ if (!part) {
794
+ return;
795
+ }
796
+ const sid = evtData?.sessionID !== void 0 ? String(evtData.sessionID) : void 0;
245
797
  if (!sid) return;
246
798
  cb({ type, scope: "global", payload: { part, sessionID: sid } });
247
799
  }));
@@ -298,45 +850,100 @@ function createPanelApi(context, settings) {
298
850
  };
299
851
  }
300
852
 
301
- // src/core/kv.ts
302
- var KV_PREFIX = "subagent_magazine";
303
- var SESSION_DATA_KEY = `${KV_PREFIX}.session_data`;
304
- var SETTING_KEYS = {
305
- lang: `${KV_PREFIX}.lang`,
306
- maxEntries: `${KV_PREFIX}.max_entries`,
307
- order: `${KV_PREFIX}.order`,
308
- scrollMode: `${KV_PREFIX}.scroll_mode`,
309
- open: `${KV_PREFIX}.open`,
310
- ttlDays: `${KV_PREFIX}.ttl_days`,
311
- border: `${KV_PREFIX}.border`
312
- };
313
- function loadSessionData(kv) {
314
- try {
315
- const raw = kv.get(SESSION_DATA_KEY, "{}");
316
- return JSON.parse(String(raw));
317
- } catch {
318
- return {};
853
+ // src/core/format.ts
854
+ function charColumns(c) {
855
+ const code = c.codePointAt(0) ?? 0;
856
+ if (code < 32) return 0;
857
+ if (code < 127) return 1;
858
+ if (code < 160) return 0;
859
+ if (code >= 4352 && code <= 4447 || code >= 11904 && code <= 42191 || code >= 44032 && code <= 55203 || code >= 63744 && code <= 64255 || code >= 65040 && code <= 65135 || code >= 65281 && code <= 65376 || code >= 65504 && code <= 65510 || code >= 127744 && code <= 128591 || code >= 131072 && code <= 262141)
860
+ return 2;
861
+ return 1;
862
+ }
863
+ function visualWidth(s) {
864
+ let w = 0;
865
+ for (const c of s) w += charColumns(c);
866
+ return w;
867
+ }
868
+ function truncate(text, maxCols) {
869
+ if (visualWidth(text) <= maxCols) return text;
870
+ let cols = 0;
871
+ let i = 0;
872
+ for (const c of text) {
873
+ const w = charColumns(c);
874
+ if (cols + w > maxCols - 1) break;
875
+ cols += w;
876
+ i += c.length;
319
877
  }
878
+ return text.slice(0, i) + "\u2026";
320
879
  }
321
- function saveSessionData(kv, data) {
322
- try {
323
- kv.set(SESSION_DATA_KEY, JSON.stringify(data));
324
- } catch {
880
+ function fmtDurationShort(ms, running) {
881
+ if (running && ms < 2e3) return "";
882
+ if (ms < 1e3) return (ms / 1e3).toFixed(2) + "s";
883
+ if (ms < 6e4) return (ms / 1e3).toFixed(2) + "s";
884
+ const m = Math.floor(ms / 6e4);
885
+ const s = Math.round(ms % 6e4 / 1e3);
886
+ return `${m}m${s}s`;
887
+ }
888
+ var TIME_FORMATS = ["short", "decimal", "clock", "compact", "seconds"];
889
+ var TIME_FORMAT_SAMPLES = {
890
+ decimal: "45.3s \xB7 9.5m \xB7 1.9h",
891
+ short: "45.32s \xB7 112m35s",
892
+ clock: "0:45 \xB7 1:52:35",
893
+ compact: "45s \xB7 1h52m",
894
+ seconds: "45s \xB7 6755s"
895
+ };
896
+ function fmtDuration(ms, running, mode = "short") {
897
+ if (running && ms < 2e3) return "";
898
+ const t = Number.isFinite(ms) && ms > 0 ? ms : 0;
899
+ switch (mode) {
900
+ case "short":
901
+ return fmtDurationShort(t, false);
902
+ case "compact": {
903
+ if (t < 6e4) return `${Math.floor(t / 1e3)}s`;
904
+ if (t < 36e5) return `${Math.floor(t / 6e4)}m${Math.floor(t % 6e4 / 1e3)}s`;
905
+ return `${Math.floor(t / 36e5)}h${Math.floor(t % 36e5 / 6e4)}m`;
906
+ }
907
+ case "clock": {
908
+ const s = Math.floor(t % 6e4 / 1e3);
909
+ if (t < 36e5) return `${Math.floor(t / 6e4)}:${String(s).padStart(2, "0")}`;
910
+ const m = Math.floor(t % 36e5 / 6e4);
911
+ return `${Math.floor(t / 36e5)}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
912
+ }
913
+ case "seconds":
914
+ return `${Math.floor(t / 1e3)}s`;
915
+ case "decimal":
916
+ default: {
917
+ if (t < 59950) return `${(t / 1e3).toFixed(1)}s`;
918
+ if (t < 3597e3) return `${(t / 6e4).toFixed(1)}m`;
919
+ return `${(t / 36e5).toFixed(1)}h`;
920
+ }
325
921
  }
326
922
  }
327
- function readTTLDays(kv) {
328
- const ttlDaysRaw = parseInt(String(kv.get(SETTING_KEYS.ttlDays, "3")), 10);
329
- return Number.isNaN(ttlDaysRaw) ? 3 : ttlDaysRaw;
923
+ function fmtTokens(n) {
924
+ if (n < 1e3) return `${n}`;
925
+ if (n < 1e6) return `${(n / 1e3).toFixed(1)}k`;
926
+ return `${(n / 1e6).toFixed(1)}M`;
927
+ }
928
+ function safeErrorMsg(err) {
929
+ if (!err) return "";
930
+ if (typeof err === "string") return err;
931
+ if (typeof err === "object") return String(err.message || err.code || "");
932
+ return "";
330
933
  }
331
934
 
332
935
  // src/_version.ts
333
- var PLUGIN_VERSION = "1.6.1";
936
+ var PLUGIN_VERSION = "1.7.1";
334
937
 
335
938
  // src/i18n.ts
336
939
  var ZH_T = {
337
940
  "panel.title": "\u5B50\u4EE3\u7406",
338
941
  "status.none": "\u6682\u65E0\u5B50\u4EE3\u7406",
339
942
  "agent.label": "\u4EE3\u7406",
943
+ "origin.label": "\u6765\u6E90",
944
+ "origin.tool": "\u5B50\u4EE3\u7406\u8C03\u7528",
945
+ "origin.sub": "\u884D\u751F\u4F1A\u8BDD",
946
+ "origin.background": "\u540E\u53F0",
340
947
  "status.label": "\u72B6\u6001",
341
948
  "time.label": "\u8017\u65F6",
342
949
  "tokens.label": "\u4E0A\u4E0B\u6587",
@@ -374,6 +981,7 @@ var ZH_T = {
374
981
  "clear.prompt": "\u786E\u5B9A\u6E05\u9664\u5F53\u524D\u4F1A\u8BDD\u6240\u6709\u5B50\u4EE3\u7406\u8BB0\u5F55\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u64A4\u9500\u3002",
375
982
  "clear.prompt_running": "\u5F53\u524D\u6709 {n} \u4E2A\u8FD0\u884C\u4E2D\u7684\u5B50\u4EE3\u7406\uFF0C\u6E05\u9664\u540E\u5C06\u4E0D\u53EF\u6062\u590D\u3002\u786E\u5B9A\u7EE7\u7EED\uFF1F",
376
983
  "clear.done": "\u5DF2\u6E05\u9664 {n} \u6761\u5B50\u4EE3\u7406\u8BB0\u5F55",
984
+ "clear.failed": "\u6E05\u9664\u5931\u8D25",
377
985
  "clear.empty": "\u5F53\u524D\u4F1A\u8BDD\u65E0\u5B50\u4EE3\u7406\u8BB0\u5F55",
378
986
  "cancel.no_session": "\u5B50\u4F1A\u8BDD ID \u4E0D\u53EF\u7528",
379
987
  "cancel.not_child": "\u76EE\u6807\u4E0D\u662F\u5B50\u4F1A\u8BDD",
@@ -384,12 +992,35 @@ var ZH_T = {
384
992
  "cancel.sent": "\u5DF2\u53D1\u9001\u53D6\u6D88\u6307\u4EE4",
385
993
  "cancel.failed": "\u53D6\u6D88\u5931\u8D25",
386
994
  "borderShown": "\u9762\u677F\u8FB9\u6846 \u5DF2\u663E\u793A",
387
- "borderHidden": "\u9762\u677F\u8FB9\u6846 \u5DF2\u9690\u85CF"
995
+ "borderHidden": "\u9762\u677F\u8FB9\u6846 \u5DF2\u9690\u85CF",
996
+ "settings.title": "SubAgent Magazine \u2014 \u8BBE\u7F6E",
997
+ "settings.section.general": "\u901A\u7528",
998
+ "settings.lang": "\u8BED\u8A00",
999
+ "settings.max": "\u6700\u5927\u6761\u76EE\u6570",
1000
+ "settings.order": "\u6392\u5E8F\u65B9\u5F0F",
1001
+ "settings.scroll": "\u6EDA\u52A8\u6A21\u5F0F",
1002
+ "settings.ttl": "\u4FDD\u7559\u671F\u9650",
1003
+ "settings.border": "\u9762\u677F\u8FB9\u6846",
1004
+ "settings.showEntryCost": "\u6BCF\u6761\u5B50\u4EE3\u7406\u663E\u793A\u8D39\u7528",
1005
+ "settings.showEntryTime": "\u5217\u8868\u4E2D\u663E\u793A\u8017\u65F6",
1006
+ "settings.showEntryTokens": "\u5217\u8868\u4E2D\u663E\u793A Token \u6570",
1007
+ "settings.timeFormat": "\u65F6\u95F4\u683C\u5F0F",
1008
+ "settings.showOrigin": "\u5217\u8868\u663E\u793A\u6765\u6E90\u6807\u8BB0",
1009
+ "settings.dbSync": "\u672C\u5730\u6570\u636E\u5E93\u8865\u5168",
1010
+ "settings.on": "\u5F00",
1011
+ "settings.off": "\u5173",
1012
+ "settings.saved": "\u5DF2\u4FDD\u5B58 {item}\uFF1A{value}",
1013
+ "settings.invalid": "\u65E0\u6548\u7684\u6570\u503C",
1014
+ "settings.close": "\u5173\u95ED"
388
1015
  };
389
1016
  var EN_T = {
390
1017
  "panel.title": "SubAgent",
391
1018
  "status.none": "No sub-agents yet",
392
1019
  "agent.label": "agent",
1020
+ "origin.label": "origin",
1021
+ "origin.tool": "sub-agent call",
1022
+ "origin.sub": "spawned session",
1023
+ "origin.background": "background",
393
1024
  "status.label": "status",
394
1025
  "time.label": "time",
395
1026
  "tokens.label": "tokens",
@@ -427,6 +1058,7 @@ var EN_T = {
427
1058
  "clear.prompt": "Clear all sub-agent records for this session? This cannot be undone.",
428
1059
  "clear.prompt_running": "{n} sub-agent(s) are still running. Clearing will discard them permanently. Continue?",
429
1060
  "clear.done": "Cleared {n} sub-agent record(s)",
1061
+ "clear.failed": "Clear failed",
430
1062
  "clear.empty": "No sub-agent records in this session",
431
1063
  "cancel.no_session": "Child session ID is unavailable",
432
1064
  "cancel.not_child": "Target is not a child session",
@@ -437,12 +1069,35 @@ var EN_T = {
437
1069
  "cancel.sent": "Cancel instruction sent",
438
1070
  "cancel.failed": "Cancellation failed",
439
1071
  "borderShown": "Panel border shown",
440
- "borderHidden": "Panel border hidden"
1072
+ "borderHidden": "Panel border hidden",
1073
+ "settings.title": "SubAgent Magazine \u2014 Settings",
1074
+ "settings.section.general": "General",
1075
+ "settings.lang": "Language",
1076
+ "settings.max": "Max entries",
1077
+ "settings.order": "Sort order",
1078
+ "settings.scroll": "Scroll mode",
1079
+ "settings.ttl": "Retention",
1080
+ "settings.border": "Panel border",
1081
+ "settings.showEntryCost": "Cost per sub-agent",
1082
+ "settings.showEntryTime": "Elapsed time in list",
1083
+ "settings.showEntryTokens": "Token count in list",
1084
+ "settings.showOrigin": "Origin indicators in list",
1085
+ "settings.timeFormat": "Time format",
1086
+ "settings.dbSync": "Local database enrichment",
1087
+ "settings.on": "on",
1088
+ "settings.off": "off",
1089
+ "settings.saved": "Saved {item}: {value}",
1090
+ "settings.invalid": "Invalid value",
1091
+ "settings.close": "Close"
441
1092
  };
442
1093
  var JA_T = {
443
1094
  "panel.title": "\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8",
444
1095
  "status.none": "\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u306A\u3057",
445
1096
  "agent.label": "\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8",
1097
+ "origin.label": "\u767A\u751F\u5143",
1098
+ "origin.tool": "\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u547C\u3073\u51FA\u3057",
1099
+ "origin.sub": "\u751F\u6210\u3055\u308C\u305F\u30BB\u30C3\u30B7\u30E7\u30F3",
1100
+ "origin.background": "\u30D0\u30C3\u30AF\u30B0\u30E9\u30A6\u30F3\u30C9",
446
1101
  "status.label": "\u72B6\u614B",
447
1102
  "time.label": "\u6642\u9593",
448
1103
  "tokens.label": "\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8",
@@ -480,6 +1135,7 @@ var JA_T = {
480
1135
  "clear.prompt": "\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u5168\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u8A18\u9332\u3092\u524A\u9664\u3057\u307E\u3059\u304B\uFF1F\u3053\u306E\u64CD\u4F5C\u306F\u5143\u306B\u623B\u305B\u307E\u305B\u3093\u3002",
481
1136
  "clear.prompt_running": "\u5B9F\u884C\u4E2D\u306E\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u304C {n} \u500B\u3042\u308A\u307E\u3059\u3002\u524A\u9664\u3059\u308B\u3068\u5FA9\u5143\u3067\u304D\u307E\u305B\u3093\u3002\u7D9A\u884C\u3057\u307E\u3059\u304B\uFF1F",
482
1137
  "clear.done": "{n} \u4EF6\u306E\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u8A18\u9332\u3092\u524A\u9664\u3057\u307E\u3057\u305F",
1138
+ "clear.failed": "\u30AF\u30EA\u30A2\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
483
1139
  "clear.empty": "\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306B\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u8A18\u9332\u306F\u3042\u308A\u307E\u305B\u3093",
484
1140
  "cancel.no_session": "\u5B50\u30BB\u30C3\u30B7\u30E7\u30F3 ID \u304C\u5229\u7528\u3067\u304D\u307E\u305B\u3093",
485
1141
  "cancel.not_child": "\u5BFE\u8C61\u306F\u5B50\u30BB\u30C3\u30B7\u30E7\u30F3\u3067\u306F\u3042\u308A\u307E\u305B\u3093",
@@ -490,12 +1146,35 @@ var JA_T = {
490
1146
  "cancel.sent": "\u30AD\u30E3\u30F3\u30BB\u30EB\u6307\u793A\u3092\u9001\u4FE1\u3057\u307E\u3057\u305F",
491
1147
  "cancel.failed": "\u30AD\u30E3\u30F3\u30BB\u30EB\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
492
1148
  "borderShown": "\u30D1\u30CD\u30EB\u67A0\u7DDA \u8868\u793A",
493
- "borderHidden": "\u30D1\u30CD\u30EB\u67A0\u7DDA \u975E\u8868\u793A"
1149
+ "borderHidden": "\u30D1\u30CD\u30EB\u67A0\u7DDA \u975E\u8868\u793A",
1150
+ "settings.title": "SubAgent Magazine \u2014 \u8A2D\u5B9A",
1151
+ "settings.section.general": "\u4E00\u822C",
1152
+ "settings.lang": "\u8A00\u8A9E",
1153
+ "settings.max": "\u6700\u5927\u8868\u793A\u6570",
1154
+ "settings.order": "\u4E26\u3073\u9806",
1155
+ "settings.scroll": "\u30B9\u30AF\u30ED\u30FC\u30EB",
1156
+ "settings.ttl": "\u4FDD\u6301\u671F\u9593",
1157
+ "settings.border": "\u30D1\u30CD\u30EB\u67A0",
1158
+ "settings.showEntryCost": "\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u6BCE\u306E\u8CBB\u7528",
1159
+ "settings.showEntryTime": "\u4E00\u89A7\u306B\u6240\u8981\u6642\u9593\u3092\u8868\u793A",
1160
+ "settings.showEntryTokens": "\u4E00\u89A7\u306B\u30C8\u30FC\u30AF\u30F3\u6570\u3092\u8868\u793A",
1161
+ "settings.showOrigin": "\u4E00\u89A7\u306B\u7531\u6765\u30DE\u30FC\u30AF\u3092\u8868\u793A",
1162
+ "settings.timeFormat": "\u6642\u9593\u5F62\u5F0F",
1163
+ "settings.dbSync": "\u30ED\u30FC\u30AB\u30EBDB\u88DC\u5B8C",
1164
+ "settings.on": "\u30AA\u30F3",
1165
+ "settings.off": "\u30AA\u30D5",
1166
+ "settings.saved": "\u4FDD\u5B58\u3057\u307E\u3057\u305F {item}\uFF1A{value}",
1167
+ "settings.invalid": "\u7121\u52B9\u306A\u5024\u3067\u3059",
1168
+ "settings.close": "\u9589\u3058\u308B"
494
1169
  };
495
1170
  var KO_T = {
496
1171
  "panel.title": "\uC11C\uBE0C \uC5D0\uC774\uC804\uD2B8",
497
1172
  "status.none": "\uC11C\uBE0C \uC5D0\uC774\uC804\uD2B8 \uC5C6\uC74C",
498
1173
  "agent.label": "\uC5D0\uC774\uC804\uD2B8",
1174
+ "origin.label": "\uCD9C\uCC98",
1175
+ "origin.tool": "\uD558\uC704 \uC5D0\uC774\uC804\uD2B8 \uD638\uCD9C",
1176
+ "origin.sub": "\uC0DD\uC131\uB41C \uC138\uC158",
1177
+ "origin.background": "\uBC31\uADF8\uB77C\uC6B4\uB4DC",
499
1178
  "status.label": "\uC0C1\uD0DC",
500
1179
  "time.label": "\uC2DC\uAC04",
501
1180
  "tokens.label": "\uCEE8\uD14D\uC2A4\uD2B8",
@@ -533,6 +1212,7 @@ var KO_T = {
533
1212
  "clear.prompt": "\uC774 \uC138\uC158\uC758 \uBAA8\uB4E0 \uC11C\uBE0C \uC5D0\uC774\uC804\uD2B8 \uAE30\uB85D\uC744 \uC0AD\uC81C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? \uC774 \uC791\uC5C5\uC740 \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
534
1213
  "clear.prompt_running": "\uC2E4\uD589 \uC911\uC778 \uC11C\uBE0C \uC5D0\uC774\uC804\uD2B8\uAC00 {n}\uAC1C \uC788\uC2B5\uB2C8\uB2E4. \uC0AD\uC81C\uD558\uBA74 \uBCF5\uAD6C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uACC4\uC18D\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?",
535
1214
  "clear.done": "\uC11C\uBE0C \uC5D0\uC774\uC804\uD2B8 \uAE30\uB85D {n}\uAC1C\uB97C \uC0AD\uC81C\uD588\uC2B5\uB2C8\uB2E4",
1215
+ "clear.failed": "\uC9C0\uC6B0\uAE30\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
536
1216
  "clear.empty": "\uC774 \uC138\uC158\uC5D0 \uC11C\uBE0C \uC5D0\uC774\uC804\uD2B8 \uAE30\uB85D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4",
537
1217
  "cancel.no_session": "\uD558\uC704 \uC138\uC158 ID\uB97C \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
538
1218
  "cancel.not_child": "\uB300\uC0C1\uC774 \uD558\uC704 \uC138\uC158\uC774 \uC544\uB2D9\uB2C8\uB2E4",
@@ -543,7 +1223,26 @@ var KO_T = {
543
1223
  "cancel.sent": "\uCDE8\uC18C \uC9C0\uC2DC\uB97C \uBCF4\uB0C8\uC2B5\uB2C8\uB2E4",
544
1224
  "cancel.failed": "\uCDE8\uC18C\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4",
545
1225
  "borderShown": "\uD328\uB110 \uD14C\uB450\uB9AC \uD45C\uC2DC",
546
- "borderHidden": "\uD328\uB110 \uD14C\uB450\uB9AC \uC228\uAE40"
1226
+ "borderHidden": "\uD328\uB110 \uD14C\uB450\uB9AC \uC228\uAE40",
1227
+ "settings.title": "SubAgent Magazine \u2014 \uC124\uC815",
1228
+ "settings.section.general": "\uC77C\uBC18",
1229
+ "settings.lang": "\uC5B8\uC5B4",
1230
+ "settings.max": "\uCD5C\uB300 \uD56D\uBAA9 \uC218",
1231
+ "settings.order": "\uC815\uB82C \uC21C\uC11C",
1232
+ "settings.scroll": "\uC2A4\uD06C\uB864 \uBAA8\uB4DC",
1233
+ "settings.ttl": "\uBCF4\uC874 \uAE30\uAC04",
1234
+ "settings.border": "\uD328\uB110 \uD14C\uB450\uB9AC",
1235
+ "settings.showEntryCost": "\uD558\uC704 \uC5D0\uC774\uC804\uD2B8\uBCC4 \uBE44\uC6A9",
1236
+ "settings.showEntryTime": "\uBAA9\uB85D\uC5D0 \uC18C\uC694 \uC2DC\uAC04 \uD45C\uC2DC",
1237
+ "settings.showEntryTokens": "\uBAA9\uB85D\uC5D0 \uD1A0\uD070 \uC218 \uD45C\uC2DC",
1238
+ "settings.showOrigin": "\uBAA9\uB85D\uC5D0 \uCD9C\uCC98 \uC544\uC774\uCF58 \uD45C\uC2DC",
1239
+ "settings.timeFormat": "\uC2DC\uAC04 \uD615\uC2DD",
1240
+ "settings.dbSync": "\uB85C\uCEEC DB \uBCF4\uC644",
1241
+ "settings.on": "\uCF2C",
1242
+ "settings.off": "\uB054",
1243
+ "settings.saved": "\uC800\uC7A5\uB428 {item}: {value}",
1244
+ "settings.invalid": "\uC798\uBABB\uB41C \uAC12",
1245
+ "settings.close": "\uB2EB\uAE30"
547
1246
  };
548
1247
  var LANGS = { zh: ZH_T, en: EN_T, ja: JA_T, ko: KO_T };
549
1248
  var LANG_META = [
@@ -579,11 +1278,308 @@ import { createSignal } from "solid-js";
579
1278
  var globalEntryCache = /* @__PURE__ */ new Map();
580
1279
  var [clearTick, setClearTick] = createSignal(0);
581
1280
 
1281
+ // src/panel/entry-map.ts
1282
+ function findSubEntryKey(entries, id, sessionId) {
1283
+ if (sessionId) {
1284
+ for (const [key, entry] of entries) {
1285
+ if (entry.sessionId === sessionId) return key;
1286
+ }
1287
+ }
1288
+ return entries.has(id) ? id : void 0;
1289
+ }
1290
+ function upsertSubEntry(entries, partial, now = Date.now()) {
1291
+ const key = findSubEntryKey(entries, partial.id, partial.sessionId) ?? partial.id;
1292
+ const existing = entries.get(key);
1293
+ const next = new Map(entries);
1294
+ if (key !== partial.id) next.delete(partial.id);
1295
+ const ended = partial.status === "done" || partial.status === "error" || partial.status === "cancelled";
1296
+ next.set(key, {
1297
+ ...existing ?? { startedAt: now },
1298
+ ...partial,
1299
+ id: existing?.id ?? partial.id,
1300
+ startedAt: existing?.startedAt || partial.startedAt || now,
1301
+ endedAt: ended ? existing?.endedAt ?? partial.endedAt ?? now : void 0
1302
+ });
1303
+ return next;
1304
+ }
1305
+ function statusRank(status) {
1306
+ if (status === "running") return 0;
1307
+ if (status === "cancel_requested") return 1;
1308
+ return 2;
1309
+ }
1310
+ var isTerminalStatus = (status) => statusRank(status) === 2;
1311
+ function minDefined(a, b) {
1312
+ if (a === void 0) return b;
1313
+ if (b === void 0) return a;
1314
+ return Math.min(a, b);
1315
+ }
1316
+ function maxDefined(a, b) {
1317
+ if (a === void 0) return b;
1318
+ if (b === void 0) return a;
1319
+ return Math.max(a, b);
1320
+ }
1321
+ function mergeSubEntry(base, incoming) {
1322
+ const successor = statusRank(incoming.status) > statusRank(base.status) ? incoming : base;
1323
+ const status = successor.status;
1324
+ const ended = isTerminalStatus(status);
1325
+ return {
1326
+ ...base,
1327
+ ...incoming,
1328
+ id: base.id,
1329
+ status,
1330
+ sessionId: incoming.sessionId ?? base.sessionId,
1331
+ startedAt: minDefined(base.startedAt, incoming.startedAt) ?? base.startedAt,
1332
+ endedAt: ended ? base.endedAt ?? incoming.endedAt ?? Date.now() : void 0,
1333
+ tokens: maxDefined(base.tokens, incoming.tokens),
1334
+ cost: maxDefined(base.cost, incoming.cost),
1335
+ model: incoming.model ?? base.model,
1336
+ todoTotal: maxDefined(base.todoTotal, incoming.todoTotal),
1337
+ todoDone: maxDefined(base.todoDone, incoming.todoDone),
1338
+ error: incoming.error ?? base.error,
1339
+ cancelRequestedAt: minDefined(base.cancelRequestedAt, incoming.cancelRequestedAt),
1340
+ abortAccepted: base.abortAccepted || incoming.abortAccepted || void 0,
1341
+ cancelReason: incoming.cancelReason ?? base.cancelReason
1342
+ };
1343
+ }
1344
+ function mergeSubEntries(base, incoming) {
1345
+ const out = /* @__PURE__ */ new Map();
1346
+ for (const entry of base) out.set(entry.id, entry);
1347
+ for (const entry of incoming) {
1348
+ const key = findSubEntryKey(out, entry.id, entry.sessionId);
1349
+ if (!key) {
1350
+ out.set(entry.id, entry);
1351
+ continue;
1352
+ }
1353
+ out.set(key, mergeSubEntry(out.get(key), entry));
1354
+ }
1355
+ return out;
1356
+ }
1357
+
1358
+ // src/v2/settings-menu.ts
1359
+ var TTL_VALUES = [3, 7, 14, 30, 0];
1360
+ var nextTick = () => new Promise((resolve) => setTimeout(resolve, 0));
1361
+ function openSettingsMenu(context, api, signals) {
1362
+ runSettingsMenu(context, api, signals).catch((err) => {
1363
+ try {
1364
+ context.ui.toast.show({ message: String(err), variant: "error" });
1365
+ } catch {
1366
+ }
1367
+ });
1368
+ }
1369
+ async function runSettingsMenu(context, api, signals) {
1370
+ const t = createT(() => signals.lang());
1371
+ const kv = api.kv;
1372
+ const onOff = (v) => v ? t("settings.on") : t("settings.off");
1373
+ const langLabel = () => LANG_META.find((m) => m.code === signals.lang())?.label ?? signals.lang();
1374
+ const ttlTitle = (d) => d === 0 ? t("ttl.unlimited") : t(d === 3 ? "ttl.3d" : d === 7 ? "ttl.7d" : d === 14 ? "ttl.14d" : "ttl.30d");
1375
+ const ttlLabel = () => ttlTitle(readTTLDays(kv));
1376
+ const orderLabel = () => signals.sortOrder() === "desc" ? t("order.desc") : t("order.asc");
1377
+ const scrollLabel = () => signals.scrollMode() === "wheel" ? t("scroll.wheel") : t("scroll.click");
1378
+ const toast = (item, value) => context.ui.toast.show({ message: t("settings.saved", { item, value }) });
1379
+ while (true) {
1380
+ const category = t("settings.section.general");
1381
+ const choice = await context.ui.dialog.select({
1382
+ title: t("settings.title"),
1383
+ options: [
1384
+ { title: t("settings.lang"), value: "lang", description: langLabel(), category },
1385
+ { title: t("settings.max"), value: "max", description: String(signals.maxEntries()), category },
1386
+ { title: t("settings.order"), value: "order", description: orderLabel(), category },
1387
+ { title: t("settings.scroll"), value: "scroll", description: scrollLabel(), category },
1388
+ { title: t("settings.ttl"), value: "ttl", description: ttlLabel(), category },
1389
+ { title: t("settings.border"), value: "border", description: onOff(signals.borderVisible()), category },
1390
+ { title: t("settings.showEntryCost"), value: "cost", description: onOff(signals.showEntryCost()), category },
1391
+ { title: t("settings.showEntryTime"), value: "time", description: onOff(signals.showEntryTime()), category },
1392
+ { title: t("settings.timeFormat"), value: "timefmt", description: TIME_FORMAT_SAMPLES[signals.timeFormat()], category },
1393
+ { title: t("settings.showEntryTokens"), value: "tokens", description: onOff(signals.showEntryTokens()), category },
1394
+ { title: t("settings.showOrigin"), value: "origin", description: onOff(signals.showOrigin()), category },
1395
+ { title: t("settings.dbSync"), value: "dbsync", description: onOff(signals.dbSync()), category },
1396
+ { title: t("settings.close"), value: "close" }
1397
+ ]
1398
+ });
1399
+ if (choice === void 0 || choice === "close") return;
1400
+ if (choice === "lang") {
1401
+ const picked = await context.ui.dialog.select({
1402
+ title: t("settings.lang"),
1403
+ options: LANG_META.map((m) => ({ title: m.label, value: m.code })),
1404
+ current: signals.lang()
1405
+ });
1406
+ if (picked) {
1407
+ signals.setLang(picked);
1408
+ kv.set(SETTING_KEYS.lang, picked);
1409
+ toast(t("settings.lang"), LANG_META.find((m) => m.code === picked)?.label ?? picked);
1410
+ }
1411
+ }
1412
+ if (choice === "max") {
1413
+ const val = await context.ui.dialog.prompt({
1414
+ title: t("settings.max"),
1415
+ message: "1\u201350",
1416
+ placeholder: String(signals.maxEntries())
1417
+ });
1418
+ if (val !== void 0) {
1419
+ const n = parseInt(val.trim(), 10);
1420
+ if (Number.isNaN(n)) {
1421
+ context.ui.toast.show({ message: t("settings.invalid"), variant: "warning" });
1422
+ } else {
1423
+ const clamped = Math.max(1, Math.min(50, n));
1424
+ signals.setMaxEntries(clamped);
1425
+ kv.set(SETTING_KEYS.maxEntries, clamped);
1426
+ toast(t("settings.max"), String(clamped));
1427
+ }
1428
+ }
1429
+ }
1430
+ if (choice === "order") {
1431
+ const picked = await context.ui.dialog.select({
1432
+ title: t("settings.order"),
1433
+ options: [
1434
+ { title: t("order.desc"), value: "desc" },
1435
+ { title: t("order.asc"), value: "asc" }
1436
+ ],
1437
+ current: signals.sortOrder()
1438
+ });
1439
+ if (picked) {
1440
+ signals.setSortOrder(picked);
1441
+ kv.set(SETTING_KEYS.order, picked);
1442
+ toast(t("settings.order"), picked === "desc" ? t("order.desc") : t("order.asc"));
1443
+ }
1444
+ }
1445
+ if (choice === "scroll") {
1446
+ const picked = await context.ui.dialog.select({
1447
+ title: t("settings.scroll"),
1448
+ options: [
1449
+ { title: t("scroll.wheel"), value: "wheel" },
1450
+ { title: t("scroll.click"), value: "click" }
1451
+ ],
1452
+ current: signals.scrollMode()
1453
+ });
1454
+ if (picked) {
1455
+ signals.setScrollMode(picked);
1456
+ kv.set(SETTING_KEYS.scrollMode, picked);
1457
+ toast(t("settings.scroll"), picked === "wheel" ? t("scroll.wheel") : t("scroll.click"));
1458
+ }
1459
+ }
1460
+ if (choice === "ttl") {
1461
+ const picked = await context.ui.dialog.select({
1462
+ title: t("settings.ttl"),
1463
+ options: TTL_VALUES.map((d) => ({ title: ttlTitle(d), value: d })),
1464
+ current: readTTLDays(kv)
1465
+ });
1466
+ if (picked !== void 0) {
1467
+ kv.set(SETTING_KEYS.ttlDays, String(picked));
1468
+ toast(t("settings.ttl"), ttlTitle(picked));
1469
+ }
1470
+ }
1471
+ if (choice === "border") {
1472
+ const picked = await context.ui.dialog.select({
1473
+ title: t("settings.border"),
1474
+ options: [
1475
+ { title: t("settings.on"), value: true },
1476
+ { title: t("settings.off"), value: false }
1477
+ ],
1478
+ current: signals.borderVisible()
1479
+ });
1480
+ if (picked !== void 0) {
1481
+ signals.setBorderVisible(picked);
1482
+ kv.set(SETTING_KEYS.border, picked);
1483
+ toast(t("settings.border"), onOff(picked));
1484
+ }
1485
+ }
1486
+ if (choice === "cost") {
1487
+ const picked = await context.ui.dialog.select({
1488
+ title: t("settings.showEntryCost"),
1489
+ options: [
1490
+ { title: t("settings.on"), value: true },
1491
+ { title: t("settings.off"), value: false }
1492
+ ],
1493
+ current: signals.showEntryCost()
1494
+ });
1495
+ if (picked !== void 0) {
1496
+ signals.setShowEntryCost(picked);
1497
+ kv.set(SETTING_KEYS.showEntryCost, picked);
1498
+ toast(t("settings.showEntryCost"), onOff(picked));
1499
+ }
1500
+ }
1501
+ if (choice === "time") {
1502
+ const picked = await context.ui.dialog.select({
1503
+ title: t("settings.showEntryTime"),
1504
+ options: [
1505
+ { title: t("settings.on"), value: true },
1506
+ { title: t("settings.off"), value: false }
1507
+ ],
1508
+ current: signals.showEntryTime()
1509
+ });
1510
+ if (picked !== void 0) {
1511
+ signals.setShowEntryTime(picked);
1512
+ kv.set(SETTING_KEYS.showEntryTime, picked);
1513
+ toast(t("settings.showEntryTime"), onOff(picked));
1514
+ }
1515
+ }
1516
+ if (choice === "timefmt") {
1517
+ const picked = await context.ui.dialog.select({
1518
+ title: t("settings.timeFormat"),
1519
+ options: TIME_FORMATS.map((f) => ({ title: `${f} \u2014 ${TIME_FORMAT_SAMPLES[f]}`, value: f })),
1520
+ current: signals.timeFormat()
1521
+ });
1522
+ if (picked) {
1523
+ signals.setTimeFormat(picked);
1524
+ kv.set(SETTING_KEYS.timeFormat, picked);
1525
+ toast(t("settings.timeFormat"), TIME_FORMAT_SAMPLES[picked]);
1526
+ }
1527
+ }
1528
+ if (choice === "tokens") {
1529
+ const picked = await context.ui.dialog.select({
1530
+ title: t("settings.showEntryTokens"),
1531
+ options: [
1532
+ { title: t("settings.on"), value: true },
1533
+ { title: t("settings.off"), value: false }
1534
+ ],
1535
+ current: signals.showEntryTokens()
1536
+ });
1537
+ if (picked !== void 0) {
1538
+ signals.setShowEntryTokens(picked);
1539
+ kv.set(SETTING_KEYS.showEntryTokens, picked);
1540
+ toast(t("settings.showEntryTokens"), onOff(picked));
1541
+ }
1542
+ }
1543
+ if (choice === "origin") {
1544
+ const picked = await context.ui.dialog.select({
1545
+ title: t("settings.showOrigin"),
1546
+ options: [
1547
+ { title: t("settings.on"), value: true },
1548
+ { title: t("settings.off"), value: false }
1549
+ ],
1550
+ current: signals.showOrigin()
1551
+ });
1552
+ if (picked !== void 0) {
1553
+ signals.setShowOrigin(picked);
1554
+ kv.set(SETTING_KEYS.showOrigin, picked);
1555
+ toast(t("settings.showOrigin"), onOff(picked));
1556
+ }
1557
+ }
1558
+ if (choice === "dbsync") {
1559
+ const picked = await context.ui.dialog.select({
1560
+ title: t("settings.dbSync"),
1561
+ options: [
1562
+ { title: t("settings.on"), value: true },
1563
+ { title: t("settings.off"), value: false }
1564
+ ],
1565
+ current: signals.dbSync()
1566
+ });
1567
+ if (picked !== void 0) {
1568
+ signals.setDbSync(picked);
1569
+ kv.set(SETTING_KEYS.dbSync, picked);
1570
+ toast(t("settings.dbSync"), onOff(picked));
1571
+ }
1572
+ }
1573
+ await nextTick();
1574
+ }
1575
+ }
1576
+
582
1577
  // src/v2/commands.ts
583
1578
  function makeCommands(context, api, signals) {
584
1579
  const t = createT(() => signals.lang());
585
1580
  const kv = api.kv;
586
1581
  const clampMax = (n) => Math.max(1, Math.min(50, n));
1582
+ const onOff = (v) => v ? t("settings.on") : t("settings.off");
587
1583
  const resolveParent = (sid) => {
588
1584
  try {
589
1585
  const session = api.session.get(sid);
@@ -670,6 +1666,125 @@ function makeCommands(context, api, signals) {
670
1666
  api.ui.toast(`Max entries: ${n}`);
671
1667
  }
672
1668
  },
1669
+ {
1670
+ id: "opencode-subagent-magazine.subagent.config",
1671
+ title: "SubAgent Magazine: Settings",
1672
+ description: "Show current settings (change each via its own command)",
1673
+ slash: { name: "subagent-config" },
1674
+ palette: true,
1675
+ run: () => {
1676
+ const ttl = readTTLDays(kv);
1677
+ const langLabel = LANG_META.find((m) => m.code === signals.lang())?.label ?? signals.lang();
1678
+ const message = [
1679
+ `${t("settings.lang")}: ${langLabel}`,
1680
+ `${t("settings.max")}: ${signals.maxEntries()}`,
1681
+ `${t("settings.order")}: ${signals.sortOrder() === "desc" ? t("order.desc") : t("order.asc")}`,
1682
+ `${t("settings.scroll")}: ${signals.scrollMode() === "wheel" ? t("scroll.wheel") : t("scroll.click")}`,
1683
+ `${t("settings.ttl")}: ${ttl === 0 ? t("ttl.unlimited") : `${ttl}d`}`,
1684
+ `${t("settings.border")}: ${onOff(signals.borderVisible())}`,
1685
+ `${t("settings.showEntryCost")}: ${onOff(signals.showEntryCost())}`,
1686
+ `${t("settings.showEntryTime")}: ${onOff(signals.showEntryTime())}`,
1687
+ `${t("settings.showEntryTokens")}: ${onOff(signals.showEntryTokens())}`,
1688
+ `${t("settings.showOrigin")}: ${onOff(signals.showOrigin())}`,
1689
+ `${t("settings.timeFormat")}: ${TIME_FORMAT_SAMPLES[signals.timeFormat()]}`,
1690
+ `${t("settings.dbSync")}: ${onOff(signals.dbSync())}`
1691
+ ].join("\n");
1692
+ context.ui.toast.show({ title: t("settings.title"), message });
1693
+ }
1694
+ },
1695
+ {
1696
+ id: "opencode-subagent-magazine.subagent.sections",
1697
+ title: "SubAgent Magazine: Settings Menu",
1698
+ description: "Open the interactive settings menu (Esc to close)",
1699
+ slash: { name: "subagent-sections" },
1700
+ palette: true,
1701
+ run: () => {
1702
+ openSettingsMenu(context, api, signals);
1703
+ }
1704
+ },
1705
+ {
1706
+ id: "opencode-subagent-magazine.subagent.cost",
1707
+ title: "SubAgent Magazine: Toggle Entry Cost",
1708
+ description: "Show or hide the per-sub-agent cost in the sidebar",
1709
+ slash: { name: "subagent-cost" },
1710
+ palette: true,
1711
+ run: () => {
1712
+ const v = !signals.showEntryCost();
1713
+ signals.setShowEntryCost(v);
1714
+ kv.set(SETTING_KEYS.showEntryCost, v);
1715
+ api.ui.toast(`${t("settings.showEntryCost")}: ${onOff(v)}`);
1716
+ }
1717
+ },
1718
+ {
1719
+ id: "opencode-subagent-magazine.subagent.time",
1720
+ title: "SubAgent Magazine: Toggle Entry Time",
1721
+ description: "Show or hide the elapsed time in the sidebar list",
1722
+ slash: { name: "subagent-time" },
1723
+ palette: true,
1724
+ run: () => {
1725
+ const v = !signals.showEntryTime();
1726
+ signals.setShowEntryTime(v);
1727
+ kv.set(SETTING_KEYS.showEntryTime, v);
1728
+ api.ui.toast(`${t("settings.showEntryTime")}: ${onOff(v)}`);
1729
+ }
1730
+ },
1731
+ {
1732
+ id: "opencode-subagent-magazine.subagent.time-format",
1733
+ title: "SubAgent Magazine: Time Format",
1734
+ description: "Set how elapsed time is displayed (short / decimal / clock / compact / seconds)",
1735
+ slash: { name: "subagent-time-format" },
1736
+ palette: true,
1737
+ run: async () => {
1738
+ const picked = await context.ui.dialog.select({
1739
+ title: t("settings.timeFormat"),
1740
+ options: TIME_FORMATS.map((f) => ({ title: `${f} \u2014 ${TIME_FORMAT_SAMPLES[f]}`, value: f })),
1741
+ current: signals.timeFormat()
1742
+ });
1743
+ if (!picked) return;
1744
+ signals.setTimeFormat(picked);
1745
+ kv.set(SETTING_KEYS.timeFormat, picked);
1746
+ api.ui.toast(`${t("settings.timeFormat")}: ${TIME_FORMAT_SAMPLES[picked]}`);
1747
+ }
1748
+ },
1749
+ {
1750
+ id: "opencode-subagent-magazine.subagent.tokens",
1751
+ title: "SubAgent Magazine: Toggle Entry Tokens",
1752
+ description: "Show or hide the token count in the sidebar list",
1753
+ slash: { name: "subagent-tokens" },
1754
+ palette: true,
1755
+ run: () => {
1756
+ const v = !signals.showEntryTokens();
1757
+ signals.setShowEntryTokens(v);
1758
+ kv.set(SETTING_KEYS.showEntryTokens, v);
1759
+ api.ui.toast(`${t("settings.showEntryTokens")}: ${onOff(v)}`);
1760
+ }
1761
+ },
1762
+ {
1763
+ id: "opencode-subagent-magazine.subagent.origin",
1764
+ title: "SubAgent Magazine: Toggle Origin Indicators",
1765
+ description: "Show or hide origin markers (\u21E2 background / \u21B3 spawned) in the sidebar list",
1766
+ slash: { name: "subagent-origin" },
1767
+ palette: true,
1768
+ run: () => {
1769
+ const v = !signals.showOrigin();
1770
+ signals.setShowOrigin(v);
1771
+ kv.set(SETTING_KEYS.showOrigin, v);
1772
+ api.ui.toast(`${t("settings.showOrigin")}: ${onOff(v)}`);
1773
+ }
1774
+ },
1775
+ {
1776
+ id: "opencode-subagent-magazine.subagent.db",
1777
+ title: "SubAgent Magazine: Toggle Local DB",
1778
+ description: "Enrich sub-agent entries from the local OpenCode database (V2)",
1779
+ slash: { name: "subagent-db" },
1780
+ palette: true,
1781
+ run: () => {
1782
+ const v = !signals.dbSync();
1783
+ signals.setDbSync(v);
1784
+ kv.set(SETTING_KEYS.dbSync, v);
1785
+ api.ui.toast(`${t("settings.dbSync")}: ${onOff(v)}`);
1786
+ }
1787
+ },
673
1788
  {
674
1789
  id: "opencode-subagent-magazine.subagent.version",
675
1790
  title: "SubAgent Magazine: Version",
@@ -712,27 +1827,43 @@ function makeCommands(context, api, signals) {
712
1827
  }
713
1828
  }
714
1829
  if (count > 0) {
1830
+ const ok = () => api.ui.toast(signals.lang() === "zh" ? `\u5DF2\u6807\u8BB0 ${count} \u4E2A\u8FD0\u884C\u4E2D\u7684\u6761\u76EE\u4E3A\u5B8C\u6210` : `Marked ${count} running entries as done`);
1831
+ const fail = () => {
1832
+ try {
1833
+ api.ui.toast(t("clear.failed"));
1834
+ } catch {
1835
+ }
1836
+ };
715
1837
  try {
716
- const data = loadSessionData(kv);
717
1838
  const { parentSid, isChild } = resolveParent(sid);
718
- if (isChild) {
719
- if (!data[parentSid]) data[parentSid] = { ts: Date.now(), entries: [], scroll: 0, expanded: "", children: {} };
720
- if (!data[parentSid].children) data[parentSid].children = {};
721
- if (!data[parentSid].children[sid]) data[parentSid].children[sid] = { scroll: 0, expanded: "", entries: [] };
722
- data[parentSid].children[sid] = { ...data[parentSid].children[sid], entries: [...entries.values()] };
1839
+ const done = updateSessionData(kv, (data) => {
1840
+ if (isChild) {
1841
+ if (!data[parentSid]) data[parentSid] = { ts: Date.now(), entries: [], scroll: 0, expanded: "", children: {} };
1842
+ if (!data[parentSid].children) data[parentSid].children = {};
1843
+ const prev = data[parentSid].children[sid]?.entries ?? [];
1844
+ const merged = [...mergeSubEntries(prev, entries.values()).values()].map(
1845
+ (e) => e.status === "running" || e.status === "cancel_requested" ? { ...e, status: "done", endedAt: e.endedAt ?? Date.now() } : e
1846
+ );
1847
+ data[parentSid].children[sid] = {
1848
+ ...data[parentSid].children[sid] ?? { scroll: 0, expanded: "" },
1849
+ entries: merged
1850
+ };
1851
+ } else {
1852
+ const rec = data[parentSid] ?? { ts: Date.now(), entries: [], scroll: 0, expanded: "", children: {} };
1853
+ const merged = [...mergeSubEntries(rec.entries ?? [], entries.values()).values()].map(
1854
+ (e) => e.status === "running" || e.status === "cancel_requested" ? { ...e, status: "done", endedAt: e.endedAt ?? Date.now() } : e
1855
+ );
1856
+ data[parentSid] = { ...rec, ts: Date.now(), entries: merged, children: rec.children ?? {} };
1857
+ }
1858
+ });
1859
+ if (done && typeof done.then === "function") {
1860
+ void done.then(ok, fail);
723
1861
  } else {
724
- data[sid] = {
725
- ts: Date.now(),
726
- entries: [...entries.values()],
727
- scroll: data[sid]?.scroll ?? 0,
728
- expanded: data[sid]?.expanded ?? "",
729
- children: data[sid]?.children ?? {}
730
- };
1862
+ ok();
731
1863
  }
732
- saveSessionData(kv, data);
733
1864
  } catch {
1865
+ fail();
734
1866
  }
735
- api.ui.toast(signals.lang() === "zh" ? `\u5DF2\u6807\u8BB0 ${count} \u4E2A\u8FD0\u884C\u4E2D\u7684\u6761\u76EE\u4E3A\u5B8C\u6210` : `Marked ${count} running entries as done`);
736
1867
  } else {
737
1868
  api.ui.toast(signals.lang() === "zh" ? "\u6CA1\u6709\u9700\u8981\u6E05\u7406\u7684\u8FD0\u884C\u4E2D\u6761\u76EE" : "No running entries to clear");
738
1869
  }
@@ -789,32 +1920,45 @@ function makeCommands(context, api, signals) {
789
1920
  });
790
1921
  if (choice !== "yes") return;
791
1922
  try {
792
- const data = loadSessionData(kv);
793
1923
  let count = 0;
794
- if (parentID) {
795
- if (data[parentID]?.children?.[sid]) {
796
- const child = data[parentID].children[sid];
797
- const ids = child.entries?.map((e) => e.id) ?? [];
798
- count = ids.length;
799
- child.entries = [];
800
- child.scroll = 0;
801
- child.expanded = "";
802
- child.clearedIds = [.../* @__PURE__ */ new Set([...child.clearedIds ?? [], ...ids])];
1924
+ const finish = () => {
1925
+ globalEntryCache.delete(sid);
1926
+ setClearTick((v) => v + 1);
1927
+ api.ui.toast(t("clear.done", { n: count }));
1928
+ };
1929
+ const done = updateSessionData(kv, (data) => {
1930
+ if (parentID) {
1931
+ const child = data[parentID]?.children?.[sid];
1932
+ if (child) {
1933
+ const ids = child.entries?.map((e) => e.id) ?? [];
1934
+ count = ids.length;
1935
+ child.entries = [];
1936
+ child.scroll = 0;
1937
+ child.expanded = "";
1938
+ child.clearedIds = [.../* @__PURE__ */ new Set([...child.clearedIds ?? [], ...ids])];
1939
+ }
1940
+ } else {
1941
+ const rec = data[sid];
1942
+ if (rec) {
1943
+ const ids = rec.entries?.map((e) => e.id) ?? [];
1944
+ count = ids.length;
1945
+ rec.entries = [];
1946
+ rec.scroll = 0;
1947
+ rec.expanded = "";
1948
+ rec.clearedIds = [.../* @__PURE__ */ new Set([...rec.clearedIds ?? [], ...ids])];
1949
+ }
803
1950
  }
1951
+ });
1952
+ if (done && typeof done.then === "function") {
1953
+ void done.then(finish, () => {
1954
+ try {
1955
+ api.ui.toast(t("clear.failed"));
1956
+ } catch {
1957
+ }
1958
+ });
804
1959
  } else {
805
- count = data[sid]?.entries?.length ?? 0;
806
- if (data[sid]) {
807
- const ids = data[sid].entries?.map((e) => e.id) ?? [];
808
- data[sid].entries = [];
809
- data[sid].scroll = 0;
810
- data[sid].expanded = "";
811
- data[sid].clearedIds = [.../* @__PURE__ */ new Set([...data[sid].clearedIds ?? [], ...ids])];
812
- }
1960
+ finish();
813
1961
  }
814
- saveSessionData(kv, data);
815
- globalEntryCache.delete(sid);
816
- setClearTick((v) => v + 1);
817
- api.ui.toast(t("clear.done", { n: count }));
818
1962
  } catch {
819
1963
  }
820
1964
  }
@@ -960,52 +2104,10 @@ async function copyText(text) {
960
2104
 
961
2105
  // src/core/types.ts
962
2106
  var SUBAGENT_TOOLS = /* @__PURE__ */ new Set(["task", "subagent", "delegate", "call_omo_agent"]);
963
-
964
- // src/core/format.ts
965
- function charColumns(c) {
966
- const code = c.codePointAt(0) ?? 0;
967
- if (code < 32) return 0;
968
- if (code < 127) return 1;
969
- if (code < 160) return 0;
970
- if (code >= 4352 && code <= 4447 || code >= 11904 && code <= 42191 || code >= 44032 && code <= 55203 || code >= 63744 && code <= 64255 || code >= 65040 && code <= 65135 || code >= 65281 && code <= 65376 || code >= 65504 && code <= 65510 || code >= 127744 && code <= 128591 || code >= 131072 && code <= 262141)
971
- return 2;
972
- return 1;
973
- }
974
- function visualWidth(s) {
975
- let w = 0;
976
- for (const c of s) w += charColumns(c);
977
- return w;
978
- }
979
- function truncate(text, maxCols) {
980
- if (visualWidth(text) <= maxCols) return text;
981
- let cols = 0;
982
- let i = 0;
983
- for (const c of text) {
984
- const w = charColumns(c);
985
- if (cols + w > maxCols - 1) break;
986
- cols += w;
987
- i += c.length;
988
- }
989
- return text.slice(0, i) + "\u2026";
990
- }
991
- function fmtDurationShort(ms, running) {
992
- if (running && ms < 2e3) return "";
993
- if (ms < 1e3) return (ms / 1e3).toFixed(2) + "s";
994
- if (ms < 6e4) return (ms / 1e3).toFixed(2) + "s";
995
- const m = Math.floor(ms / 6e4);
996
- const s = Math.round(ms % 6e4 / 1e3);
997
- return `${m}m${s}s`;
998
- }
999
- function fmtTokens(n) {
1000
- if (n < 1e3) return `${n}`;
1001
- if (n < 1e6) return `${(n / 1e3).toFixed(1)}k`;
1002
- return `${(n / 1e6).toFixed(1)}M`;
1003
- }
1004
- function safeErrorMsg(err) {
1005
- if (!err) return "";
1006
- if (typeof err === "string") return err;
1007
- if (typeof err === "object") return String(err.message || err.code || "");
1008
- return "";
2107
+ function isBackgroundInput(input) {
2108
+ if (!input || typeof input !== "object") return false;
2109
+ const i = input;
2110
+ return i.background === true || i.run_in_background === true;
1009
2111
  }
1010
2112
 
1011
2113
  // src/core/color.ts
@@ -1081,34 +2183,20 @@ function isDirectChildSession(currentSessionId, eventSessionId, eventSession) {
1081
2183
  return eventSessionId !== currentSessionId && eventSession?.parentID === currentSessionId;
1082
2184
  }
1083
2185
 
1084
- // src/panel/entry-map.ts
1085
- function findSubEntryKey(entries, id, sessionId) {
1086
- if (sessionId) {
1087
- for (const [key, entry] of entries) {
1088
- if (entry.sessionId === sessionId) return key;
1089
- }
1090
- }
1091
- return entries.has(id) ? id : void 0;
1092
- }
1093
- function upsertSubEntry(entries, partial, now = Date.now()) {
1094
- const key = findSubEntryKey(entries, partial.id, partial.sessionId) ?? partial.id;
1095
- const existing = entries.get(key);
1096
- const next = new Map(entries);
1097
- if (key !== partial.id) next.delete(partial.id);
1098
- const ended = partial.status === "done" || partial.status === "error" || partial.status === "cancelled";
1099
- next.set(key, {
1100
- ...existing ?? { startedAt: now },
1101
- ...partial,
1102
- id: existing?.id ?? partial.id,
1103
- startedAt: existing?.startedAt || partial.startedAt || now,
1104
- endedAt: ended ? existing?.endedAt || now : void 0
1105
- });
1106
- return next;
1107
- }
1108
-
1109
2186
  // src/panel/SubAgentPanel.tsx
1110
2187
  var LEFT_PAD = 4;
1111
2188
  var INDENT = 2;
2189
+ function partMetadata(part, st) {
2190
+ const partMeta = part?.metadata;
2191
+ if (partMeta && Object.keys(partMeta).length > 0) return partMeta;
2192
+ const stateMeta = st?.metadata;
2193
+ return stateMeta && Object.keys(stateMeta).length > 0 ? stateMeta : void 0;
2194
+ }
2195
+ function validModel(model) {
2196
+ if (typeof model !== "string") return false;
2197
+ const s = model.trim();
2198
+ return s.length > 0 && s !== "[object Object]" && !s.startsWith("{");
2199
+ }
1112
2200
  function SubAgentPanel(props) {
1113
2201
  const t = createT(() => props.lang());
1114
2202
  const SESSION_DATA_KEY2 = `${KV_PREFIX}.session_data`;
@@ -1123,11 +2211,32 @@ function SubAgentPanel(props) {
1123
2211
  return {};
1124
2212
  }
1125
2213
  };
1126
- const saveSessionData2 = (data) => {
1127
- try {
1128
- props.api.kv.set(SESSION_DATA_KEY2, JSON.stringify(data));
1129
- } catch {
1130
- }
2214
+ const mutateSessionData = (mutator) => {
2215
+ void Promise.resolve(updateSessionData(props.api.kv, mutator)).catch(() => {
2216
+ });
2217
+ };
2218
+ const ensureRootRecord = (data, sid) => {
2219
+ const rec = data[sid];
2220
+ if (rec) return rec;
2221
+ const created = {
2222
+ ts: Date.now(),
2223
+ entries: [],
2224
+ scroll: 0,
2225
+ expanded: "",
2226
+ children: {}
2227
+ };
2228
+ data[sid] = created;
2229
+ return created;
2230
+ };
2231
+ const ensureChildRecord = (data, parentSid, sid) => {
2232
+ const parent = ensureRootRecord(data, parentSid);
2233
+ if (!parent.children) parent.children = {};
2234
+ if (!parent.children[sid]) parent.children[sid] = {
2235
+ scroll: 0,
2236
+ expanded: "",
2237
+ entries: []
2238
+ };
2239
+ return parent.children[sid];
1131
2240
  };
1132
2241
  const resolveParent = (sid) => {
1133
2242
  try {
@@ -1169,129 +2278,71 @@ function SubAgentPanel(props) {
1169
2278
  clearTimeout(persistTimer);
1170
2279
  persistTimer = setTimeout(() => {
1171
2280
  try {
1172
- const data = loadSessionData2();
2281
+ mutateSessionData((data) => {
2282
+ const {
2283
+ parentSid,
2284
+ isChild
2285
+ } = resolveParent(sid);
2286
+ if (isChild) {
2287
+ const parent = ensureRootRecord(data, parentSid);
2288
+ const child = ensureChildRecord(data, parentSid, sid);
2289
+ child.entries = [...mergeSubEntries(child.entries ?? [], entries2.values()).values()];
2290
+ parent.ts = Date.now();
2291
+ } else {
2292
+ const rec = ensureRootRecord(data, sid);
2293
+ rec.ts = Date.now();
2294
+ rec.entries = [...mergeSubEntries(rec.entries ?? [], entries2.values()).values()];
2295
+ }
2296
+ });
2297
+ } catch {
2298
+ }
2299
+ }, 200);
2300
+ };
2301
+ const persistScroll = (sid, scroll) => {
2302
+ try {
2303
+ mutateSessionData((data) => {
1173
2304
  const {
1174
2305
  parentSid,
1175
2306
  isChild
1176
2307
  } = resolveParent(sid);
1177
2308
  if (isChild) {
1178
- if (!data[parentSid]) data[parentSid] = {
1179
- ts: Date.now(),
1180
- entries: [],
1181
- scroll: 0,
1182
- expanded: "",
1183
- children: {}
1184
- };
1185
- if (!data[parentSid].children) data[parentSid].children = {};
1186
- if (!data[parentSid].children[sid]) data[parentSid].children[sid] = {
1187
- scroll: 0,
1188
- expanded: "",
1189
- entries: []
1190
- };
1191
- data[parentSid].children[sid] = {
1192
- ...data[parentSid].children[sid],
1193
- entries: [...entries2.values()]
1194
- };
2309
+ ensureChildRecord(data, parentSid, sid).scroll = scroll;
1195
2310
  } else {
1196
- data[sid] = {
1197
- ...data[sid],
1198
- ts: Date.now(),
1199
- entries: [...entries2.values()],
1200
- children: data[sid]?.children ?? {}
1201
- };
2311
+ const rec = ensureRootRecord(data, sid);
2312
+ rec.ts = Date.now();
2313
+ rec.scroll = scroll;
1202
2314
  }
1203
- saveSessionData2(data);
1204
- } catch {
1205
- }
1206
- }, 200);
1207
- };
1208
- const persistScroll = (sid, scroll) => {
1209
- try {
1210
- const data = loadSessionData2();
1211
- const {
1212
- parentSid,
1213
- isChild
1214
- } = resolveParent(sid);
1215
- if (isChild) {
1216
- if (!data[parentSid]) data[parentSid] = {
1217
- ts: Date.now(),
1218
- entries: [],
1219
- scroll: 0,
1220
- expanded: "",
1221
- children: {}
1222
- };
1223
- if (!data[parentSid].children) data[parentSid].children = {};
1224
- if (!data[parentSid].children[sid]) data[parentSid].children[sid] = {
1225
- scroll: 0,
1226
- expanded: "",
1227
- entries: []
1228
- };
1229
- data[parentSid].children[sid] = {
1230
- ...data[parentSid].children[sid],
1231
- scroll
1232
- };
1233
- } else {
1234
- data[sid] = {
1235
- ...data[sid],
1236
- ts: Date.now(),
1237
- scroll,
1238
- children: data[sid]?.children ?? {}
1239
- };
1240
- }
1241
- saveSessionData2(data);
2315
+ });
1242
2316
  } catch {
1243
2317
  }
1244
2318
  };
1245
2319
  const persistExpanded = (sid, expanded2) => {
1246
2320
  try {
1247
- const data = loadSessionData2();
1248
- const {
1249
- parentSid,
1250
- isChild
1251
- } = resolveParent(sid);
1252
- if (isChild) {
1253
- if (!data[parentSid]) data[parentSid] = {
1254
- ts: Date.now(),
1255
- entries: [],
1256
- scroll: 0,
1257
- expanded: "",
1258
- children: {}
1259
- };
1260
- if (!data[parentSid].children) data[parentSid].children = {};
1261
- if (!data[parentSid].children[sid]) data[parentSid].children[sid] = {
1262
- scroll: 0,
1263
- expanded: "",
1264
- entries: []
1265
- };
1266
- data[parentSid].children[sid] = {
1267
- ...data[parentSid].children[sid],
1268
- expanded: expanded2
1269
- };
1270
- } else {
1271
- data[sid] = {
1272
- ...data[sid],
1273
- ts: Date.now(),
1274
- expanded: expanded2,
1275
- children: data[sid]?.children ?? {}
1276
- };
1277
- }
1278
- saveSessionData2(data);
2321
+ mutateSessionData((data) => {
2322
+ const {
2323
+ parentSid,
2324
+ isChild
2325
+ } = resolveParent(sid);
2326
+ if (isChild) {
2327
+ ensureChildRecord(data, parentSid, sid).expanded = expanded2;
2328
+ } else {
2329
+ const rec = ensureRootRecord(data, sid);
2330
+ rec.ts = Date.now();
2331
+ rec.expanded = expanded2;
2332
+ }
2333
+ });
1279
2334
  } catch {
1280
2335
  }
1281
2336
  };
1282
2337
  const cleanupOldSessions = () => {
1283
2338
  if (ttlDays <= 0) return;
1284
2339
  try {
1285
- const data = loadSessionData2();
1286
2340
  const cutoff = Date.now() - TTL_MS;
1287
- let changed = false;
1288
- for (const sid of Object.keys(data)) {
1289
- if (data[sid].ts < cutoff) {
1290
- delete data[sid];
1291
- changed = true;
2341
+ mutateSessionData((data) => {
2342
+ for (const sid of Object.keys(data)) {
2343
+ if (data[sid].ts < cutoff) delete data[sid];
1292
2344
  }
1293
- }
1294
- if (changed) saveSessionData2(data);
2345
+ });
1295
2346
  } catch {
1296
2347
  }
1297
2348
  };
@@ -1311,38 +2362,22 @@ function SubAgentPanel(props) {
1311
2362
  if (needsImmediateFlush) {
1312
2363
  clearTimeout(persistTimer);
1313
2364
  try {
1314
- const data = loadSessionData2();
1315
- const {
1316
- parentSid,
1317
- isChild
1318
- } = resolveParent(props.sessionId);
1319
- if (isChild) {
1320
- if (!data[parentSid]) data[parentSid] = {
1321
- ts: Date.now(),
1322
- entries: [],
1323
- scroll: 0,
1324
- expanded: "",
1325
- children: {}
1326
- };
1327
- if (!data[parentSid].children) data[parentSid].children = {};
1328
- if (!data[parentSid].children[props.sessionId]) data[parentSid].children[props.sessionId] = {
1329
- scroll: 0,
1330
- expanded: "",
1331
- entries: []
1332
- };
1333
- data[parentSid].children[props.sessionId] = {
1334
- ...data[parentSid].children[props.sessionId],
1335
- entries: [...next.values()]
1336
- };
1337
- } else {
1338
- data[props.sessionId] = {
1339
- ...data[props.sessionId],
1340
- ts: Date.now(),
1341
- entries: [...next.values()],
1342
- children: data[props.sessionId]?.children ?? {}
1343
- };
1344
- }
1345
- saveSessionData2(data);
2365
+ mutateSessionData((data) => {
2366
+ const {
2367
+ parentSid,
2368
+ isChild
2369
+ } = resolveParent(props.sessionId);
2370
+ if (isChild) {
2371
+ const parent = ensureRootRecord(data, parentSid);
2372
+ const child = ensureChildRecord(data, parentSid, props.sessionId);
2373
+ child.entries = [...mergeSubEntries(child.entries ?? [], next.values()).values()];
2374
+ parent.ts = Date.now();
2375
+ } else {
2376
+ const rec = ensureRootRecord(data, props.sessionId);
2377
+ rec.ts = Date.now();
2378
+ rec.entries = [...mergeSubEntries(rec.entries ?? [], next.values()).values()];
2379
+ }
2380
+ });
1346
2381
  } catch {
1347
2382
  }
1348
2383
  } else {
@@ -1510,7 +2545,9 @@ function SubAgentPanel(props) {
1510
2545
  const part = event.payload?.part;
1511
2546
  if (!part) return;
1512
2547
  const eventSid = event.payload?.sessionID !== void 0 ? String(event.payload.sessionID) : void 0;
1513
- if (event.scope === "global" && eventSid !== props.sessionId) return;
2548
+ if (event.scope === "global" && eventSid !== props.sessionId) {
2549
+ return;
2550
+ }
1514
2551
  if (part.type === "subtask") {
1515
2552
  const agent = String(part.agent ?? "?");
1516
2553
  const prompt = String(part.prompt ?? "");
@@ -1527,7 +2564,8 @@ function SubAgentPanel(props) {
1527
2564
  prompt,
1528
2565
  sessionId: subSid,
1529
2566
  status: "running",
1530
- model: modelId
2567
+ model: modelId,
2568
+ origin: "sub"
1531
2569
  });
1532
2570
  }
1533
2571
  if (part.type === "tool") {
@@ -1539,7 +2577,7 @@ function SubAgentPanel(props) {
1539
2577
  if (rawStatus === "error") {
1540
2578
  const id2 = `tool:${String(part.id ?? "")}`;
1541
2579
  if (!part.id) return;
1542
- const stMeta2 = st?.metadata;
2580
+ const stMeta2 = partMetadata(part, st);
1543
2581
  const errorSid = stMeta2?.session_id !== void 0 ? String(stMeta2.session_id) : stMeta2?.sessionId !== void 0 ? String(stMeta2.sessionId) : void 0;
1544
2582
  const key = findSubEntryKey(entryMap(), id2, errorSid);
1545
2583
  const existing = key ? entryMap().get(key) : void 0;
@@ -1550,7 +2588,9 @@ function SubAgentPanel(props) {
1550
2588
  agent: existing.agent,
1551
2589
  prompt: existing.prompt,
1552
2590
  sessionId: existing.sessionId ?? errorSid,
1553
- status: "error"
2591
+ status: "error",
2592
+ origin: "tool",
2593
+ background: isBackgroundInput(st?.input)
1554
2594
  });
1555
2595
  }
1556
2596
  return;
@@ -1558,8 +2598,8 @@ function SubAgentPanel(props) {
1558
2598
  const input = st?.input;
1559
2599
  let status = "running";
1560
2600
  if (rawStatus === "completed") status = "done";
1561
- if ((input?.run_in_background === true || input?.background === true) && status === "done") {
1562
- const stMetaCheck = st?.metadata;
2601
+ if (isBackgroundInput(input) && status === "done") {
2602
+ const stMetaCheck = partMetadata(part, st);
1563
2603
  const hasChild = stMetaCheck?.session_id !== void 0 || stMetaCheck?.sessionId !== void 0;
1564
2604
  if (hasChild) status = "running";
1565
2605
  }
@@ -1568,7 +2608,7 @@ function SubAgentPanel(props) {
1568
2608
  const desc = input?.description !== void 0 ? String(input.description) : "";
1569
2609
  const title = desc || truncate(prompt.replace(/\n/g, " ").replace(/\s+/g, " ").trim(), 40);
1570
2610
  const id = `tool:${String(part.id ?? crypto.randomUUID())}`;
1571
- const stMeta = st?.metadata;
2611
+ const stMeta = partMetadata(part, st);
1572
2612
  const subSid = stMeta?.session_id !== void 0 ? String(stMeta.session_id) : stMeta?.sessionId !== void 0 ? String(stMeta.sessionId) : void 0;
1573
2613
  upsertEntry({
1574
2614
  id,
@@ -1576,7 +2616,9 @@ function SubAgentPanel(props) {
1576
2616
  agent,
1577
2617
  prompt,
1578
2618
  sessionId: subSid,
1579
- status
2619
+ status,
2620
+ origin: "tool",
2621
+ background: isBackgroundInput(input)
1580
2622
  });
1581
2623
  }
1582
2624
  };
@@ -1758,112 +2800,482 @@ function SubAgentPanel(props) {
1758
2800
  if (!found) {
1759
2801
  const data = loadSessionData2();
1760
2802
  const rec = data[parentSid];
1761
- if (rec?.entries) {
2803
+ if (rec?.entries?.length) {
1762
2804
  const fallbackMap = new Map(rec.entries.map((e) => [e.id, e]));
1763
2805
  found = tryMatchAndUpdate(fallbackMap, sid, status, nowTs);
1764
2806
  if (found) {
1765
- data[parentSid] = {
1766
- ...rec,
1767
- ts: nowTs,
1768
- entries: [...fallbackMap.values()]
1769
- };
1770
- saveSessionData2(data);
1771
2807
  globalEntryCache.set(parentSid, fallbackMap);
2808
+ mutateSessionData((latest) => {
2809
+ const latestRec = latest[parentSid];
2810
+ latest[parentSid] = {
2811
+ ...latestRec ?? rec,
2812
+ ts: nowTs,
2813
+ entries: [...mergeSubEntries(latestRec?.entries ?? [], fallbackMap.values()).values()]
2814
+ };
2815
+ });
2816
+ }
2817
+ }
2818
+ }
2819
+ if (found && parentCache) {
2820
+ mutateSessionData((data) => {
2821
+ const rec = data[parentSid];
2822
+ data[parentSid] = {
2823
+ ...rec ?? {
2824
+ entries: [],
2825
+ scroll: 0,
2826
+ expanded: "",
2827
+ children: {},
2828
+ ts: nowTs
2829
+ },
2830
+ ts: nowTs,
2831
+ entries: [...mergeSubEntries(rec?.entries ?? [], parentCache.values()).values()]
2832
+ };
2833
+ });
2834
+ }
2835
+ }
2836
+ } catch {
2837
+ }
2838
+ setTimeout(() => {
2839
+ if (disposed) return;
2840
+ const finalTokens = props.api.usage.readSessionTokens(sid);
2841
+ const finalCost = props.api.usage.readSessionCost(sid);
2842
+ const finalModel = props.api.usage.readSessionModel(sid);
2843
+ const finalTodo = props.api.usage.readSessionTodo(sid);
2844
+ setEntryMap((prev) => {
2845
+ let changed = false;
2846
+ const next = new Map(prev);
2847
+ for (const [id, entry] of next) {
2848
+ if (entry.sessionId !== sid) continue;
2849
+ const t2 = finalTokens ?? entry.tokens;
2850
+ const c = finalCost ?? entry.cost;
2851
+ const m = finalModel ?? entry.model;
2852
+ const tt = finalTodo?.total ?? entry.todoTotal;
2853
+ const td = finalTodo?.done ?? entry.todoDone;
2854
+ if (t2 !== entry.tokens || c !== entry.cost || m !== entry.model || tt !== entry.todoTotal || td !== entry.todoDone) {
2855
+ next.set(id, {
2856
+ ...entry,
2857
+ tokens: t2,
2858
+ cost: c,
2859
+ model: m,
2860
+ todoTotal: tt,
2861
+ todoDone: td
2862
+ });
2863
+ changed = true;
2864
+ }
2865
+ }
2866
+ return changed ? next : prev;
2867
+ });
2868
+ bump();
2869
+ }, 150);
2870
+ };
2871
+ const bump = () => setRenderTick((v) => v + 1);
2872
+ const settleIdleEntries = (prev) => {
2873
+ let changed = false;
2874
+ const next = new Map(prev);
2875
+ for (const [id, entry] of next) {
2876
+ if (entry.status !== "running" && entry.status !== "cancel_requested") continue;
2877
+ let sid = entry.sessionId;
2878
+ if (!sid) {
2879
+ try {
2880
+ const callId = String(entry.id).replace(/^tool:/, "");
2881
+ sid = props.api.session.resolveChild?.({
2882
+ parentId: props.sessionId,
2883
+ callId,
2884
+ agent: entry.agent,
2885
+ startedAt: entry.startedAt
2886
+ });
2887
+ } catch {
2888
+ }
2889
+ if (!sid) continue;
2890
+ next.set(id, {
2891
+ ...entry,
2892
+ sessionId: sid
2893
+ });
2894
+ changed = true;
2895
+ }
2896
+ try {
2897
+ const st = props.api.session.status(sid);
2898
+ if (!st || st.type !== "idle") continue;
2899
+ const tokens = props.api.usage.readSessionTokens(sid);
2900
+ const cost = props.api.usage.readSessionCost(sid);
2901
+ const finalStatus = entry.status === "cancel_requested" && entry.abortAccepted ? "cancelled" : "done";
2902
+ next.set(id, {
2903
+ ...entry,
2904
+ sessionId: sid,
2905
+ status: finalStatus,
2906
+ endedAt: Date.now(),
2907
+ tokens: tokens ?? entry.tokens,
2908
+ cost: cost ?? entry.cost
2909
+ });
2910
+ changed = true;
2911
+ } catch {
2912
+ }
2913
+ }
2914
+ return changed ? next : prev;
2915
+ };
2916
+ const reconcileIdleEntries = () => {
2917
+ try {
2918
+ const before = entryMap();
2919
+ const after = settleIdleEntries(before);
2920
+ if (after !== before) setEntryMap(after);
2921
+ } catch {
2922
+ }
2923
+ };
2924
+ const runSessionScan = (sid, replace) => {
2925
+ let msgs;
2926
+ try {
2927
+ msgs = props.api.session.messages(sid);
2928
+ } catch {
2929
+ }
2930
+ if (!msgs || msgs.length === 0) return false;
2931
+ setEntryMapRaw((prev) => {
2932
+ const next = replace ? new Map(globalEntryCache.get(sid) ?? loadEntries(sid)) : new Map(prev);
2933
+ const {
2934
+ parentSid: scanPSid,
2935
+ isChild: scanChild
2936
+ } = resolveParent(sid);
2937
+ const scanRec = loadSessionData2()[scanPSid];
2938
+ const clearedIds = new Set(scanChild ? scanRec?.children?.[sid]?.clearedIds : scanRec?.clearedIds);
2939
+ try {
2940
+ for (const msg of msgs) {
2941
+ const parts = props.api.session.part(msg.id) ?? [];
2942
+ for (const partRaw of parts) {
2943
+ const part = partRaw;
2944
+ if (part.type === "tool") {
2945
+ const tool = String(part.tool ?? "");
2946
+ if (!SUBAGENT_TOOLS.has(tool)) continue;
2947
+ const id = `tool:${String(part.id ?? "")}`;
2948
+ if (!part.id) continue;
2949
+ const st = part.state;
2950
+ const rawStatus = String(st?.status ?? "");
2951
+ const scanInput = st?.input;
2952
+ const scanMeta = partMetadata(part, st);
2953
+ let scanSubSid = scanMeta?.session_id !== void 0 ? String(scanMeta.session_id) : scanMeta?.sessionId !== void 0 ? String(scanMeta.sessionId) : scanMeta?.sessionID !== void 0 ? String(scanMeta.sessionID) : void 0;
2954
+ if (!scanSubSid) {
2955
+ try {
2956
+ scanSubSid = props.api.session.resolveChild?.({
2957
+ parentId: sid,
2958
+ callId: String(part.id)
2959
+ });
2960
+ } catch {
2961
+ }
2962
+ }
2963
+ const existingKey = findSubEntryKey(next, id, scanSubSid);
2964
+ const exists = existingKey ? next.get(existingKey) : void 0;
2965
+ if (!exists && clearedIds.has(id)) continue;
2966
+ if ((rawStatus === "pending" || rawStatus === "") && !exists) continue;
2967
+ if (rawStatus === "error") {
2968
+ if (exists && exists.status === "running") {
2969
+ next.set(existingKey ?? id, {
2970
+ ...exists,
2971
+ status: "error",
2972
+ endedAt: Date.now()
2973
+ });
2974
+ }
2975
+ continue;
2976
+ }
2977
+ const settled = exists !== void 0 && exists.status !== "running" && exists.status !== "cancel_requested";
2978
+ const needsRepair = settled && (exists.sessionId === void 0 || exists.tokens === void 0 || exists.cost === void 0 || !validModel(exists.model));
2979
+ if (settled && !needsRepair) continue;
2980
+ let status = "running";
2981
+ if (rawStatus === "completed") status = "done";
2982
+ if (isBackgroundInput(scanInput) && status === "done") {
2983
+ if (scanSubSid !== void 0) status = "running";
2984
+ }
2985
+ if (status === "done" && scanSubSid) {
2986
+ try {
2987
+ const childStatus = props.api.session.status(scanSubSid);
2988
+ if (childStatus && childStatus.type !== "idle") status = "running";
2989
+ } catch {
2990
+ }
2991
+ }
2992
+ if (settled) {
2993
+ status = exists.status;
2994
+ } else if (exists && status === "running") {
2995
+ if (!rawStatus) {
2996
+ const msgTokens = msg?.tokens;
2997
+ if (msgTokens && (Number(msgTokens.input) > 0 || Number(msgTokens.output) > 0)) {
2998
+ status = "done";
2999
+ } else {
3000
+ continue;
3001
+ }
3002
+ } else {
3003
+ continue;
3004
+ }
3005
+ }
3006
+ const agent = String(part.subagent_type ?? scanInput?.agent ?? scanInput?.subagent_type ?? tool);
3007
+ const prompt = String(scanInput?.prompt ?? part.description ?? "");
3008
+ const desc = scanInput?.description !== void 0 ? String(scanInput.description) : "";
3009
+ const title = desc || truncate(prompt.replace(/\n/g, " ").trim(), 40);
3010
+ let tokens;
3011
+ if (scanSubSid) tokens = props.api.usage.readSessionTokens(scanSubSid);
3012
+ let cost;
3013
+ if (scanSubSid) cost = props.api.usage.readSessionCost(scanSubSid);
3014
+ let model;
3015
+ if (scanSubSid) model = props.api.usage.readSessionModel(scanSubSid);
3016
+ const entryKey = existingKey ?? id;
3017
+ next.set(entryKey, {
3018
+ ...exists ?? {
3019
+ id
3020
+ },
3021
+ title: title || exists?.title || "",
3022
+ agent: exists?.agent ?? agent,
3023
+ prompt: prompt || exists?.prompt || "",
3024
+ // Preserve existing values (from handleSessionEnd / KV) — scan must not overwrite
3025
+ tokens: exists?.tokens ?? tokens,
3026
+ cost: exists?.cost ?? cost,
3027
+ model: validModel(exists?.model) ? exists.model : model,
3028
+ sessionId: exists?.sessionId ?? scanSubSid,
3029
+ status,
3030
+ startedAt: exists?.startedAt || Date.now(),
3031
+ endedAt: exists?.endedAt ?? (status === "done" ? Date.now() : void 0),
3032
+ origin: "tool",
3033
+ background: isBackgroundInput(scanInput) || exists?.background === true
3034
+ });
3035
+ if (entryKey !== id) next.delete(id);
1772
3036
  }
1773
3037
  }
1774
3038
  }
1775
- if (found && parentCache) {
1776
- const data = loadSessionData2();
1777
- data[parentSid] = {
1778
- ...data[parentSid],
1779
- ts: nowTs,
1780
- entries: [...parentCache.values()]
1781
- };
1782
- saveSessionData2(data);
3039
+ } catch {
3040
+ }
3041
+ return next;
3042
+ });
3043
+ setEntryMapRaw((prev) => settleIdleEntries(prev));
3044
+ const snapshot = entryMap();
3045
+ if (snapshot.size > 0) {
3046
+ globalEntryCache.set(sid, new Map(snapshot));
3047
+ persistEntries(sid, snapshot);
3048
+ }
3049
+ return true;
3050
+ };
3051
+ let rescanTimer;
3052
+ const missingNewestToolPart = () => {
3053
+ try {
3054
+ const msgs = props.api.session.messages(props.sessionId);
3055
+ if (!msgs || msgs.length === 0) return false;
3056
+ const map = entryMap();
3057
+ const from = Math.max(0, msgs.length - 6);
3058
+ for (let i = msgs.length - 1; i >= from; i--) {
3059
+ const parts = props.api.session.part(msgs[i]?.id) ?? [];
3060
+ for (const part of parts) {
3061
+ if (!part || part.type !== "tool") continue;
3062
+ if (!SUBAGENT_TOOLS.has(String(part.tool ?? ""))) continue;
3063
+ const partId = String(part.id ?? "");
3064
+ if (!partId) continue;
3065
+ const st = part.state;
3066
+ const rawStatus = String(st?.status ?? "");
3067
+ if (rawStatus !== "running" && rawStatus !== "completed") continue;
3068
+ const id = `tool:${partId}`;
3069
+ const meta = partMetadata(part, st);
3070
+ const subSid = meta?.session_id !== void 0 ? String(meta.session_id) : meta?.sessionId !== void 0 ? String(meta.sessionId) : void 0;
3071
+ const key = findSubEntryKey(map, id, subSid);
3072
+ const entry = key ? map.get(key) : void 0;
3073
+ if (!entry) return true;
3074
+ const input = st?.input;
3075
+ const isBackground = isBackgroundInput(input);
3076
+ const stillRunning = entry.status === "running" || entry.status === "cancel_requested";
3077
+ if (rawStatus === "completed" && !isBackground && stillRunning) return true;
1783
3078
  }
1784
3079
  }
1785
3080
  } catch {
1786
3081
  }
1787
- setTimeout(() => {
3082
+ return false;
3083
+ };
3084
+ const scheduleRescan = () => {
3085
+ if (disposed) return;
3086
+ clearTimeout(rescanTimer);
3087
+ rescanTimer = setTimeout(() => {
1788
3088
  if (disposed) return;
1789
- const finalTokens = props.api.usage.readSessionTokens(sid);
1790
- const finalCost = props.api.usage.readSessionCost(sid);
1791
- const finalModel = props.api.usage.readSessionModel(sid);
1792
- const finalTodo = props.api.usage.readSessionTodo(sid);
1793
- setEntryMap((prev) => {
1794
- let changed = false;
1795
- const next = new Map(prev);
1796
- for (const [id, entry] of next) {
1797
- if (entry.sessionId !== sid) continue;
1798
- const t2 = finalTokens ?? entry.tokens;
1799
- const c = finalCost ?? entry.cost;
1800
- const m = finalModel ?? entry.model;
1801
- const tt = finalTodo?.total ?? entry.todoTotal;
1802
- const td = finalTodo?.done ?? entry.todoDone;
1803
- if (t2 !== entry.tokens || c !== entry.cost || m !== entry.model || tt !== entry.todoTotal || td !== entry.todoDone) {
1804
- next.set(id, {
1805
- ...entry,
1806
- tokens: t2,
1807
- cost: c,
1808
- model: m,
1809
- todoTotal: tt,
1810
- todoDone: td
1811
- });
1812
- changed = true;
1813
- }
1814
- }
1815
- return changed ? next : prev;
3089
+ untrack(() => {
3090
+ if (!missingNewestToolPart()) return;
3091
+ runSessionScan(props.sessionId, false);
3092
+ bump();
1816
3093
  });
1817
- bump();
1818
- }, 150);
3094
+ }, 400);
1819
3095
  };
1820
- const bump = () => setRenderTick((v) => v + 1);
3096
+ let enrichCursor = 0;
1821
3097
  onMount(() => {
1822
3098
  const clock = setInterval(() => {
1823
3099
  setNow(Date.now());
1824
3100
  bump();
1825
3101
  }, 100);
3102
+ let tick = 0;
1826
3103
  const tokenTimer = setInterval(() => {
3104
+ tick++;
1827
3105
  untrack(() => {
3106
+ let enriched;
3107
+ let attempted = 0;
1828
3108
  setEntryMapRaw((prev) => {
1829
3109
  let changed = false;
1830
3110
  const next = new Map(prev);
3111
+ if (tick % 4 === 0) {
3112
+ try {
3113
+ const children = props.api.session.listChildren?.(props.sessionId);
3114
+ if (children) {
3115
+ const linked = /* @__PURE__ */ new Set();
3116
+ for (const e of next.values()) if (e.sessionId) linked.add(e.sessionId);
3117
+ const {
3118
+ parentSid,
3119
+ isChild
3120
+ } = resolveParent(props.sessionId);
3121
+ const rec = loadSessionData2()[parentSid];
3122
+ const cleared = new Set(isChild ? rec?.children?.[props.sessionId]?.clearedIds : rec?.clearedIds);
3123
+ for (const child of children) {
3124
+ if (!child.id || linked.has(child.id)) continue;
3125
+ const id = `sub:${child.id}`;
3126
+ if (cleared.has(id)) continue;
3127
+ const finished = child.timeIdle != null && child.active !== true;
3128
+ const status = finished ? child.idleOutcome === "interrupted" ? "cancelled" : "done" : "running";
3129
+ next.set(id, {
3130
+ id,
3131
+ title: child.title ?? child.agent ?? "",
3132
+ agent: child.agent ?? "?",
3133
+ prompt: "",
3134
+ status,
3135
+ sessionId: child.id,
3136
+ startedAt: child.timeCreated ?? Date.now(),
3137
+ endedAt: finished ? child.timeIdle : void 0,
3138
+ tokens: child.tokens,
3139
+ cost: child.cost,
3140
+ model: child.model,
3141
+ origin: "sub"
3142
+ });
3143
+ changed = true;
3144
+ }
3145
+ }
3146
+ } catch {
3147
+ }
3148
+ for (const [id, entry] of next) {
3149
+ if (entry.status !== "done" && entry.status !== "error") continue;
3150
+ const sid = entry.sessionId;
3151
+ if (!sid) continue;
3152
+ try {
3153
+ if (props.api.session.status(sid)?.type === "busy") {
3154
+ next.set(id, {
3155
+ ...entry,
3156
+ status: "running",
3157
+ endedAt: void 0
3158
+ });
3159
+ changed = true;
3160
+ }
3161
+ } catch {
3162
+ }
3163
+ }
3164
+ }
3165
+ const candidates = [];
1831
3166
  for (const [id, entry] of next) {
1832
- if (entry.status === "running" && entry.sessionId) {
3167
+ const running = entry.status === "running" || entry.status === "cancel_requested";
3168
+ const missing = entry.tokens === void 0 || entry.cost === void 0 || !validModel(entry.model);
3169
+ if (entry.sessionId && !running && !missing) continue;
3170
+ candidates.push(id);
3171
+ }
3172
+ if (candidates.length > 0) {
3173
+ const start = enrichCursor % candidates.length;
3174
+ let budget = 4;
3175
+ for (let k = 0; k < candidates.length && budget > 0; k++) {
3176
+ const id = candidates[(start + k) % candidates.length];
3177
+ let entry = next.get(id);
3178
+ if (!entry) continue;
3179
+ if (!entry.sessionId) {
3180
+ try {
3181
+ const callId = String(entry.id).replace(/^tool:/, "");
3182
+ const sid = props.api.session.resolveChild?.({
3183
+ parentId: props.sessionId,
3184
+ callId,
3185
+ agent: entry.agent,
3186
+ startedAt: entry.startedAt
3187
+ });
3188
+ if (sid) {
3189
+ entry = {
3190
+ ...entry,
3191
+ sessionId: sid
3192
+ };
3193
+ next.set(id, entry);
3194
+ changed = true;
3195
+ }
3196
+ } catch {
3197
+ }
3198
+ }
3199
+ if (!entry.sessionId) continue;
3200
+ const childSid = entry.sessionId;
1833
3201
  let isChild = false;
1834
3202
  try {
1835
- const s = props.api.session.get(entry.sessionId);
1836
- isChild = s?.parentID === props.sessionId;
3203
+ const s = props.api.session.get(childSid);
3204
+ isChild = !s || s.parentID === props.sessionId;
1837
3205
  } catch {
3206
+ isChild = true;
1838
3207
  }
1839
3208
  if (!isChild) continue;
1840
- const total = props.api.usage.readSessionTokens(entry.sessionId);
1841
- const todo = props.api.usage.readSessionTodo(entry.sessionId);
1842
- const model = entry.model ?? props.api.usage.readSessionModel(entry.sessionId);
3209
+ budget--;
3210
+ attempted++;
3211
+ const running = entry.status === "running" || entry.status === "cancel_requested";
3212
+ let entryChanged = false;
1843
3213
  const nextEntry = {
1844
3214
  ...entry
1845
3215
  };
1846
- if (total !== void 0 && total !== entry.tokens) {
1847
- nextEntry.tokens = total;
1848
- changed = true;
3216
+ if (running || entry.tokens === void 0) {
3217
+ const total = props.api.usage.readSessionTokens(childSid);
3218
+ if (total !== void 0 && total !== entry.tokens) {
3219
+ nextEntry.tokens = total;
3220
+ entryChanged = true;
3221
+ }
3222
+ }
3223
+ if (running || entry.cost === void 0) {
3224
+ const cost = props.api.usage.readSessionCost(childSid);
3225
+ if (cost !== void 0 && cost !== entry.cost) {
3226
+ nextEntry.cost = cost;
3227
+ entryChanged = true;
3228
+ }
1849
3229
  }
1850
- if (todo !== void 0) {
1851
- if (todo.total !== entry.todoTotal || todo.done !== entry.todoDone) {
3230
+ if (running || !validModel(entry.model)) {
3231
+ const model = props.api.usage.readSessionModel(childSid);
3232
+ if (model && model !== entry.model) {
3233
+ nextEntry.model = model;
3234
+ entryChanged = true;
3235
+ }
3236
+ }
3237
+ if (running) {
3238
+ const todo = props.api.usage.readSessionTodo(childSid);
3239
+ if (todo !== void 0 && (todo.total !== entry.todoTotal || todo.done !== entry.todoDone)) {
1852
3240
  nextEntry.todoTotal = todo.total;
1853
3241
  nextEntry.todoDone = todo.done;
1854
- changed = true;
3242
+ entryChanged = true;
1855
3243
  }
1856
3244
  }
1857
- if (model && !entry.model) {
1858
- nextEntry.model = model;
3245
+ if (entryChanged) {
3246
+ next.set(id, nextEntry);
1859
3247
  changed = true;
1860
3248
  }
1861
- if (changed) next.set(id, nextEntry);
1862
3249
  }
3250
+ enrichCursor = (start + Math.max(1, attempted)) % candidates.length;
1863
3251
  }
1864
- return changed ? next : prev;
3252
+ if (!changed) return prev;
3253
+ enriched = next;
3254
+ return next;
1865
3255
  });
3256
+ if (enriched) {
3257
+ globalEntryCache.set(props.sessionId, new Map(enriched));
3258
+ persistEntries(props.sessionId, enriched);
3259
+ }
1866
3260
  });
3261
+ reconcileIdleEntries();
3262
+ if (tick % 4 === 0) {
3263
+ untrack(() => {
3264
+ if (missingNewestToolPart()) {
3265
+ runSessionScan(props.sessionId, false);
3266
+ bump();
3267
+ }
3268
+ });
3269
+ }
3270
+ if (tick % 60 === 0) {
3271
+ untrack(() => {
3272
+ const hasRunning = entryList().some((e) => e.status === "running" || e.status === "cancel_requested");
3273
+ if (hasRunning) {
3274
+ runSessionScan(props.sessionId, false);
3275
+ bump();
3276
+ }
3277
+ });
3278
+ }
1867
3279
  bump();
1868
3280
  }, 500);
1869
3281
  bump();
@@ -1871,7 +3283,10 @@ function SubAgentPanel(props) {
1871
3283
  handlePartUpdated(e);
1872
3284
  bump();
1873
3285
  });
1874
- const unsubMsg = props.api.event.on("message.updated", () => bump());
3286
+ const unsubMsg = props.api.event.on("message.updated", () => {
3287
+ bump();
3288
+ scheduleRescan();
3289
+ });
1875
3290
  const unsubIdle = props.api.event.on("session.idle", (e) => {
1876
3291
  handleSessionEnd(e, "done");
1877
3292
  bump();
@@ -1882,6 +3297,7 @@ function SubAgentPanel(props) {
1882
3297
  });
1883
3298
  onCleanup(() => {
1884
3299
  disposed = true;
3300
+ clearTimeout(rescanTimer);
1885
3301
  clearInterval(clock);
1886
3302
  clearInterval(tokenTimer);
1887
3303
  unsubPart();
@@ -1899,7 +3315,9 @@ function SubAgentPanel(props) {
1899
3315
  const tick = clearTick();
1900
3316
  const forceReload = tick !== lastTick && !switched;
1901
3317
  lastTick = tick;
1902
- const t2 = setTimeout(() => {
3318
+ let attempts = 0;
3319
+ let retryTimer;
3320
+ const run = () => {
1903
3321
  untrack(() => {
1904
3322
  if (switched) {
1905
3323
  const {
@@ -1910,128 +3328,25 @@ function SubAgentPanel(props) {
1910
3328
  const saved = isChild ? data[parentSid]?.children?.[sid]?.scroll ?? 0 : data[sid]?.scroll ?? 0;
1911
3329
  setScrollOffset(saved);
1912
3330
  if (!isChild && data[sid]?.entries?.length) {
1913
- data[sid].ts = Date.now();
1914
- saveSessionData2(data);
3331
+ void Promise.resolve(updateSessionData(props.api.kv, (latest) => {
3332
+ if (latest[sid]?.entries?.length) latest[sid].ts = Date.now();
3333
+ })).catch(() => {
3334
+ });
1915
3335
  }
1916
3336
  }
1917
- setEntryMapRaw((prev) => {
1918
- const next = switched || forceReload ? new Map(globalEntryCache.get(sid) ?? loadEntries(sid)) : new Map(prev);
1919
- const {
1920
- parentSid: scanPSid,
1921
- isChild: scanChild
1922
- } = resolveParent(sid);
1923
- const scanRec = loadSessionData2()[scanPSid];
1924
- const clearedIds = new Set(scanChild ? scanRec?.children?.[sid]?.clearedIds : scanRec?.clearedIds);
1925
- try {
1926
- const msgs = props.api.session.messages(sid);
1927
- if (msgs && msgs.length) {
1928
- for (const msg of msgs) {
1929
- const parts = props.api.session.part(msg.id) ?? [];
1930
- for (const partRaw of parts) {
1931
- const part = partRaw;
1932
- if (part.type === "tool") {
1933
- const tool = String(part.tool ?? "");
1934
- if (!SUBAGENT_TOOLS.has(tool)) continue;
1935
- const id = `tool:${String(part.id ?? "")}`;
1936
- if (!part.id) continue;
1937
- const st = part.state;
1938
- const rawStatus = String(st?.status ?? "");
1939
- const scanStMeta = st?.metadata;
1940
- const scanSubSid = scanStMeta?.session_id !== void 0 ? String(scanStMeta.session_id) : scanStMeta?.sessionId !== void 0 ? String(scanStMeta.sessionId) : void 0;
1941
- const existingKey = findSubEntryKey(next, id, scanSubSid);
1942
- const exists = existingKey ? next.get(existingKey) : void 0;
1943
- if (!exists && clearedIds.has(id)) continue;
1944
- if ((rawStatus === "pending" || rawStatus === "") && !exists) continue;
1945
- if (rawStatus === "error") {
1946
- if (exists && exists.status === "running") {
1947
- next.set(existingKey ?? id, {
1948
- ...exists,
1949
- status: "error",
1950
- endedAt: Date.now()
1951
- });
1952
- }
1953
- continue;
1954
- }
1955
- let status = "running";
1956
- if (rawStatus === "completed") status = "done";
1957
- if ((st?.input?.run_in_background === true || st?.input?.background === true) && status === "done") {
1958
- const scanStMeta2 = st?.metadata;
1959
- const scanHasChild = scanStMeta2?.session_id !== void 0 || scanStMeta2?.sessionId !== void 0;
1960
- if (scanHasChild) status = "running";
1961
- }
1962
- if (exists && existingKey === id && exists.status !== "running" && exists.status !== "cancel_requested") continue;
1963
- if (exists && status === "running") {
1964
- if (!rawStatus) {
1965
- const msgTokens = msg?.tokens;
1966
- if (msgTokens && (Number(msgTokens.input) > 0 || Number(msgTokens.output) > 0)) {
1967
- status = "done";
1968
- } else if (Date.now() - exists.startedAt > 30 * 60 * 1e3) {
1969
- status = "done";
1970
- } else {
1971
- continue;
1972
- }
1973
- } else {
1974
- continue;
1975
- }
1976
- }
1977
- const input = st?.input;
1978
- const agent = String(part.subagent_type ?? input?.agent ?? input?.subagent_type ?? tool);
1979
- const prompt = String(input?.prompt ?? part.description ?? "");
1980
- const desc = input?.description !== void 0 ? String(input.description) : "";
1981
- const title = desc || truncate(prompt.replace(/\n/g, " ").trim(), 40);
1982
- let tokens;
1983
- if (scanSubSid) tokens = props.api.usage.readSessionTokens(scanSubSid);
1984
- const ended = status === "done";
1985
- const entryKey = existingKey ?? id;
1986
- next.set(entryKey, {
1987
- id: exists?.id ?? id,
1988
- title,
1989
- agent,
1990
- prompt,
1991
- // Preserve existing values (from handleSessionEnd / KV) — scan must not overwrite
1992
- tokens: exists?.tokens ?? tokens,
1993
- sessionId: exists?.sessionId ?? scanSubSid,
1994
- status,
1995
- startedAt: exists?.startedAt || Date.now(),
1996
- endedAt: ended ? exists?.endedAt || Date.now() : void 0
1997
- });
1998
- if (entryKey !== id) next.delete(id);
1999
- }
2000
- }
2001
- }
2002
- }
2003
- } catch {
2004
- }
2005
- return next;
2006
- });
2007
- setEntryMapRaw((prev) => {
2008
- let changed = false;
2009
- const next = new Map(prev);
2010
- for (const [id, entry] of next) {
2011
- if (entry.status !== "running" && entry.status !== "cancel_requested" || !entry.sessionId) continue;
2012
- try {
2013
- const st = props.api.session.status(entry.sessionId);
2014
- if (!st || st.type !== "idle") continue;
2015
- const tokens = props.api.usage.readSessionTokens(entry.sessionId);
2016
- const cost = props.api.usage.readSessionCost(entry.sessionId);
2017
- const finalStatus = entry.status === "cancel_requested" && entry.abortAccepted ? "cancelled" : "done";
2018
- next.set(id, {
2019
- ...entry,
2020
- status: finalStatus,
2021
- endedAt: Date.now(),
2022
- tokens: tokens ?? entry.tokens,
2023
- cost: cost ?? entry.cost
2024
- });
2025
- changed = true;
2026
- } catch {
2027
- }
2028
- }
2029
- return changed ? next : prev;
2030
- });
2031
- bump();
3337
+ const ok = runSessionScan(sid, switched || forceReload);
3338
+ if (!ok && attempts < 4) {
3339
+ attempts++;
3340
+ retryTimer = setTimeout(run, 400);
3341
+ }
2032
3342
  });
2033
- }, 150);
2034
- onCleanup(() => clearTimeout(t2));
3343
+ bump();
3344
+ };
3345
+ const t2 = setTimeout(run, 150);
3346
+ onCleanup(() => {
3347
+ clearTimeout(t2);
3348
+ if (retryTimer) clearTimeout(retryTimer);
3349
+ });
2035
3350
  });
2036
3351
  const pal = createMemo(() => {
2037
3352
  const th = props.theme;
@@ -2410,17 +3725,26 @@ function SubAgentPanel(props) {
2410
3725
  return "#" + [r, g, bl].map((v) => Math.max(0, Math.min(255, v)).toString(16).padStart(2, "0")).join("");
2411
3726
  };
2412
3727
  const timeColor = () => isActiveRunning ? pal().warning : isError ? pal().error : pal().muted;
2413
- const tokenText = () => !isExpanded() && entry.tokens !== void 0 && entry.tokens > 0 ? ` ${fmtTokens(entry.tokens)}` : "";
2414
- const timeText = () => !isExpanded() && (elapsed() >= 2e3 || entry.endedAt !== void 0) ? fmtDurationShort(elapsed(), isActiveRunning) : "";
3728
+ const tokenText = () => !isExpanded() && props.showEntryTokens() && entry.tokens !== void 0 && entry.tokens > 0 ? ` ${fmtTokens(entry.tokens)}` : "";
3729
+ const costText = () => !isExpanded() && props.showEntryCost() && entry.cost !== void 0 && entry.cost > 0 ? ` $${entry.cost.toFixed(4)}` : "";
3730
+ const timeText = () => !isExpanded() && props.showEntryTime() && (elapsed() >= 2e3 || entry.endedAt !== void 0) ? fmtDuration(elapsed(), isActiveRunning, props.timeFormat()) : "";
3731
+ const originMark = () => {
3732
+ if (!props.showOrigin()) return "";
3733
+ if (entry.origin === "sub") return "\u21B3";
3734
+ return entry.background === true ? "\u21E2" : "";
3735
+ };
3736
+ const originW = () => originMark() ? 2 : 0;
2415
3737
  const suffixW = () => {
2416
3738
  let w = 0;
2417
3739
  const t2 = timeText();
2418
3740
  if (t2) w += 1 + visualWidth(t2);
2419
3741
  const tk = tokenText();
2420
3742
  if (tk) w += visualWidth(tk);
3743
+ const c = costText();
3744
+ if (c) w += visualWidth(c);
2421
3745
  return w;
2422
3746
  };
2423
- const labelAvail = () => Math.max(6, panelWidth() - gutter() - LEFT_PAD - suffixW());
3747
+ const labelAvail = () => Math.max(6, panelWidth() - gutter() - LEFT_PAD - originW() - suffixW());
2424
3748
  const labelText = () => {
2425
3749
  const max2 = labelAvail();
2426
3750
  const text = entry.title || entry.agent;
@@ -2438,27 +3762,49 @@ function SubAgentPanel(props) {
2438
3762
  _$setProp(_el$25, "onMouseUp", () => toggleExpand(entry.id));
2439
3763
  _$insert(_el$26, () => isExpanded() ? "\u25BC" : "\u25B6");
2440
3764
  _$insert(_el$28, statusDot);
3765
+ _$insert(_el$25, (() => {
3766
+ var _c$2 = _$memo(() => !!originMark());
3767
+ return () => _c$2() ? (() => {
3768
+ var _el$88 = _$createElement("span");
3769
+ _$insert(_el$88, () => originMark() + " ");
3770
+ _$effect((_$p) => _$setProp(_el$88, "style", {
3771
+ fg: pal().muted
3772
+ }, _$p));
3773
+ return _el$88;
3774
+ })() : null;
3775
+ })(), _el$30);
2441
3776
  _$insert(_el$30, labelText);
2442
3777
  _$insert(_el$25, (() => {
2443
- var _c$2 = _$memo(() => !!timeText());
2444
- return () => _c$2() ? [" ", (() => {
2445
- var _el$82 = _$createElement("span");
2446
- _$insert(_el$82, timeText);
2447
- _$effect((_$p) => _$setProp(_el$82, "style", {
3778
+ var _c$3 = _$memo(() => !!timeText());
3779
+ return () => _c$3() ? [" ", (() => {
3780
+ var _el$89 = _$createElement("span");
3781
+ _$insert(_el$89, timeText);
3782
+ _$effect((_$p) => _$setProp(_el$89, "style", {
2448
3783
  fg: timeColor()
2449
3784
  }, _$p));
2450
- return _el$82;
3785
+ return _el$89;
2451
3786
  })()] : null;
2452
3787
  })(), null);
2453
3788
  _$insert(_el$25, (() => {
2454
- var _c$3 = _$memo(() => !!tokenText());
2455
- return () => _c$3() ? (() => {
2456
- var _el$83 = _$createElement("span");
2457
- _$insert(_el$83, tokenText);
2458
- _$effect((_$p) => _$setProp(_el$83, "style", {
3789
+ var _c$4 = _$memo(() => !!tokenText());
3790
+ return () => _c$4() ? (() => {
3791
+ var _el$90 = _$createElement("span");
3792
+ _$insert(_el$90, tokenText);
3793
+ _$effect((_$p) => _$setProp(_el$90, "style", {
2459
3794
  fg: pal().muted
2460
3795
  }, _$p));
2461
- return _el$83;
3796
+ return _el$90;
3797
+ })() : null;
3798
+ })(), null);
3799
+ _$insert(_el$25, (() => {
3800
+ var _c$5 = _$memo(() => !!costText());
3801
+ return () => _c$5() ? (() => {
3802
+ var _el$91 = _$createElement("span");
3803
+ _$insert(_el$91, costText);
3804
+ _$effect((_$p) => _$setProp(_el$91, "style", {
3805
+ fg: pal().warning
3806
+ }, _$p));
3807
+ return _el$91;
2462
3808
  })() : null;
2463
3809
  })(), null);
2464
3810
  _$effect((_p$) => {
@@ -2512,70 +3858,77 @@ function SubAgentPanel(props) {
2512
3858
  a: void 0
2513
3859
  });
2514
3860
  return _el$31;
2515
- })(), (() => {
2516
- var _el$37 = _$createElement("text"), _el$38 = _$createTextNode(` `), _el$39 = _$createElement("span"), _el$40 = _$createTextNode(`: `), _el$41 = _$createElement("span"), _el$42 = _$createElement("span");
2517
- _$insertNode(_el$37, _el$38);
2518
- _$insertNode(_el$37, _el$39);
2519
- _$insertNode(_el$37, _el$41);
2520
- _$insertNode(_el$37, _el$42);
2521
- _$insertNode(_el$39, _el$40);
2522
- _$insert(_el$39, () => t("status.label"), _el$40);
2523
- _$insert(_el$41, () => " ".repeat(expandedPad(t("status.label"))));
2524
- _$insert(_el$42, () => isActiveRunning ? t("status.running") : isCancelled ? t("status.cancelled") : isError ? t("status.error") : t("status.done"));
2525
- _$effect((_p$) => {
2526
- var _v$9 = {
2527
- fg: pal().primary
2528
- }, _v$0 = {
2529
- fg: pal().muted
2530
- }, _v$1 = {
2531
- fg: isActiveRunning ? pal().warning : isCancelled ? pal().muted : isError ? pal().error : pal().success
2532
- };
2533
- _v$9 !== _p$.e && (_p$.e = _$setProp(_el$39, "style", _v$9, _p$.e));
2534
- _v$0 !== _p$.t && (_p$.t = _$setProp(_el$41, "style", _v$0, _p$.t));
2535
- _v$1 !== _p$.a && (_p$.a = _$setProp(_el$42, "style", _v$1, _p$.a));
2536
- return _p$;
2537
- }, {
2538
- e: void 0,
2539
- t: void 0,
2540
- a: void 0
2541
- });
2542
- return _el$37;
2543
3861
  })(), _$createComponent(Show, {
2544
3862
  get when() {
2545
- return elapsed() >= 2e3 || entry.endedAt !== void 0;
3863
+ return props.showOrigin();
2546
3864
  },
2547
3865
  get children() {
2548
- var _el$43 = _$createElement("text"), _el$44 = _$createTextNode(` `), _el$45 = _$createElement("span"), _el$46 = _$createTextNode(`: `), _el$47 = _$createElement("span"), _el$48 = _$createElement("span");
2549
- _$insertNode(_el$43, _el$44);
2550
- _$insertNode(_el$43, _el$45);
2551
- _$insertNode(_el$43, _el$47);
2552
- _$insertNode(_el$43, _el$48);
2553
- _$insertNode(_el$45, _el$46);
2554
- _$insert(_el$45, () => t("time.label"), _el$46);
2555
- _$insert(_el$47, () => " ".repeat(expandedPad(t("time.label"))));
2556
- _$insert(_el$48, () => fmtDurationShort(elapsed(), isActiveRunning));
3866
+ var _el$37 = _$createElement("text"), _el$38 = _$createTextNode(` `), _el$39 = _$createElement("span"), _el$40 = _$createTextNode(`: `), _el$41 = _$createElement("span"), _el$42 = _$createElement("span");
3867
+ _$insertNode(_el$37, _el$38);
3868
+ _$insertNode(_el$37, _el$39);
3869
+ _$insertNode(_el$37, _el$41);
3870
+ _$insertNode(_el$37, _el$42);
3871
+ _$insertNode(_el$39, _el$40);
3872
+ _$insert(_el$39, () => t("origin.label"), _el$40);
3873
+ _$insert(_el$41, () => " ".repeat(expandedPad(t("origin.label"))));
3874
+ _$insert(_el$42, (() => {
3875
+ var _c$6 = _$memo(() => entry.origin === "sub");
3876
+ return () => _c$6() ? t("origin.sub") : t("origin.tool");
3877
+ })(), null);
3878
+ _$insert(_el$42, (() => {
3879
+ var _c$7 = _$memo(() => entry.background === true);
3880
+ return () => _c$7() ? ` (${t("origin.background")})` : "";
3881
+ })(), null);
2557
3882
  _$effect((_p$) => {
2558
- var _v$10 = {
3883
+ var _v$9 = {
2559
3884
  fg: pal().primary
2560
- }, _v$11 = {
3885
+ }, _v$0 = {
2561
3886
  fg: pal().muted
2562
- }, _v$12 = {
3887
+ }, _v$1 = {
2563
3888
  fg: pal().muted
2564
3889
  };
2565
- _v$10 !== _p$.e && (_p$.e = _$setProp(_el$45, "style", _v$10, _p$.e));
2566
- _v$11 !== _p$.t && (_p$.t = _$setProp(_el$47, "style", _v$11, _p$.t));
2567
- _v$12 !== _p$.a && (_p$.a = _$setProp(_el$48, "style", _v$12, _p$.a));
3890
+ _v$9 !== _p$.e && (_p$.e = _$setProp(_el$39, "style", _v$9, _p$.e));
3891
+ _v$0 !== _p$.t && (_p$.t = _$setProp(_el$41, "style", _v$0, _p$.t));
3892
+ _v$1 !== _p$.a && (_p$.a = _$setProp(_el$42, "style", _v$1, _p$.a));
2568
3893
  return _p$;
2569
3894
  }, {
2570
3895
  e: void 0,
2571
3896
  t: void 0,
2572
3897
  a: void 0
2573
3898
  });
2574
- return _el$43;
3899
+ return _el$37;
2575
3900
  }
2576
- }), _$createComponent(Show, {
3901
+ }), (() => {
3902
+ var _el$43 = _$createElement("text"), _el$44 = _$createTextNode(` `), _el$45 = _$createElement("span"), _el$46 = _$createTextNode(`: `), _el$47 = _$createElement("span"), _el$48 = _$createElement("span");
3903
+ _$insertNode(_el$43, _el$44);
3904
+ _$insertNode(_el$43, _el$45);
3905
+ _$insertNode(_el$43, _el$47);
3906
+ _$insertNode(_el$43, _el$48);
3907
+ _$insertNode(_el$45, _el$46);
3908
+ _$insert(_el$45, () => t("status.label"), _el$46);
3909
+ _$insert(_el$47, () => " ".repeat(expandedPad(t("status.label"))));
3910
+ _$insert(_el$48, () => isActiveRunning ? t("status.running") : isCancelled ? t("status.cancelled") : isError ? t("status.error") : t("status.done"));
3911
+ _$effect((_p$) => {
3912
+ var _v$10 = {
3913
+ fg: pal().primary
3914
+ }, _v$11 = {
3915
+ fg: pal().muted
3916
+ }, _v$12 = {
3917
+ fg: isActiveRunning ? pal().warning : isCancelled ? pal().muted : isError ? pal().error : pal().success
3918
+ };
3919
+ _v$10 !== _p$.e && (_p$.e = _$setProp(_el$45, "style", _v$10, _p$.e));
3920
+ _v$11 !== _p$.t && (_p$.t = _$setProp(_el$47, "style", _v$11, _p$.t));
3921
+ _v$12 !== _p$.a && (_p$.a = _$setProp(_el$48, "style", _v$12, _p$.a));
3922
+ return _p$;
3923
+ }, {
3924
+ e: void 0,
3925
+ t: void 0,
3926
+ a: void 0
3927
+ });
3928
+ return _el$43;
3929
+ })(), _$createComponent(Show, {
2577
3930
  get when() {
2578
- return entry.tokens !== void 0;
3931
+ return elapsed() >= 2e3 || entry.endedAt !== void 0;
2579
3932
  },
2580
3933
  get children() {
2581
3934
  var _el$49 = _$createElement("text"), _el$50 = _$createTextNode(` `), _el$51 = _$createElement("span"), _el$52 = _$createTextNode(`: `), _el$53 = _$createElement("span"), _el$54 = _$createElement("span");
@@ -2584,9 +3937,9 @@ function SubAgentPanel(props) {
2584
3937
  _$insertNode(_el$49, _el$53);
2585
3938
  _$insertNode(_el$49, _el$54);
2586
3939
  _$insertNode(_el$51, _el$52);
2587
- _$insert(_el$51, () => t("tokens.label"), _el$52);
2588
- _$insert(_el$53, () => " ".repeat(expandedPad(t("tokens.label"))));
2589
- _$insert(_el$54, () => fmtTokens(entry.tokens));
3940
+ _$insert(_el$51, () => t("time.label"), _el$52);
3941
+ _$insert(_el$53, () => " ".repeat(expandedPad(t("time.label"))));
3942
+ _$insert(_el$54, () => fmtDuration(elapsed(), isActiveRunning, props.timeFormat()));
2590
3943
  _$effect((_p$) => {
2591
3944
  var _v$13 = {
2592
3945
  fg: pal().primary
@@ -2608,7 +3961,7 @@ function SubAgentPanel(props) {
2608
3961
  }
2609
3962
  }), _$createComponent(Show, {
2610
3963
  get when() {
2611
- return entry.error;
3964
+ return entry.tokens !== void 0;
2612
3965
  },
2613
3966
  get children() {
2614
3967
  var _el$55 = _$createElement("text"), _el$56 = _$createTextNode(` `), _el$57 = _$createElement("span"), _el$58 = _$createTextNode(`: `), _el$59 = _$createElement("span"), _el$60 = _$createElement("span");
@@ -2617,16 +3970,16 @@ function SubAgentPanel(props) {
2617
3970
  _$insertNode(_el$55, _el$59);
2618
3971
  _$insertNode(_el$55, _el$60);
2619
3972
  _$insertNode(_el$57, _el$58);
2620
- _$insert(_el$57, () => t("error.label"), _el$58);
2621
- _$insert(_el$59, () => " ".repeat(expandedPad(t("error.label"))));
2622
- _$insert(_el$60, () => truncate(String(entry.error), expandedValAvail()));
3973
+ _$insert(_el$57, () => t("tokens.label"), _el$58);
3974
+ _$insert(_el$59, () => " ".repeat(expandedPad(t("tokens.label"))));
3975
+ _$insert(_el$60, () => fmtTokens(entry.tokens));
2623
3976
  _$effect((_p$) => {
2624
3977
  var _v$16 = {
2625
- fg: pal().error
3978
+ fg: pal().primary
2626
3979
  }, _v$17 = {
2627
3980
  fg: pal().muted
2628
3981
  }, _v$18 = {
2629
- fg: pal().error
3982
+ fg: pal().muted
2630
3983
  };
2631
3984
  _v$16 !== _p$.e && (_p$.e = _$setProp(_el$57, "style", _v$16, _p$.e));
2632
3985
  _v$17 !== _p$.t && (_p$.t = _$setProp(_el$59, "style", _v$17, _p$.t));
@@ -2641,46 +3994,7 @@ function SubAgentPanel(props) {
2641
3994
  }
2642
3995
  }), _$createComponent(Show, {
2643
3996
  get when() {
2644
- return entry.cost !== void 0;
2645
- },
2646
- get children() {
2647
- return (() => {
2648
- const cost = entry.cost;
2649
- return (() => {
2650
- var _el$84 = _$createElement("text"), _el$85 = _$createTextNode(` `), _el$86 = _$createElement("span"), _el$87 = _$createTextNode(`: `), _el$88 = _$createElement("span"), _el$89 = _$createElement("span"), _el$90 = _$createTextNode(`$`);
2651
- _$insertNode(_el$84, _el$85);
2652
- _$insertNode(_el$84, _el$86);
2653
- _$insertNode(_el$84, _el$88);
2654
- _$insertNode(_el$84, _el$89);
2655
- _$insertNode(_el$86, _el$87);
2656
- _$insert(_el$86, () => t("cost.label"), _el$87);
2657
- _$insert(_el$88, () => " ".repeat(expandedPad(t("cost.label"))));
2658
- _$insertNode(_el$89, _el$90);
2659
- _$insert(_el$89, () => cost.toFixed(4), null);
2660
- _$effect((_p$) => {
2661
- var _v$29 = {
2662
- fg: pal().primary
2663
- }, _v$30 = {
2664
- fg: pal().muted
2665
- }, _v$31 = {
2666
- fg: pal().muted
2667
- };
2668
- _v$29 !== _p$.e && (_p$.e = _$setProp(_el$86, "style", _v$29, _p$.e));
2669
- _v$30 !== _p$.t && (_p$.t = _$setProp(_el$88, "style", _v$30, _p$.t));
2670
- _v$31 !== _p$.a && (_p$.a = _$setProp(_el$89, "style", _v$31, _p$.a));
2671
- return _p$;
2672
- }, {
2673
- e: void 0,
2674
- t: void 0,
2675
- a: void 0
2676
- });
2677
- return _el$84;
2678
- })();
2679
- })();
2680
- }
2681
- }), _$createComponent(Show, {
2682
- get when() {
2683
- return entry.model;
3997
+ return entry.error;
2684
3998
  },
2685
3999
  get children() {
2686
4000
  var _el$61 = _$createElement("text"), _el$62 = _$createTextNode(` `), _el$63 = _$createElement("span"), _el$64 = _$createTextNode(`: `), _el$65 = _$createElement("span"), _el$66 = _$createElement("span");
@@ -2689,16 +4003,16 @@ function SubAgentPanel(props) {
2689
4003
  _$insertNode(_el$61, _el$65);
2690
4004
  _$insertNode(_el$61, _el$66);
2691
4005
  _$insertNode(_el$63, _el$64);
2692
- _$insert(_el$63, () => t("model.label"), _el$64);
2693
- _$insert(_el$65, () => " ".repeat(expandedPad(t("model.label"))));
2694
- _$insert(_el$66, () => truncate(entry.model, expandedValAvail()));
4006
+ _$insert(_el$63, () => t("error.label"), _el$64);
4007
+ _$insert(_el$65, () => " ".repeat(expandedPad(t("error.label"))));
4008
+ _$insert(_el$66, () => truncate(String(entry.error), expandedValAvail()));
2695
4009
  _$effect((_p$) => {
2696
4010
  var _v$19 = {
2697
- fg: pal().primary
4011
+ fg: pal().error
2698
4012
  }, _v$20 = {
2699
4013
  fg: pal().muted
2700
4014
  }, _v$21 = {
2701
- fg: pal().muted
4015
+ fg: pal().error
2702
4016
  };
2703
4017
  _v$19 !== _p$.e && (_p$.e = _$setProp(_el$63, "style", _v$19, _p$.e));
2704
4018
  _v$20 !== _p$.t && (_p$.t = _$setProp(_el$65, "style", _v$20, _p$.t));
@@ -2713,20 +4027,57 @@ function SubAgentPanel(props) {
2713
4027
  }
2714
4028
  }), _$createComponent(Show, {
2715
4029
  get when() {
2716
- return entry.todoTotal !== void 0;
4030
+ return entry.cost !== void 0;
4031
+ },
4032
+ get children() {
4033
+ return (() => {
4034
+ const cost = entry.cost;
4035
+ return (() => {
4036
+ var _el$92 = _$createElement("text"), _el$93 = _$createTextNode(` `), _el$94 = _$createElement("span"), _el$95 = _$createTextNode(`: `), _el$96 = _$createElement("span"), _el$97 = _$createElement("span"), _el$98 = _$createTextNode(`$`);
4037
+ _$insertNode(_el$92, _el$93);
4038
+ _$insertNode(_el$92, _el$94);
4039
+ _$insertNode(_el$92, _el$96);
4040
+ _$insertNode(_el$92, _el$97);
4041
+ _$insertNode(_el$94, _el$95);
4042
+ _$insert(_el$94, () => t("cost.label"), _el$95);
4043
+ _$insert(_el$96, () => " ".repeat(expandedPad(t("cost.label"))));
4044
+ _$insertNode(_el$97, _el$98);
4045
+ _$insert(_el$97, () => cost.toFixed(4), null);
4046
+ _$effect((_p$) => {
4047
+ var _v$32 = {
4048
+ fg: pal().primary
4049
+ }, _v$33 = {
4050
+ fg: pal().muted
4051
+ }, _v$34 = {
4052
+ fg: pal().muted
4053
+ };
4054
+ _v$32 !== _p$.e && (_p$.e = _$setProp(_el$94, "style", _v$32, _p$.e));
4055
+ _v$33 !== _p$.t && (_p$.t = _$setProp(_el$96, "style", _v$33, _p$.t));
4056
+ _v$34 !== _p$.a && (_p$.a = _$setProp(_el$97, "style", _v$34, _p$.a));
4057
+ return _p$;
4058
+ }, {
4059
+ e: void 0,
4060
+ t: void 0,
4061
+ a: void 0
4062
+ });
4063
+ return _el$92;
4064
+ })();
4065
+ })();
4066
+ }
4067
+ }), _$createComponent(Show, {
4068
+ get when() {
4069
+ return _$memo(() => !!validModel(entry.model))() ? entry.model : void 0;
2717
4070
  },
2718
4071
  get children() {
2719
- var _el$67 = _$createElement("text"), _el$68 = _$createTextNode(` `), _el$69 = _$createElement("span"), _el$70 = _$createTextNode(`: `), _el$71 = _$createElement("span"), _el$72 = _$createElement("span"), _el$73 = _$createTextNode(`/`);
4072
+ var _el$67 = _$createElement("text"), _el$68 = _$createTextNode(` `), _el$69 = _$createElement("span"), _el$70 = _$createTextNode(`: `), _el$71 = _$createElement("span"), _el$72 = _$createElement("span");
2720
4073
  _$insertNode(_el$67, _el$68);
2721
4074
  _$insertNode(_el$67, _el$69);
2722
4075
  _$insertNode(_el$67, _el$71);
2723
4076
  _$insertNode(_el$67, _el$72);
2724
4077
  _$insertNode(_el$69, _el$70);
2725
- _$insert(_el$69, () => t("todo.label"), _el$70);
2726
- _$insert(_el$71, () => " ".repeat(expandedPad(t("todo.label"))));
2727
- _$insertNode(_el$72, _el$73);
2728
- _$insert(_el$72, () => entry.todoDone, _el$73);
2729
- _$insert(_el$72, () => entry.todoTotal, null);
4078
+ _$insert(_el$69, () => t("model.label"), _el$70);
4079
+ _$insert(_el$71, () => " ".repeat(expandedPad(t("model.label"))));
4080
+ _$insert(_el$72, () => truncate(entry.model, expandedValAvail()));
2730
4081
  _$effect((_p$) => {
2731
4082
  var _v$22 = {
2732
4083
  fg: pal().primary
@@ -2746,18 +4097,53 @@ function SubAgentPanel(props) {
2746
4097
  });
2747
4098
  return _el$67;
2748
4099
  }
4100
+ }), _$createComponent(Show, {
4101
+ get when() {
4102
+ return entry.todoTotal !== void 0;
4103
+ },
4104
+ get children() {
4105
+ var _el$73 = _$createElement("text"), _el$74 = _$createTextNode(` `), _el$75 = _$createElement("span"), _el$76 = _$createTextNode(`: `), _el$77 = _$createElement("span"), _el$78 = _$createElement("span"), _el$79 = _$createTextNode(`/`);
4106
+ _$insertNode(_el$73, _el$74);
4107
+ _$insertNode(_el$73, _el$75);
4108
+ _$insertNode(_el$73, _el$77);
4109
+ _$insertNode(_el$73, _el$78);
4110
+ _$insertNode(_el$75, _el$76);
4111
+ _$insert(_el$75, () => t("todo.label"), _el$76);
4112
+ _$insert(_el$77, () => " ".repeat(expandedPad(t("todo.label"))));
4113
+ _$insertNode(_el$78, _el$79);
4114
+ _$insert(_el$78, () => entry.todoDone, _el$79);
4115
+ _$insert(_el$78, () => entry.todoTotal, null);
4116
+ _$effect((_p$) => {
4117
+ var _v$25 = {
4118
+ fg: pal().primary
4119
+ }, _v$26 = {
4120
+ fg: pal().muted
4121
+ }, _v$27 = {
4122
+ fg: pal().muted
4123
+ };
4124
+ _v$25 !== _p$.e && (_p$.e = _$setProp(_el$75, "style", _v$25, _p$.e));
4125
+ _v$26 !== _p$.t && (_p$.t = _$setProp(_el$77, "style", _v$26, _p$.t));
4126
+ _v$27 !== _p$.a && (_p$.a = _$setProp(_el$78, "style", _v$27, _p$.a));
4127
+ return _p$;
4128
+ }, {
4129
+ e: void 0,
4130
+ t: void 0,
4131
+ a: void 0
4132
+ });
4133
+ return _el$73;
4134
+ }
2749
4135
  }), _$createComponent(Show, {
2750
4136
  get when() {
2751
4137
  return entry.sessionId;
2752
4138
  },
2753
4139
  get children() {
2754
- var _el$74 = _$createElement("text"), _el$75 = _$createTextNode(` `), _el$76 = _$createElement("span"), _el$77 = _$createTextNode(`: `), _el$78 = _$createElement("span"), _el$79 = _$createElement("span"), _el$80 = _$createElement("span");
2755
- _$insertNode(_el$74, _el$75);
2756
- _$insertNode(_el$74, _el$76);
2757
- _$insertNode(_el$74, _el$78);
2758
- _$insertNode(_el$74, _el$79);
2759
- _$insertNode(_el$74, _el$80);
2760
- _$setProp(_el$74, "onMouseUp", async () => {
4140
+ var _el$80 = _$createElement("text"), _el$81 = _$createTextNode(` `), _el$82 = _$createElement("span"), _el$83 = _$createTextNode(`: `), _el$84 = _$createElement("span"), _el$85 = _$createElement("span"), _el$86 = _$createElement("span");
4141
+ _$insertNode(_el$80, _el$81);
4142
+ _$insertNode(_el$80, _el$82);
4143
+ _$insertNode(_el$80, _el$84);
4144
+ _$insertNode(_el$80, _el$85);
4145
+ _$insertNode(_el$80, _el$86);
4146
+ _$setProp(_el$80, "onMouseUp", async () => {
2761
4147
  const sessionId = entry.sessionId;
2762
4148
  if (!sessionId) return;
2763
4149
  const result = await copyText(sessionId);
@@ -2777,25 +4163,25 @@ ${t("session.toast.copy_failed")}`, {
2777
4163
  duration: 8e3
2778
4164
  });
2779
4165
  });
2780
- _$insertNode(_el$76, _el$77);
2781
- _$insert(_el$76, () => t("session.label"), _el$77);
2782
- _$insert(_el$78, () => " ".repeat(expandedPad(t("session.label"))));
2783
- _$insert(_el$79, () => truncate(entry.sessionId, expandedValAvail() - visualWidth(" \u2398")));
2784
- _$insertNode(_el$80, _$createTextNode(` \u2398`));
4166
+ _$insertNode(_el$82, _el$83);
4167
+ _$insert(_el$82, () => t("session.label"), _el$83);
4168
+ _$insert(_el$84, () => " ".repeat(expandedPad(t("session.label"))));
4169
+ _$insert(_el$85, () => truncate(entry.sessionId, expandedValAvail() - visualWidth(" \u2398")));
4170
+ _$insertNode(_el$86, _$createTextNode(` \u2398`));
2785
4171
  _$effect((_p$) => {
2786
- var _v$25 = {
4172
+ var _v$28 = {
2787
4173
  fg: pal().primary
2788
- }, _v$26 = {
4174
+ }, _v$29 = {
2789
4175
  fg: pal().muted
2790
- }, _v$27 = {
4176
+ }, _v$30 = {
2791
4177
  fg: pal().muted
2792
- }, _v$28 = {
4178
+ }, _v$31 = {
2793
4179
  fg: pal().warning
2794
4180
  };
2795
- _v$25 !== _p$.e && (_p$.e = _$setProp(_el$76, "style", _v$25, _p$.e));
2796
- _v$26 !== _p$.t && (_p$.t = _$setProp(_el$78, "style", _v$26, _p$.t));
2797
- _v$27 !== _p$.a && (_p$.a = _$setProp(_el$79, "style", _v$27, _p$.a));
2798
- _v$28 !== _p$.o && (_p$.o = _$setProp(_el$80, "style", _v$28, _p$.o));
4181
+ _v$28 !== _p$.e && (_p$.e = _$setProp(_el$82, "style", _v$28, _p$.e));
4182
+ _v$29 !== _p$.t && (_p$.t = _$setProp(_el$84, "style", _v$29, _p$.t));
4183
+ _v$30 !== _p$.a && (_p$.a = _$setProp(_el$85, "style", _v$30, _p$.a));
4184
+ _v$31 !== _p$.o && (_p$.o = _$setProp(_el$86, "style", _v$31, _p$.o));
2799
4185
  return _p$;
2800
4186
  }, {
2801
4187
  e: void 0,
@@ -2803,7 +4189,7 @@ ${t("session.toast.copy_failed")}`, {
2803
4189
  a: void 0,
2804
4190
  o: void 0
2805
4191
  });
2806
- return _el$74;
4192
+ return _el$80;
2807
4193
  }
2808
4194
  }), _$createComponent(Show, {
2809
4195
  get when() {
@@ -2819,68 +4205,68 @@ ${t("session.toast.copy_failed")}`, {
2819
4205
  const rightW = (isRunning ? visualWidth(dismissLabel()) : 0) + (isRunning && entry.sessionId ? visualWidth(cancelLabel()) : 0);
2820
4206
  const spacerW = () => Math.max(1, panelWidth() - gutter() - openW() - rightW - 2);
2821
4207
  return (() => {
2822
- var _el$91 = _$createElement("box"), _el$95 = _$createElement("text");
2823
- _$insertNode(_el$91, _el$95);
2824
- _$setProp(_el$91, "flexDirection", "row");
2825
- _$insert(_el$91, _$createComponent(Show, {
4208
+ var _el$99 = _$createElement("box"), _el$103 = _$createElement("text");
4209
+ _$insertNode(_el$99, _el$103);
4210
+ _$setProp(_el$99, "flexDirection", "row");
4211
+ _$insert(_el$99, _$createComponent(Show, {
2826
4212
  get when() {
2827
4213
  return entry.sessionId;
2828
4214
  },
2829
4215
  get children() {
2830
- var _el$92 = _$createElement("text"), _el$93 = _$createElement("span"), _el$94 = _$createElement("span");
2831
- _$insertNode(_el$92, _el$93);
2832
- _$insertNode(_el$92, _el$94);
2833
- _$setProp(_el$92, "onMouseOver", () => setHoveredOpen(entry.id));
2834
- _$setProp(_el$92, "onMouseOut", () => setHoveredOpen(void 0));
2835
- _$setProp(_el$92, "onMouseUp", () => {
4216
+ var _el$100 = _$createElement("text"), _el$101 = _$createElement("span"), _el$102 = _$createElement("span");
4217
+ _$insertNode(_el$100, _el$101);
4218
+ _$insertNode(_el$100, _el$102);
4219
+ _$setProp(_el$100, "onMouseOver", () => setHoveredOpen(entry.id));
4220
+ _$setProp(_el$100, "onMouseOut", () => setHoveredOpen(void 0));
4221
+ _$setProp(_el$100, "onMouseUp", () => {
2836
4222
  if (entry.sessionId) {
2837
4223
  props.api.route.navigateSession(entry.sessionId);
2838
4224
  }
2839
4225
  });
2840
- _$insert(_el$93, openPrefix);
2841
- _$insert(_el$94, () => t("open.label"));
4226
+ _$insert(_el$101, openPrefix);
4227
+ _$insert(_el$102, () => t("open.label"));
2842
4228
  _$effect((_p$) => {
2843
- var _v$32 = {
4229
+ var _v$35 = {
2844
4230
  fg: hoveredOpen() === entry.id ? pal().warning : pal().primary
2845
- }, _v$33 = {
4231
+ }, _v$36 = {
2846
4232
  fg: hoveredOpen() === entry.id ? pal().warning : pal().primary
2847
4233
  };
2848
- _v$32 !== _p$.e && (_p$.e = _$setProp(_el$93, "style", _v$32, _p$.e));
2849
- _v$33 !== _p$.t && (_p$.t = _$setProp(_el$94, "style", _v$33, _p$.t));
4234
+ _v$35 !== _p$.e && (_p$.e = _$setProp(_el$101, "style", _v$35, _p$.e));
4235
+ _v$36 !== _p$.t && (_p$.t = _$setProp(_el$102, "style", _v$36, _p$.t));
2850
4236
  return _p$;
2851
4237
  }, {
2852
4238
  e: void 0,
2853
4239
  t: void 0
2854
4240
  });
2855
- return _el$92;
4241
+ return _el$100;
2856
4242
  }
2857
- }), _el$95);
2858
- _$insert(_el$95, () => " ".repeat(spacerW()));
2859
- _$insert(_el$91, _$createComponent(Show, {
4243
+ }), _el$103);
4244
+ _$insert(_el$103, () => " ".repeat(spacerW()));
4245
+ _$insert(_el$99, _$createComponent(Show, {
2860
4246
  get when() {
2861
4247
  return isRunning && entry.sessionId;
2862
4248
  },
2863
4249
  get children() {
2864
- var _el$96 = _$createElement("text"), _el$97 = _$createElement("span");
2865
- _$insertNode(_el$96, _el$97);
2866
- _$setProp(_el$96, "onMouseOver", () => setHoveredCancel(entry.id));
2867
- _$setProp(_el$96, "onMouseOut", () => setHoveredCancel(void 0));
2868
- _$setProp(_el$96, "onMouseUp", () => cancelEntry(entry));
2869
- _$insert(_el$97, cancelLabel);
2870
- _$effect((_$p) => _$setProp(_el$97, "style", {
4250
+ var _el$104 = _$createElement("text"), _el$105 = _$createElement("span");
4251
+ _$insertNode(_el$104, _el$105);
4252
+ _$setProp(_el$104, "onMouseOver", () => setHoveredCancel(entry.id));
4253
+ _$setProp(_el$104, "onMouseOut", () => setHoveredCancel(void 0));
4254
+ _$setProp(_el$104, "onMouseUp", () => cancelEntry(entry));
4255
+ _$insert(_el$105, cancelLabel);
4256
+ _$effect((_$p) => _$setProp(_el$105, "style", {
2871
4257
  fg: hoveredCancel() === entry.id ? pal().warning : pal().error
2872
4258
  }, _$p));
2873
- return _el$96;
4259
+ return _el$104;
2874
4260
  }
2875
4261
  }), null);
2876
- _$insert(_el$91, _$createComponent(Show, {
4262
+ _$insert(_el$99, _$createComponent(Show, {
2877
4263
  when: isRunning,
2878
4264
  get children() {
2879
- var _el$98 = _$createElement("text"), _el$99 = _$createElement("span");
2880
- _$insertNode(_el$98, _el$99);
2881
- _$setProp(_el$98, "onMouseOver", () => setHoveredDismiss(entry.id));
2882
- _$setProp(_el$98, "onMouseOut", () => setHoveredDismiss(void 0));
2883
- _$setProp(_el$98, "onMouseUp", () => {
4265
+ var _el$106 = _$createElement("text"), _el$107 = _$createElement("span");
4266
+ _$insertNode(_el$106, _el$107);
4267
+ _$setProp(_el$106, "onMouseOver", () => setHoveredDismiss(entry.id));
4268
+ _$setProp(_el$106, "onMouseOut", () => setHoveredDismiss(void 0));
4269
+ _$setProp(_el$106, "onMouseUp", () => {
2884
4270
  upsertEntry({
2885
4271
  id: entry.id,
2886
4272
  title: entry.title,
@@ -2889,17 +4275,17 @@ ${t("session.toast.copy_failed")}`, {
2889
4275
  status: "done"
2890
4276
  });
2891
4277
  });
2892
- _$insert(_el$99, dismissLabel);
2893
- _$effect((_$p) => _$setProp(_el$99, "style", {
4278
+ _$insert(_el$107, dismissLabel);
4279
+ _$effect((_$p) => _$setProp(_el$107, "style", {
2894
4280
  fg: hoveredDismiss() === entry.id ? pal().warning : pal().muted
2895
4281
  }, _$p));
2896
- return _el$98;
4282
+ return _el$106;
2897
4283
  }
2898
4284
  }), null);
2899
- _$effect((_$p) => _$setProp(_el$95, "style", {
4285
+ _$effect((_$p) => _$setProp(_el$103, "style", {
2900
4286
  fg: pal().muted
2901
4287
  }, _$p));
2902
- return _el$91;
4288
+ return _el$99;
2903
4289
  })();
2904
4290
  })();
2905
4291
  }
@@ -2920,13 +4306,13 @@ ${t("session.toast.copy_failed")}`, {
2920
4306
  const right = props.sortOrder() === "desc" ? `\u2191 ${t("scroll.top")}` : `\u2193 ${t("scroll.bottom")}`;
2921
4307
  const pad = showTop ? Math.max(1, panelWidth() - gutter() - visualWidth(left) - visualWidth(right)) : 0;
2922
4308
  return (() => {
2923
- var _el$100 = _$createElement("box"), _el$101 = _$createElement("text"), _el$102 = _$createElement("span");
2924
- _$insertNode(_el$100, _el$101);
2925
- _$setProp(_el$100, "flexDirection", "row");
2926
- _$insertNode(_el$101, _el$102);
2927
- _$setProp(_el$101, "onMouseOver", () => showMore && setHoveredMoreBelow(true));
2928
- _$setProp(_el$101, "onMouseOut", () => setHoveredMoreBelow(false));
2929
- _$setProp(_el$101, "onMouseUp", () => {
4309
+ var _el$108 = _$createElement("box"), _el$109 = _$createElement("text"), _el$110 = _$createElement("span");
4310
+ _$insertNode(_el$108, _el$109);
4311
+ _$setProp(_el$108, "flexDirection", "row");
4312
+ _$insertNode(_el$109, _el$110);
4313
+ _$setProp(_el$109, "onMouseOver", () => showMore && setHoveredMoreBelow(true));
4314
+ _$setProp(_el$109, "onMouseOut", () => setHoveredMoreBelow(false));
4315
+ _$setProp(_el$109, "onMouseUp", () => {
2930
4316
  if (!showMore) return;
2931
4317
  const total = entryList().length;
2932
4318
  const m = max();
@@ -2938,20 +4324,20 @@ ${t("session.toast.copy_failed")}`, {
2938
4324
  }
2939
4325
  setHoveredMoreBelow(false);
2940
4326
  });
2941
- _$insert(_el$102, left);
2942
- _$insert(_el$100, showTop ? [(() => {
2943
- var _el$103 = _$createElement("text");
2944
- _$insert(_el$103, () => " ".repeat(pad));
2945
- _$effect((_$p) => _$setProp(_el$103, "style", {
4327
+ _$insert(_el$110, left);
4328
+ _$insert(_el$108, showTop ? [(() => {
4329
+ var _el$111 = _$createElement("text");
4330
+ _$insert(_el$111, () => " ".repeat(pad));
4331
+ _$effect((_$p) => _$setProp(_el$111, "style", {
2946
4332
  fg: pal().muted
2947
4333
  }, _$p));
2948
- return _el$103;
4334
+ return _el$111;
2949
4335
  })(), (() => {
2950
- var _el$104 = _$createElement("text"), _el$105 = _$createElement("span");
2951
- _$insertNode(_el$104, _el$105);
2952
- _$setProp(_el$104, "onMouseOver", () => setHoveredTop(true));
2953
- _$setProp(_el$104, "onMouseOut", () => setHoveredTop(false));
2954
- _$setProp(_el$104, "onMouseUp", () => {
4336
+ var _el$112 = _$createElement("text"), _el$113 = _$createElement("span");
4337
+ _$insertNode(_el$112, _el$113);
4338
+ _$setProp(_el$112, "onMouseOver", () => setHoveredTop(true));
4339
+ _$setProp(_el$112, "onMouseOut", () => setHoveredTop(false));
4340
+ _$setProp(_el$112, "onMouseUp", () => {
2955
4341
  const total = entryList().length;
2956
4342
  const m = max();
2957
4343
  if (props.sortOrder() === "desc") {
@@ -2961,16 +4347,16 @@ ${t("session.toast.copy_failed")}`, {
2961
4347
  }
2962
4348
  setHoveredTop(false);
2963
4349
  });
2964
- _$insert(_el$105, right);
2965
- _$effect((_$p) => _$setProp(_el$105, "style", {
4350
+ _$insert(_el$113, right);
4351
+ _$effect((_$p) => _$setProp(_el$113, "style", {
2966
4352
  fg: hoveredTop() ? pal().warning : pal().muted
2967
4353
  }, _$p));
2968
- return _el$104;
4354
+ return _el$112;
2969
4355
  })()] : null, null);
2970
- _$effect((_$p) => _$setProp(_el$102, "style", {
4356
+ _$effect((_$p) => _$setProp(_el$110, "style", {
2971
4357
  fg: showMore && hoveredMoreBelow() ? pal().warning : pal().muted
2972
4358
  }, _$p));
2973
- return _el$100;
4359
+ return _el$108;
2974
4360
  })();
2975
4361
  })();
2976
4362
  }
@@ -2998,11 +4384,14 @@ ${t("session.toast.copy_failed")}`, {
2998
4384
  }
2999
4385
 
3000
4386
  // src/v2/index.tsx
3001
- function PluginRoot(props) {
4387
+ function CommandRoot(props) {
3002
4388
  props.context.keymap.layer(() => ({
3003
4389
  mode: "global",
3004
4390
  commands: makeCommands(props.context, props.api, props.signals)
3005
4391
  }));
4392
+ return null;
4393
+ }
4394
+ function PluginRoot(props) {
3006
4395
  return _$createComponent2(SubAgentPanel, {
3007
4396
  get api() {
3008
4397
  return props.api;
@@ -3025,6 +4414,21 @@ function PluginRoot(props) {
3025
4414
  get borderVisible() {
3026
4415
  return props.signals.borderVisible;
3027
4416
  },
4417
+ get showEntryCost() {
4418
+ return props.signals.showEntryCost;
4419
+ },
4420
+ get showEntryTime() {
4421
+ return props.signals.showEntryTime;
4422
+ },
4423
+ get showEntryTokens() {
4424
+ return props.signals.showEntryTokens;
4425
+ },
4426
+ get timeFormat() {
4427
+ return props.signals.timeFormat;
4428
+ },
4429
+ get showOrigin() {
4430
+ return props.signals.showOrigin;
4431
+ },
3028
4432
  get sessionId() {
3029
4433
  return props.sessionID;
3030
4434
  }
@@ -3050,6 +4454,13 @@ var mod = {
3050
4454
  const [orderSignal, setSortOrder] = createSignal3(String(api.kv.get(SETTING_KEYS.order, "desc")) === "asc" ? "asc" : "desc");
3051
4455
  const [scrollSignal, setScrollMode] = createSignal3(String(api.kv.get(SETTING_KEYS.scrollMode, "wheel")) === "click" ? "click" : "wheel");
3052
4456
  const [borderSignal, setBorderVisible] = createSignal3(api.kv.get(SETTING_KEYS.border, false) === true);
4457
+ const [showEntryCostSignal, setShowEntryCost] = createSignal3(api.kv.get(SETTING_KEYS.showEntryCost, false) === true);
4458
+ const [showEntryTimeSignal, setShowEntryTime] = createSignal3(api.kv.get(SETTING_KEYS.showEntryTime, true) !== false);
4459
+ const [showEntryTokensSignal, setShowEntryTokens] = createSignal3(api.kv.get(SETTING_KEYS.showEntryTokens, true) !== false);
4460
+ const storedTimeFormat = String(api.kv.get(SETTING_KEYS.timeFormat, "short"));
4461
+ const [timeFormatSignal, setTimeFormat] = createSignal3(TIME_FORMATS.includes(storedTimeFormat) ? storedTimeFormat : "short");
4462
+ const [showOriginSignal, setShowOrigin] = createSignal3(api.kv.get(SETTING_KEYS.showOrigin, false) === true);
4463
+ const [dbSyncSignal, setDbSync] = createSignal3(api.kv.get(SETTING_KEYS.dbSync, true) !== false);
3053
4464
  lang = langSignal;
3054
4465
  maxEntries = maxSignal;
3055
4466
  sortOrder = orderSignal;
@@ -3065,8 +4476,28 @@ var mod = {
3065
4476
  setScrollMode,
3066
4477
  borderVisible: borderSignal,
3067
4478
  setBorderVisible,
4479
+ showEntryCost: showEntryCostSignal,
4480
+ setShowEntryCost,
4481
+ showEntryTime: showEntryTimeSignal,
4482
+ setShowEntryTime,
4483
+ showEntryTokens: showEntryTokensSignal,
4484
+ setShowEntryTokens,
4485
+ timeFormat: timeFormatSignal,
4486
+ setTimeFormat,
4487
+ showOrigin: showOriginSignal,
4488
+ setShowOrigin,
4489
+ dbSync: dbSyncSignal,
4490
+ setDbSync,
3068
4491
  sessionId: ""
3069
4492
  };
4493
+ context.ui.slot({
4494
+ append: "app",
4495
+ render: () => _$createComponent2(CommandRoot, {
4496
+ context,
4497
+ api,
4498
+ signals
4499
+ })
4500
+ });
3070
4501
  context.ui.slot({
3071
4502
  prepend: "sidebar.content",
3072
4503
  render: (props) => {