opencode-subagent-magazine 1.6.0-beta.0 → 1.6.0-beta.2

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.
@@ -1 +1 @@
1
- export declare const PLUGIN_VERSION = "1.6.0-beta.0";
1
+ export declare const PLUGIN_VERSION = "1.6.0-beta.2";
package/dist/_version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // auto-generated
2
- export const PLUGIN_VERSION = "1.6.0-beta.0";
2
+ export const PLUGIN_VERSION = "1.6.0-beta.2";
@@ -1,2 +1,2 @@
1
1
  /** OpenCode built-in tool names that spawn sub-agents or delegate tasks. */
2
- export const SUBAGENT_TOOLS = new Set(["task", "delegate", "call_omo_agent"]);
2
+ export const SUBAGENT_TOOLS = new Set(["task", "subagent", "delegate", "call_omo_agent"]);
@@ -8,6 +8,7 @@ import { visualWidth, truncate, fmtDurationShort, fmtTokens, safeErrorMsg } from
8
8
  import { rgb, desaturateTo, dimColor, FALLBACK, MAX_SAT } from "../core/color";
9
9
  import { KV_PREFIX } from "../core/kv";
10
10
  import { globalEntryCache, clearTick } from "./store";
11
+ import { isDirectChildSession } from "./session-routing";
11
12
  /** Entry line left prefix: icon + space + status dot + space */
12
13
  const LEFT_PAD = 4;
13
14
  /** Detail row indent: two spaces */
@@ -334,6 +335,11 @@ export function SubAgentPanel(props) {
334
335
  const part = event.payload?.part;
335
336
  if (!part)
336
337
  return;
338
+ // V2 事件流是全局的——若 payload 带发起会话 ID,则只归账到正在查看的会话,
339
+ // 避免其他会话的子代理写进当前侧边栏;V1 宿主已按会话 scope,不传 sessionID。
340
+ const eventSid = event.payload?.sessionID !== undefined ? String(event.payload.sessionID) : undefined;
341
+ if (event.scope === "global" && eventSid !== props.sessionId)
342
+ return;
337
343
  // SubtaskPart
338
344
  if (part.type === "subtask") {
339
345
  const agent = String(part.agent ?? "?");
@@ -384,7 +390,7 @@ export function SubAgentPanel(props) {
384
390
  if (hasChild)
385
391
  status = "running";
386
392
  }
387
- const agent = String(part.subagent_type ?? input?.subagent_type ?? input?.category ?? tool);
393
+ const agent = String(part.subagent_type ?? input?.agent ?? input?.subagent_type ?? input?.category ?? tool);
388
394
  const prompt = String(input?.prompt ?? part.description ?? "");
389
395
  const desc = input?.description !== undefined ? String(input.description) : "";
390
396
  const title = desc || truncate(prompt.replace(/\n/g, " ").replace(/\s+/g, " ").trim(), 40);
@@ -403,6 +409,17 @@ export function SubAgentPanel(props) {
403
409
  const sid = String(props_?.sessionID ?? "");
404
410
  if (!sid)
405
411
  return;
412
+ const isGlobalEvent = event.scope === "global";
413
+ let eventSession;
414
+ if (isGlobalEvent) {
415
+ try {
416
+ eventSession = props.api.session.get(sid);
417
+ }
418
+ catch {
419
+ eventSession = undefined;
420
+ }
421
+ }
422
+ const isCurrentChild = !isGlobalEvent || isDirectChildSession(props.sessionId, sid, eventSession);
406
423
  const sessionTokens = props.api.usage.readSessionTokens(sid);
407
424
  const sessionCost = props.api.usage.readSessionCost(sid);
408
425
  const sessionModel = props.api.usage.readSessionModel(sid);
@@ -496,7 +513,7 @@ export function SubAgentPanel(props) {
496
513
  let changed = false;
497
514
  const next = new Map(prev);
498
515
  for (const [id, entry] of next) {
499
- if (entry.sessionId !== sid)
516
+ if (!isCurrentChild || entry.sessionId !== sid)
500
517
  continue;
501
518
  if (entry.status !== "running" && entry.status !== "done" && entry.status !== "cancel_requested")
502
519
  continue;
@@ -518,7 +535,7 @@ export function SubAgentPanel(props) {
518
535
  });
519
536
  changed = true;
520
537
  }
521
- if (!changed && sessionAgent) {
538
+ if (!changed && sessionAgent && isCurrentChild) {
522
539
  const nowTs = Date.now();
523
540
  const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9-]/g, "");
524
541
  const saNorm = normalize(sessionAgent);
@@ -567,8 +584,7 @@ export function SubAgentPanel(props) {
567
584
  // 当子代理所属的父 session 与当前视图不同时,通过模块级缓存定位
568
585
  // 并更新父 session 的 entry 状态,随后写回 KV。
569
586
  try {
570
- const sessionObj = props.api.session.get(sid);
571
- const parentSid = sessionObj?.parentID;
587
+ const parentSid = (isGlobalEvent ? eventSession : props.api.session.get(sid))?.parentID;
572
588
  if (parentSid && parentSid !== props.sessionId) {
573
589
  // 优先从模块级缓存获取父 session 的 entries,不受当前视图切换影响
574
590
  const parentCache = globalEntryCache.get(parentSid);
@@ -813,7 +829,7 @@ export function SubAgentPanel(props) {
813
829
  // If already tracked as running but tool state says completed/error → update
814
830
  // If not tracked → add fresh
815
831
  const input = st?.input;
816
- const agent = String(part.subagent_type ?? input?.subagent_type ?? tool);
832
+ const agent = String(part.subagent_type ?? input?.agent ?? input?.subagent_type ?? tool);
817
833
  const prompt = String(input?.prompt ?? part.description ?? "");
818
834
  const desc = input?.description !== undefined ? String(input.description) : "";
819
835
  const title = desc || truncate(prompt.replace(/\n/g, " ").trim(), 40);
@@ -16,8 +16,11 @@ export interface SessionStatusLike {
16
16
  * into these shapes so the panel component never touches host APIs directly.
17
17
  */
18
18
  export type PanelEventType = "part.updated" | "message.updated" | "session.idle" | "session.error";
19
+ export type PanelEventScope = "session" | "global";
19
20
  export interface PanelEvent {
20
21
  type: PanelEventType;
22
+ /** Global V2 events need explicit session ownership checks. */
23
+ scope?: PanelEventScope;
21
24
  /** part payload for part.updated; session properties for idle/error. */
22
25
  payload?: Record<string, unknown>;
23
26
  }
@@ -0,0 +1,3 @@
1
+ import type { SessionLike } from "./panel-api";
2
+ /** Whether an end event belongs to a child tracked by the current panel. */
3
+ export declare function isDirectChildSession(currentSessionId: string, eventSessionId: string, eventSession: SessionLike | undefined): boolean;
@@ -0,0 +1,4 @@
1
+ /** Whether an end event belongs to a child tracked by the current panel. */
2
+ export function isDirectChildSession(currentSessionId, eventSessionId, eventSession) {
3
+ return eventSessionId !== currentSessionId && eventSession?.parentID === currentSessionId;
4
+ }
package/dist/tui.js CHANGED
@@ -6,7 +6,7 @@ import { createComponent as _$createComponent2 } from "@opentui/solid";
6
6
  import { createSignal as createSignal3 } from "solid-js";
7
7
 
8
8
  // src/_version.ts
9
- var PLUGIN_VERSION = "1.6.0-beta.0";
9
+ var PLUGIN_VERSION = "1.6.0-beta.2";
10
10
 
11
11
  // src/i18n.ts
12
12
  var ZH_T = {
@@ -363,7 +363,7 @@ async function copyText(text) {
363
363
  }
364
364
 
365
365
  // src/core/types.ts
366
- var SUBAGENT_TOOLS = /* @__PURE__ */ new Set(["task", "delegate", "call_omo_agent"]);
366
+ var SUBAGENT_TOOLS = /* @__PURE__ */ new Set(["task", "subagent", "delegate", "call_omo_agent"]);
367
367
 
368
368
  // src/core/format.ts
369
369
  function charColumns(c) {
@@ -485,6 +485,11 @@ import { createSignal } from "solid-js";
485
485
  var globalEntryCache = /* @__PURE__ */ new Map();
486
486
  var [clearTick, setClearTick] = createSignal(0);
487
487
 
488
+ // src/panel/session-routing.ts
489
+ function isDirectChildSession(currentSessionId, eventSessionId, eventSession) {
490
+ return eventSessionId !== currentSessionId && eventSession?.parentID === currentSessionId;
491
+ }
492
+
488
493
  // src/panel/SubAgentPanel.tsx
489
494
  var LEFT_PAD = 4;
490
495
  var INDENT = 2;
@@ -901,6 +906,8 @@ function SubAgentPanel(props) {
901
906
  const handlePartUpdated = (event) => {
902
907
  const part = event.payload?.part;
903
908
  if (!part) return;
909
+ const eventSid = event.payload?.sessionID !== void 0 ? String(event.payload.sessionID) : void 0;
910
+ if (event.scope === "global" && eventSid !== props.sessionId) return;
904
911
  if (part.type === "subtask") {
905
912
  const agent = String(part.agent ?? "?");
906
913
  const prompt = String(part.prompt ?? "");
@@ -949,7 +956,7 @@ function SubAgentPanel(props) {
949
956
  const hasChild = stMetaCheck?.session_id !== void 0 || stMetaCheck?.sessionId !== void 0;
950
957
  if (hasChild) status = "running";
951
958
  }
952
- const agent = String(part.subagent_type ?? input?.subagent_type ?? input?.category ?? tool);
959
+ const agent = String(part.subagent_type ?? input?.agent ?? input?.subagent_type ?? input?.category ?? tool);
953
960
  const prompt = String(input?.prompt ?? part.description ?? "");
954
961
  const desc = input?.description !== void 0 ? String(input.description) : "";
955
962
  const title = desc || truncate(prompt.replace(/\n/g, " ").replace(/\s+/g, " ").trim(), 40);
@@ -970,6 +977,16 @@ function SubAgentPanel(props) {
970
977
  const props_ = event.payload;
971
978
  const sid = String(props_?.sessionID ?? "");
972
979
  if (!sid) return;
980
+ const isGlobalEvent = event.scope === "global";
981
+ let eventSession;
982
+ if (isGlobalEvent) {
983
+ try {
984
+ eventSession = props.api.session.get(sid);
985
+ } catch {
986
+ eventSession = void 0;
987
+ }
988
+ }
989
+ const isCurrentChild = !isGlobalEvent || isDirectChildSession(props.sessionId, sid, eventSession);
973
990
  const sessionTokens = props.api.usage.readSessionTokens(sid);
974
991
  const sessionCost = props.api.usage.readSessionCost(sid);
975
992
  const sessionModel = props.api.usage.readSessionModel(sid);
@@ -1058,7 +1075,7 @@ function SubAgentPanel(props) {
1058
1075
  let changed = false;
1059
1076
  const next = new Map(prev);
1060
1077
  for (const [id, entry] of next) {
1061
- if (entry.sessionId !== sid) continue;
1078
+ if (!isCurrentChild || entry.sessionId !== sid) continue;
1062
1079
  if (entry.status !== "running" && entry.status !== "done" && entry.status !== "cancel_requested") continue;
1063
1080
  if (sid === props.sessionId) continue;
1064
1081
  const alreadySettled = entry.status !== "running" && entry.status !== "cancel_requested";
@@ -1078,7 +1095,7 @@ function SubAgentPanel(props) {
1078
1095
  });
1079
1096
  changed = true;
1080
1097
  }
1081
- if (!changed && sessionAgent) {
1098
+ if (!changed && sessionAgent && isCurrentChild) {
1082
1099
  const nowTs = Date.now();
1083
1100
  const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9-]/g, "");
1084
1101
  const saNorm = normalize(sessionAgent);
@@ -1123,8 +1140,7 @@ function SubAgentPanel(props) {
1123
1140
  return changed ? next : prev;
1124
1141
  });
1125
1142
  try {
1126
- const sessionObj = props.api.session.get(sid);
1127
- const parentSid = sessionObj?.parentID;
1143
+ const parentSid = (isGlobalEvent ? eventSession : props.api.session.get(sid))?.parentID;
1128
1144
  if (parentSid && parentSid !== props.sessionId) {
1129
1145
  const parentCache = globalEntryCache.get(parentSid);
1130
1146
  const nowTs = Date.now();
@@ -1349,7 +1365,7 @@ function SubAgentPanel(props) {
1349
1365
  }
1350
1366
  }
1351
1367
  const input = st?.input;
1352
- const agent = String(part.subagent_type ?? input?.subagent_type ?? tool);
1368
+ const agent = String(part.subagent_type ?? input?.agent ?? input?.subagent_type ?? tool);
1353
1369
  const prompt = String(input?.prompt ?? part.description ?? "");
1354
1370
  const desc = input?.description !== void 0 ? String(input.description) : "";
1355
1371
  const title = desc || truncate(prompt.replace(/\n/g, " ").trim(), 40);
@@ -289,8 +289,15 @@ export function createPanelApi(context, settings) {
289
289
  for (const evt of ["session.tool.input.started", "session.tool.called", "session.tool.progress", "session.tool.success", "session.tool.failed"]) {
290
290
  unsubs.push(context.data.on(evt, (e) => {
291
291
  const part = toolEventToPart(e);
292
- if (part)
293
- cb({ type, payload: { part } });
292
+ if (!part)
293
+ return;
294
+ // V2 事件流是全局的(跨所有会话);把发起会话 ID 一并传给面板,
295
+ // 让面板只归账到正在查看的会话(V1 宿主已按会话 scope,无需该字段)。
296
+ const evt = e.data;
297
+ const sid = evt?.sessionID !== undefined ? String(evt.sessionID) : undefined;
298
+ if (!sid)
299
+ return;
300
+ cb({ type, scope: "global", payload: { part, sessionID: sid } });
294
301
  }));
295
302
  }
296
303
  return () => { for (const u of unsubs)
@@ -312,7 +319,7 @@ export function createPanelApi(context, settings) {
312
319
  unsubs.push(context.data.on(evt, (e) => {
313
320
  const sid = String(e.data?.sessionID ?? "");
314
321
  if (sid)
315
- cb({ type, payload: { sessionID: sid } });
322
+ cb({ type, scope: "global", payload: { sessionID: sid } });
316
323
  }));
317
324
  }
318
325
  return () => { for (const u of unsubs)
@@ -323,7 +330,7 @@ export function createPanelApi(context, settings) {
323
330
  const evt = e.data;
324
331
  const sid = String(evt?.sessionID ?? "");
325
332
  if (sid)
326
- cb({ type, payload: { sessionID: sid, error: evt?.error } });
333
+ cb({ type, scope: "global", payload: { sessionID: sid, error: evt?.error } });
327
334
  });
328
335
  default:
329
336
  return () => { };
package/dist/v2.js CHANGED
@@ -239,7 +239,11 @@ function createPanelApi(context, settings) {
239
239
  for (const evt of ["session.tool.input.started", "session.tool.called", "session.tool.progress", "session.tool.success", "session.tool.failed"]) {
240
240
  unsubs.push(context.data.on(evt, (e) => {
241
241
  const part = toolEventToPart(e);
242
- if (part) cb({ type, payload: { part } });
242
+ if (!part) return;
243
+ const evt2 = e.data;
244
+ const sid = evt2?.sessionID !== void 0 ? String(evt2.sessionID) : void 0;
245
+ if (!sid) return;
246
+ cb({ type, scope: "global", payload: { part, sessionID: sid } });
243
247
  }));
244
248
  }
245
249
  return () => {
@@ -260,7 +264,7 @@ function createPanelApi(context, settings) {
260
264
  for (const evt of ["session.execution.succeeded", "session.execution.interrupted"]) {
261
265
  unsubs.push(context.data.on(evt, (e) => {
262
266
  const sid = String(e.data?.sessionID ?? "");
263
- if (sid) cb({ type, payload: { sessionID: sid } });
267
+ if (sid) cb({ type, scope: "global", payload: { sessionID: sid } });
264
268
  }));
265
269
  }
266
270
  return () => {
@@ -271,7 +275,7 @@ function createPanelApi(context, settings) {
271
275
  return context.data.on("session.execution.failed", (e) => {
272
276
  const evt = e.data;
273
277
  const sid = String(evt?.sessionID ?? "");
274
- if (sid) cb({ type, payload: { sessionID: sid, error: evt?.error } });
278
+ if (sid) cb({ type, scope: "global", payload: { sessionID: sid, error: evt?.error } });
275
279
  });
276
280
  default:
277
281
  return () => {
@@ -325,7 +329,7 @@ function readTTLDays(kv) {
325
329
  }
326
330
 
327
331
  // src/_version.ts
328
- var PLUGIN_VERSION = "1.6.0-beta.0";
332
+ var PLUGIN_VERSION = "1.6.0-beta.2";
329
333
 
330
334
  // src/i18n.ts
331
335
  var ZH_T = {
@@ -931,7 +935,7 @@ async function copyText(text) {
931
935
  }
932
936
 
933
937
  // src/core/types.ts
934
- var SUBAGENT_TOOLS = /* @__PURE__ */ new Set(["task", "delegate", "call_omo_agent"]);
938
+ var SUBAGENT_TOOLS = /* @__PURE__ */ new Set(["task", "subagent", "delegate", "call_omo_agent"]);
935
939
 
936
940
  // src/core/format.ts
937
941
  function charColumns(c) {
@@ -1048,6 +1052,11 @@ var FALLBACK = {
1048
1052
  };
1049
1053
  var MAX_SAT = 0.28;
1050
1054
 
1055
+ // src/panel/session-routing.ts
1056
+ function isDirectChildSession(currentSessionId, eventSessionId, eventSession) {
1057
+ return eventSessionId !== currentSessionId && eventSession?.parentID === currentSessionId;
1058
+ }
1059
+
1051
1060
  // src/panel/SubAgentPanel.tsx
1052
1061
  var LEFT_PAD = 4;
1053
1062
  var INDENT = 2;
@@ -1464,6 +1473,8 @@ function SubAgentPanel(props) {
1464
1473
  const handlePartUpdated = (event) => {
1465
1474
  const part = event.payload?.part;
1466
1475
  if (!part) return;
1476
+ const eventSid = event.payload?.sessionID !== void 0 ? String(event.payload.sessionID) : void 0;
1477
+ if (event.scope === "global" && eventSid !== props.sessionId) return;
1467
1478
  if (part.type === "subtask") {
1468
1479
  const agent = String(part.agent ?? "?");
1469
1480
  const prompt = String(part.prompt ?? "");
@@ -1512,7 +1523,7 @@ function SubAgentPanel(props) {
1512
1523
  const hasChild = stMetaCheck?.session_id !== void 0 || stMetaCheck?.sessionId !== void 0;
1513
1524
  if (hasChild) status = "running";
1514
1525
  }
1515
- const agent = String(part.subagent_type ?? input?.subagent_type ?? input?.category ?? tool);
1526
+ const agent = String(part.subagent_type ?? input?.agent ?? input?.subagent_type ?? input?.category ?? tool);
1516
1527
  const prompt = String(input?.prompt ?? part.description ?? "");
1517
1528
  const desc = input?.description !== void 0 ? String(input.description) : "";
1518
1529
  const title = desc || truncate(prompt.replace(/\n/g, " ").replace(/\s+/g, " ").trim(), 40);
@@ -1533,6 +1544,16 @@ function SubAgentPanel(props) {
1533
1544
  const props_ = event.payload;
1534
1545
  const sid = String(props_?.sessionID ?? "");
1535
1546
  if (!sid) return;
1547
+ const isGlobalEvent = event.scope === "global";
1548
+ let eventSession;
1549
+ if (isGlobalEvent) {
1550
+ try {
1551
+ eventSession = props.api.session.get(sid);
1552
+ } catch {
1553
+ eventSession = void 0;
1554
+ }
1555
+ }
1556
+ const isCurrentChild = !isGlobalEvent || isDirectChildSession(props.sessionId, sid, eventSession);
1536
1557
  const sessionTokens = props.api.usage.readSessionTokens(sid);
1537
1558
  const sessionCost = props.api.usage.readSessionCost(sid);
1538
1559
  const sessionModel = props.api.usage.readSessionModel(sid);
@@ -1621,7 +1642,7 @@ function SubAgentPanel(props) {
1621
1642
  let changed = false;
1622
1643
  const next = new Map(prev);
1623
1644
  for (const [id, entry] of next) {
1624
- if (entry.sessionId !== sid) continue;
1645
+ if (!isCurrentChild || entry.sessionId !== sid) continue;
1625
1646
  if (entry.status !== "running" && entry.status !== "done" && entry.status !== "cancel_requested") continue;
1626
1647
  if (sid === props.sessionId) continue;
1627
1648
  const alreadySettled = entry.status !== "running" && entry.status !== "cancel_requested";
@@ -1641,7 +1662,7 @@ function SubAgentPanel(props) {
1641
1662
  });
1642
1663
  changed = true;
1643
1664
  }
1644
- if (!changed && sessionAgent) {
1665
+ if (!changed && sessionAgent && isCurrentChild) {
1645
1666
  const nowTs = Date.now();
1646
1667
  const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9-]/g, "");
1647
1668
  const saNorm = normalize(sessionAgent);
@@ -1686,8 +1707,7 @@ function SubAgentPanel(props) {
1686
1707
  return changed ? next : prev;
1687
1708
  });
1688
1709
  try {
1689
- const sessionObj = props.api.session.get(sid);
1690
- const parentSid = sessionObj?.parentID;
1710
+ const parentSid = (isGlobalEvent ? eventSession : props.api.session.get(sid))?.parentID;
1691
1711
  if (parentSid && parentSid !== props.sessionId) {
1692
1712
  const parentCache = globalEntryCache.get(parentSid);
1693
1713
  const nowTs = Date.now();
@@ -1912,7 +1932,7 @@ function SubAgentPanel(props) {
1912
1932
  }
1913
1933
  }
1914
1934
  const input = st?.input;
1915
- const agent = String(part.subagent_type ?? input?.subagent_type ?? tool);
1935
+ const agent = String(part.subagent_type ?? input?.agent ?? input?.subagent_type ?? tool);
1916
1936
  const prompt = String(input?.prompt ?? part.description ?? "");
1917
1937
  const desc = input?.description !== void 0 ? String(input.description) : "";
1918
1938
  const title = desc || truncate(prompt.replace(/\n/g, " ").trim(), 40);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-subagent-magazine",
3
- "version": "1.6.0-beta.0",
3
+ "version": "1.6.0-beta.2",
4
4
  "description": "OpenCode TUI plugin monitoring sub-agent invocation status in real time",
5
5
  "type": "module",
6
6
  "types": "dist/index.d.ts",
@@ -30,6 +30,7 @@
30
30
  "build": "tsc && node build.tui.mjs",
31
31
  "build:tui": "node build.tui.mjs",
32
32
  "typecheck": "tsc --noEmit",
33
+ "test": "tsx --test tests/*.test.ts",
33
34
  "version": "node -e \"require('fs').writeFileSync('src/_version.ts','// auto-generated\\nexport const PLUGIN_VERSION='+JSON.stringify(require('./package.json').version)+';\\n')\"",
34
35
  "prepublishOnly": "tsc"
35
36
  },
package/src/_version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // auto-generated
2
- export const PLUGIN_VERSION="1.6.0-beta.0";
2
+ export const PLUGIN_VERSION="1.6.0-beta.2";
package/src/core/types.ts CHANGED
@@ -1,58 +1,58 @@
1
- import type { LangCode } from "../i18n"
2
-
3
- export type SubStatus = "running" | "done" | "error" | "cancel_requested" | "cancelled"
4
-
5
- export interface SubEntry {
6
- id: string
7
- title: string
8
- agent: string
9
- prompt: string
10
- error?: string
11
- tokens?: number
12
- cost?: number
13
- status: SubStatus
14
- sessionId?: string
15
- startedAt: number
16
- endedAt?: number
17
- model?: string
18
- todoTotal?: number
19
- todoDone?: number
20
- cancelRequestedAt?: number
21
- abortAccepted?: boolean
22
- cancelReason?: "manual"
23
- }
24
-
25
- export type Lang = LangCode
26
- export type SortOrder = "desc" | "asc"
27
- export type ScrollMode = "wheel" | "click"
28
-
29
- /** OpenCode built-in tool names that spawn sub-agents or delegate tasks. */
30
- export const SUBAGENT_TOOLS = new Set(["task", "delegate", "call_omo_agent"])
31
-
32
- export interface ChildRecord {
33
- scroll: number
34
- expanded: string
35
- entries: SubEntry[]
36
- clearedIds?: string[]
37
- }
38
-
39
- export interface SessionRecord {
40
- ts: number
41
- entries: SubEntry[]
42
- scroll: number
43
- expanded: string
44
- children: Record<string, ChildRecord>
45
- clearedIds?: string[]
46
- }
47
-
48
- export interface SharedSignals {
49
- lang: () => Lang
50
- setLang: (l: Lang) => void
51
- maxEntries: () => number
52
- setMaxEntries: (n: number) => void
53
- sortOrder: () => SortOrder
54
- setSortOrder: (o: SortOrder) => void
55
- scrollMode: () => ScrollMode
56
- setScrollMode: (m: ScrollMode) => void
57
- sessionId: string
58
- }
1
+ import type { LangCode } from "../i18n"
2
+
3
+ export type SubStatus = "running" | "done" | "error" | "cancel_requested" | "cancelled"
4
+
5
+ export interface SubEntry {
6
+ id: string
7
+ title: string
8
+ agent: string
9
+ prompt: string
10
+ error?: string
11
+ tokens?: number
12
+ cost?: number
13
+ status: SubStatus
14
+ sessionId?: string
15
+ startedAt: number
16
+ endedAt?: number
17
+ model?: string
18
+ todoTotal?: number
19
+ todoDone?: number
20
+ cancelRequestedAt?: number
21
+ abortAccepted?: boolean
22
+ cancelReason?: "manual"
23
+ }
24
+
25
+ export type Lang = LangCode
26
+ export type SortOrder = "desc" | "asc"
27
+ export type ScrollMode = "wheel" | "click"
28
+
29
+ /** OpenCode built-in tool names that spawn sub-agents or delegate tasks. */
30
+ export const SUBAGENT_TOOLS = new Set(["task", "subagent", "delegate", "call_omo_agent"])
31
+
32
+ export interface ChildRecord {
33
+ scroll: number
34
+ expanded: string
35
+ entries: SubEntry[]
36
+ clearedIds?: string[]
37
+ }
38
+
39
+ export interface SessionRecord {
40
+ ts: number
41
+ entries: SubEntry[]
42
+ scroll: number
43
+ expanded: string
44
+ children: Record<string, ChildRecord>
45
+ clearedIds?: string[]
46
+ }
47
+
48
+ export interface SharedSignals {
49
+ lang: () => Lang
50
+ setLang: (l: Lang) => void
51
+ maxEntries: () => number
52
+ setMaxEntries: (n: number) => void
53
+ sortOrder: () => SortOrder
54
+ setSortOrder: (o: SortOrder) => void
55
+ scrollMode: () => ScrollMode
56
+ setScrollMode: (m: ScrollMode) => void
57
+ sessionId: string
58
+ }
@@ -21,6 +21,7 @@ import { rgb, desaturateTo, dimColor, FALLBACK, MAX_SAT } from "../core/color"
21
21
  import { KV_PREFIX } from "../core/kv"
22
22
  import type { PanelApi, PanelEvent } from "./panel-api"
23
23
  import { globalEntryCache, clearTick } from "./store"
24
+ import { isDirectChildSession } from "./session-routing"
24
25
 
25
26
  /** Entry line left prefix: icon + space + status dot + space */
26
27
  const LEFT_PAD = 4
@@ -360,6 +361,11 @@ export function SubAgentPanel(props: {
360
361
  const part = event.payload?.part as Record<string, unknown> | undefined
361
362
  if (!part) return
362
363
 
364
+ // V2 事件流是全局的——若 payload 带发起会话 ID,则只归账到正在查看的会话,
365
+ // 避免其他会话的子代理写进当前侧边栏;V1 宿主已按会话 scope,不传 sessionID。
366
+ const eventSid = event.payload?.sessionID !== undefined ? String(event.payload.sessionID) : undefined
367
+ if (event.scope === "global" && eventSid !== props.sessionId) return
368
+
363
369
  // SubtaskPart
364
370
  if (part.type === "subtask") {
365
371
  const agent = String(part.agent ?? "?")
@@ -411,7 +417,7 @@ export function SubAgentPanel(props: {
411
417
  if (hasChild) status = "running"
412
418
  }
413
419
 
414
- const agent = String((part as any).subagent_type ?? input?.subagent_type ?? input?.category ?? tool)
420
+ const agent = String((part as any).subagent_type ?? input?.agent ?? input?.subagent_type ?? input?.category ?? tool)
415
421
  const prompt = String(input?.prompt ?? (part as any).description ?? "")
416
422
  const desc = input?.description !== undefined ? String(input.description) : ""
417
423
  const title = desc || truncate(prompt.replace(/\n/g, " ").replace(/\s+/g, " ").trim(), 40)
@@ -432,6 +438,13 @@ export function SubAgentPanel(props: {
432
438
  const sid = String(props_?.sessionID ?? "")
433
439
  if (!sid) return
434
440
 
441
+ const isGlobalEvent = event.scope === "global"
442
+ let eventSession: ReturnType<PanelApi["session"]["get"]>
443
+ if (isGlobalEvent) {
444
+ try { eventSession = props.api.session.get(sid) } catch { eventSession = undefined }
445
+ }
446
+ const isCurrentChild = !isGlobalEvent || isDirectChildSession(props.sessionId, sid, eventSession)
447
+
435
448
  const sessionTokens = props.api.usage.readSessionTokens(sid)
436
449
  const sessionCost = props.api.usage.readSessionCost(sid)
437
450
  const sessionModel = props.api.usage.readSessionModel(sid)
@@ -524,7 +537,7 @@ export function SubAgentPanel(props: {
524
537
  let changed = false
525
538
  const next = new Map(prev)
526
539
  for (const [id, entry] of next) {
527
- if (entry.sessionId !== sid) continue
540
+ if (!isCurrentChild || entry.sessionId !== sid) continue
528
541
  if (entry.status !== "running" && entry.status !== "done" && entry.status !== "cancel_requested") continue
529
542
  // Skip parent session idle — subagent entries belong to child sessions only
530
543
  if (sid === props.sessionId) continue
@@ -543,7 +556,7 @@ export function SubAgentPanel(props: {
543
556
  })
544
557
  changed = true
545
558
  }
546
- if (!changed && sessionAgent) {
559
+ if (!changed && sessionAgent && isCurrentChild) {
547
560
  const nowTs = Date.now()
548
561
  const normalize = (s: string) => s.toLowerCase().replace(/[^a-z0-9-]/g, "")
549
562
  const saNorm = normalize(sessionAgent)
@@ -589,8 +602,7 @@ export function SubAgentPanel(props: {
589
602
  // 当子代理所属的父 session 与当前视图不同时,通过模块级缓存定位
590
603
  // 并更新父 session 的 entry 状态,随后写回 KV。
591
604
  try {
592
- const sessionObj = props.api.session.get(sid)
593
- const parentSid = sessionObj?.parentID
605
+ const parentSid = (isGlobalEvent ? eventSession : props.api.session.get(sid))?.parentID
594
606
  if (parentSid && parentSid !== props.sessionId) {
595
607
  // 优先从模块级缓存获取父 session 的 entries,不受当前视图切换影响
596
608
  const parentCache = globalEntryCache.get(parentSid)
@@ -829,7 +841,7 @@ export function SubAgentPanel(props: {
829
841
  // If not tracked → add fresh
830
842
 
831
843
  const input = st?.input as Record<string, unknown> | undefined
832
- const agent = String((part as any).subagent_type ?? input?.subagent_type ?? tool)
844
+ const agent = String((part as any).subagent_type ?? input?.agent ?? input?.subagent_type ?? tool)
833
845
  const prompt = String(input?.prompt ?? (part as any).description ?? "")
834
846
  const desc = input?.description !== undefined ? String(input.description) : ""
835
847
  const title = desc || truncate(prompt.replace(/\n/g, " ").trim(), 40)