opencode-subagent-magazine 1.6.0-beta.1 → 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.
- package/dist/_version.d.ts +1 -1
- package/dist/_version.js +1 -1
- package/dist/panel/SubAgentPanel.js +16 -5
- package/dist/panel/panel-api.d.ts +3 -0
- package/dist/panel/session-routing.d.ts +3 -0
- package/dist/panel/session-routing.js +4 -0
- package/dist/tui.js +20 -6
- package/dist/v2/v2-panel-api.js +5 -3
- package/dist/v2.js +24 -9
- package/package.json +2 -1
- package/src/_version.ts +1 -1
- package/src/panel/SubAgentPanel.tsx +12 -5
- package/src/panel/panel-api.ts +75 -72
- package/src/panel/session-routing.ts +10 -0
- package/src/v2/v2-panel-api.ts +4 -3
package/dist/_version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const PLUGIN_VERSION = "1.6.0-beta.
|
|
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.
|
|
2
|
+
export const PLUGIN_VERSION = "1.6.0-beta.2";
|
|
@@ -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 */
|
|
@@ -337,7 +338,7 @@ export function SubAgentPanel(props) {
|
|
|
337
338
|
// V2 事件流是全局的——若 payload 带发起会话 ID,则只归账到正在查看的会话,
|
|
338
339
|
// 避免其他会话的子代理写进当前侧边栏;V1 宿主已按会话 scope,不传 sessionID。
|
|
339
340
|
const eventSid = event.payload?.sessionID !== undefined ? String(event.payload.sessionID) : undefined;
|
|
340
|
-
if (
|
|
341
|
+
if (event.scope === "global" && eventSid !== props.sessionId)
|
|
341
342
|
return;
|
|
342
343
|
// SubtaskPart
|
|
343
344
|
if (part.type === "subtask") {
|
|
@@ -408,6 +409,17 @@ export function SubAgentPanel(props) {
|
|
|
408
409
|
const sid = String(props_?.sessionID ?? "");
|
|
409
410
|
if (!sid)
|
|
410
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);
|
|
411
423
|
const sessionTokens = props.api.usage.readSessionTokens(sid);
|
|
412
424
|
const sessionCost = props.api.usage.readSessionCost(sid);
|
|
413
425
|
const sessionModel = props.api.usage.readSessionModel(sid);
|
|
@@ -501,7 +513,7 @@ export function SubAgentPanel(props) {
|
|
|
501
513
|
let changed = false;
|
|
502
514
|
const next = new Map(prev);
|
|
503
515
|
for (const [id, entry] of next) {
|
|
504
|
-
if (entry.sessionId !== sid)
|
|
516
|
+
if (!isCurrentChild || entry.sessionId !== sid)
|
|
505
517
|
continue;
|
|
506
518
|
if (entry.status !== "running" && entry.status !== "done" && entry.status !== "cancel_requested")
|
|
507
519
|
continue;
|
|
@@ -523,7 +535,7 @@ export function SubAgentPanel(props) {
|
|
|
523
535
|
});
|
|
524
536
|
changed = true;
|
|
525
537
|
}
|
|
526
|
-
if (!changed && sessionAgent) {
|
|
538
|
+
if (!changed && sessionAgent && isCurrentChild) {
|
|
527
539
|
const nowTs = Date.now();
|
|
528
540
|
const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9-]/g, "");
|
|
529
541
|
const saNorm = normalize(sessionAgent);
|
|
@@ -572,8 +584,7 @@ export function SubAgentPanel(props) {
|
|
|
572
584
|
// 当子代理所属的父 session 与当前视图不同时,通过模块级缓存定位
|
|
573
585
|
// 并更新父 session 的 entry 状态,随后写回 KV。
|
|
574
586
|
try {
|
|
575
|
-
const
|
|
576
|
-
const parentSid = sessionObj?.parentID;
|
|
587
|
+
const parentSid = (isGlobalEvent ? eventSession : props.api.session.get(sid))?.parentID;
|
|
577
588
|
if (parentSid && parentSid !== props.sessionId) {
|
|
578
589
|
// 优先从模块级缓存获取父 session 的 entries,不受当前视图切换影响
|
|
579
590
|
const parentCache = globalEntryCache.get(parentSid);
|
|
@@ -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
|
}
|
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.
|
|
9
|
+
var PLUGIN_VERSION = "1.6.0-beta.2";
|
|
10
10
|
|
|
11
11
|
// src/i18n.ts
|
|
12
12
|
var ZH_T = {
|
|
@@ -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;
|
|
@@ -902,7 +907,7 @@ function SubAgentPanel(props) {
|
|
|
902
907
|
const part = event.payload?.part;
|
|
903
908
|
if (!part) return;
|
|
904
909
|
const eventSid = event.payload?.sessionID !== void 0 ? String(event.payload.sessionID) : void 0;
|
|
905
|
-
if (
|
|
910
|
+
if (event.scope === "global" && eventSid !== props.sessionId) return;
|
|
906
911
|
if (part.type === "subtask") {
|
|
907
912
|
const agent = String(part.agent ?? "?");
|
|
908
913
|
const prompt = String(part.prompt ?? "");
|
|
@@ -972,6 +977,16 @@ function SubAgentPanel(props) {
|
|
|
972
977
|
const props_ = event.payload;
|
|
973
978
|
const sid = String(props_?.sessionID ?? "");
|
|
974
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);
|
|
975
990
|
const sessionTokens = props.api.usage.readSessionTokens(sid);
|
|
976
991
|
const sessionCost = props.api.usage.readSessionCost(sid);
|
|
977
992
|
const sessionModel = props.api.usage.readSessionModel(sid);
|
|
@@ -1060,7 +1075,7 @@ function SubAgentPanel(props) {
|
|
|
1060
1075
|
let changed = false;
|
|
1061
1076
|
const next = new Map(prev);
|
|
1062
1077
|
for (const [id, entry] of next) {
|
|
1063
|
-
if (entry.sessionId !== sid) continue;
|
|
1078
|
+
if (!isCurrentChild || entry.sessionId !== sid) continue;
|
|
1064
1079
|
if (entry.status !== "running" && entry.status !== "done" && entry.status !== "cancel_requested") continue;
|
|
1065
1080
|
if (sid === props.sessionId) continue;
|
|
1066
1081
|
const alreadySettled = entry.status !== "running" && entry.status !== "cancel_requested";
|
|
@@ -1080,7 +1095,7 @@ function SubAgentPanel(props) {
|
|
|
1080
1095
|
});
|
|
1081
1096
|
changed = true;
|
|
1082
1097
|
}
|
|
1083
|
-
if (!changed && sessionAgent) {
|
|
1098
|
+
if (!changed && sessionAgent && isCurrentChild) {
|
|
1084
1099
|
const nowTs = Date.now();
|
|
1085
1100
|
const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9-]/g, "");
|
|
1086
1101
|
const saNorm = normalize(sessionAgent);
|
|
@@ -1125,8 +1140,7 @@ function SubAgentPanel(props) {
|
|
|
1125
1140
|
return changed ? next : prev;
|
|
1126
1141
|
});
|
|
1127
1142
|
try {
|
|
1128
|
-
const
|
|
1129
|
-
const parentSid = sessionObj?.parentID;
|
|
1143
|
+
const parentSid = (isGlobalEvent ? eventSession : props.api.session.get(sid))?.parentID;
|
|
1130
1144
|
if (parentSid && parentSid !== props.sessionId) {
|
|
1131
1145
|
const parentCache = globalEntryCache.get(parentSid);
|
|
1132
1146
|
const nowTs = Date.now();
|
package/dist/v2/v2-panel-api.js
CHANGED
|
@@ -295,7 +295,9 @@ export function createPanelApi(context, settings) {
|
|
|
295
295
|
// 让面板只归账到正在查看的会话(V1 宿主已按会话 scope,无需该字段)。
|
|
296
296
|
const evt = e.data;
|
|
297
297
|
const sid = evt?.sessionID !== undefined ? String(evt.sessionID) : undefined;
|
|
298
|
-
|
|
298
|
+
if (!sid)
|
|
299
|
+
return;
|
|
300
|
+
cb({ type, scope: "global", payload: { part, sessionID: sid } });
|
|
299
301
|
}));
|
|
300
302
|
}
|
|
301
303
|
return () => { for (const u of unsubs)
|
|
@@ -317,7 +319,7 @@ export function createPanelApi(context, settings) {
|
|
|
317
319
|
unsubs.push(context.data.on(evt, (e) => {
|
|
318
320
|
const sid = String(e.data?.sessionID ?? "");
|
|
319
321
|
if (sid)
|
|
320
|
-
cb({ type, payload: { sessionID: sid } });
|
|
322
|
+
cb({ type, scope: "global", payload: { sessionID: sid } });
|
|
321
323
|
}));
|
|
322
324
|
}
|
|
323
325
|
return () => { for (const u of unsubs)
|
|
@@ -328,7 +330,7 @@ export function createPanelApi(context, settings) {
|
|
|
328
330
|
const evt = e.data;
|
|
329
331
|
const sid = String(evt?.sessionID ?? "");
|
|
330
332
|
if (sid)
|
|
331
|
-
cb({ type, payload: { sessionID: sid, error: evt?.error } });
|
|
333
|
+
cb({ type, scope: "global", payload: { sessionID: sid, error: evt?.error } });
|
|
332
334
|
});
|
|
333
335
|
default:
|
|
334
336
|
return () => { };
|
package/dist/v2.js
CHANGED
|
@@ -242,7 +242,8 @@ function createPanelApi(context, settings) {
|
|
|
242
242
|
if (!part) return;
|
|
243
243
|
const evt2 = e.data;
|
|
244
244
|
const sid = evt2?.sessionID !== void 0 ? String(evt2.sessionID) : void 0;
|
|
245
|
-
|
|
245
|
+
if (!sid) return;
|
|
246
|
+
cb({ type, scope: "global", payload: { part, sessionID: sid } });
|
|
246
247
|
}));
|
|
247
248
|
}
|
|
248
249
|
return () => {
|
|
@@ -263,7 +264,7 @@ function createPanelApi(context, settings) {
|
|
|
263
264
|
for (const evt of ["session.execution.succeeded", "session.execution.interrupted"]) {
|
|
264
265
|
unsubs.push(context.data.on(evt, (e) => {
|
|
265
266
|
const sid = String(e.data?.sessionID ?? "");
|
|
266
|
-
if (sid) cb({ type, payload: { sessionID: sid } });
|
|
267
|
+
if (sid) cb({ type, scope: "global", payload: { sessionID: sid } });
|
|
267
268
|
}));
|
|
268
269
|
}
|
|
269
270
|
return () => {
|
|
@@ -274,7 +275,7 @@ function createPanelApi(context, settings) {
|
|
|
274
275
|
return context.data.on("session.execution.failed", (e) => {
|
|
275
276
|
const evt = e.data;
|
|
276
277
|
const sid = String(evt?.sessionID ?? "");
|
|
277
|
-
if (sid) cb({ type, payload: { sessionID: sid, error: evt?.error } });
|
|
278
|
+
if (sid) cb({ type, scope: "global", payload: { sessionID: sid, error: evt?.error } });
|
|
278
279
|
});
|
|
279
280
|
default:
|
|
280
281
|
return () => {
|
|
@@ -328,7 +329,7 @@ function readTTLDays(kv) {
|
|
|
328
329
|
}
|
|
329
330
|
|
|
330
331
|
// src/_version.ts
|
|
331
|
-
var PLUGIN_VERSION = "1.6.0-beta.
|
|
332
|
+
var PLUGIN_VERSION = "1.6.0-beta.2";
|
|
332
333
|
|
|
333
334
|
// src/i18n.ts
|
|
334
335
|
var ZH_T = {
|
|
@@ -1051,6 +1052,11 @@ var FALLBACK = {
|
|
|
1051
1052
|
};
|
|
1052
1053
|
var MAX_SAT = 0.28;
|
|
1053
1054
|
|
|
1055
|
+
// src/panel/session-routing.ts
|
|
1056
|
+
function isDirectChildSession(currentSessionId, eventSessionId, eventSession) {
|
|
1057
|
+
return eventSessionId !== currentSessionId && eventSession?.parentID === currentSessionId;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1054
1060
|
// src/panel/SubAgentPanel.tsx
|
|
1055
1061
|
var LEFT_PAD = 4;
|
|
1056
1062
|
var INDENT = 2;
|
|
@@ -1468,7 +1474,7 @@ function SubAgentPanel(props) {
|
|
|
1468
1474
|
const part = event.payload?.part;
|
|
1469
1475
|
if (!part) return;
|
|
1470
1476
|
const eventSid = event.payload?.sessionID !== void 0 ? String(event.payload.sessionID) : void 0;
|
|
1471
|
-
if (
|
|
1477
|
+
if (event.scope === "global" && eventSid !== props.sessionId) return;
|
|
1472
1478
|
if (part.type === "subtask") {
|
|
1473
1479
|
const agent = String(part.agent ?? "?");
|
|
1474
1480
|
const prompt = String(part.prompt ?? "");
|
|
@@ -1538,6 +1544,16 @@ function SubAgentPanel(props) {
|
|
|
1538
1544
|
const props_ = event.payload;
|
|
1539
1545
|
const sid = String(props_?.sessionID ?? "");
|
|
1540
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);
|
|
1541
1557
|
const sessionTokens = props.api.usage.readSessionTokens(sid);
|
|
1542
1558
|
const sessionCost = props.api.usage.readSessionCost(sid);
|
|
1543
1559
|
const sessionModel = props.api.usage.readSessionModel(sid);
|
|
@@ -1626,7 +1642,7 @@ function SubAgentPanel(props) {
|
|
|
1626
1642
|
let changed = false;
|
|
1627
1643
|
const next = new Map(prev);
|
|
1628
1644
|
for (const [id, entry] of next) {
|
|
1629
|
-
if (entry.sessionId !== sid) continue;
|
|
1645
|
+
if (!isCurrentChild || entry.sessionId !== sid) continue;
|
|
1630
1646
|
if (entry.status !== "running" && entry.status !== "done" && entry.status !== "cancel_requested") continue;
|
|
1631
1647
|
if (sid === props.sessionId) continue;
|
|
1632
1648
|
const alreadySettled = entry.status !== "running" && entry.status !== "cancel_requested";
|
|
@@ -1646,7 +1662,7 @@ function SubAgentPanel(props) {
|
|
|
1646
1662
|
});
|
|
1647
1663
|
changed = true;
|
|
1648
1664
|
}
|
|
1649
|
-
if (!changed && sessionAgent) {
|
|
1665
|
+
if (!changed && sessionAgent && isCurrentChild) {
|
|
1650
1666
|
const nowTs = Date.now();
|
|
1651
1667
|
const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9-]/g, "");
|
|
1652
1668
|
const saNorm = normalize(sessionAgent);
|
|
@@ -1691,8 +1707,7 @@ function SubAgentPanel(props) {
|
|
|
1691
1707
|
return changed ? next : prev;
|
|
1692
1708
|
});
|
|
1693
1709
|
try {
|
|
1694
|
-
const
|
|
1695
|
-
const parentSid = sessionObj?.parentID;
|
|
1710
|
+
const parentSid = (isGlobalEvent ? eventSession : props.api.session.get(sid))?.parentID;
|
|
1696
1711
|
if (parentSid && parentSid !== props.sessionId) {
|
|
1697
1712
|
const parentCache = globalEntryCache.get(parentSid);
|
|
1698
1713
|
const nowTs = Date.now();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-subagent-magazine",
|
|
3
|
-
"version": "1.6.0-beta.
|
|
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.
|
|
2
|
+
export const PLUGIN_VERSION="1.6.0-beta.2";
|
|
@@ -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
|
|
@@ -363,7 +364,7 @@ export function SubAgentPanel(props: {
|
|
|
363
364
|
// V2 事件流是全局的——若 payload 带发起会话 ID,则只归账到正在查看的会话,
|
|
364
365
|
// 避免其他会话的子代理写进当前侧边栏;V1 宿主已按会话 scope,不传 sessionID。
|
|
365
366
|
const eventSid = event.payload?.sessionID !== undefined ? String(event.payload.sessionID) : undefined
|
|
366
|
-
if (
|
|
367
|
+
if (event.scope === "global" && eventSid !== props.sessionId) return
|
|
367
368
|
|
|
368
369
|
// SubtaskPart
|
|
369
370
|
if (part.type === "subtask") {
|
|
@@ -437,6 +438,13 @@ export function SubAgentPanel(props: {
|
|
|
437
438
|
const sid = String(props_?.sessionID ?? "")
|
|
438
439
|
if (!sid) return
|
|
439
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
|
+
|
|
440
448
|
const sessionTokens = props.api.usage.readSessionTokens(sid)
|
|
441
449
|
const sessionCost = props.api.usage.readSessionCost(sid)
|
|
442
450
|
const sessionModel = props.api.usage.readSessionModel(sid)
|
|
@@ -529,7 +537,7 @@ export function SubAgentPanel(props: {
|
|
|
529
537
|
let changed = false
|
|
530
538
|
const next = new Map(prev)
|
|
531
539
|
for (const [id, entry] of next) {
|
|
532
|
-
if (entry.sessionId !== sid) continue
|
|
540
|
+
if (!isCurrentChild || entry.sessionId !== sid) continue
|
|
533
541
|
if (entry.status !== "running" && entry.status !== "done" && entry.status !== "cancel_requested") continue
|
|
534
542
|
// Skip parent session idle — subagent entries belong to child sessions only
|
|
535
543
|
if (sid === props.sessionId) continue
|
|
@@ -548,7 +556,7 @@ export function SubAgentPanel(props: {
|
|
|
548
556
|
})
|
|
549
557
|
changed = true
|
|
550
558
|
}
|
|
551
|
-
if (!changed && sessionAgent) {
|
|
559
|
+
if (!changed && sessionAgent && isCurrentChild) {
|
|
552
560
|
const nowTs = Date.now()
|
|
553
561
|
const normalize = (s: string) => s.toLowerCase().replace(/[^a-z0-9-]/g, "")
|
|
554
562
|
const saNorm = normalize(sessionAgent)
|
|
@@ -594,8 +602,7 @@ export function SubAgentPanel(props: {
|
|
|
594
602
|
// 当子代理所属的父 session 与当前视图不同时,通过模块级缓存定位
|
|
595
603
|
// 并更新父 session 的 entry 状态,随后写回 KV。
|
|
596
604
|
try {
|
|
597
|
-
const
|
|
598
|
-
const parentSid = sessionObj?.parentID
|
|
605
|
+
const parentSid = (isGlobalEvent ? eventSession : props.api.session.get(sid))?.parentID
|
|
599
606
|
if (parentSid && parentSid !== props.sessionId) {
|
|
600
607
|
// 优先从模块级缓存获取父 session 的 entries,不受当前视图切换影响
|
|
601
608
|
const parentCache = globalEntryCache.get(parentSid)
|
package/src/panel/panel-api.ts
CHANGED
|
@@ -1,72 +1,75 @@
|
|
|
1
|
-
import type { KVApi } from "../core/kv"
|
|
2
|
-
import type { UsageReader, TodoStats } from "../core/usage"
|
|
3
|
-
import type { Lang, SortOrder, ScrollMode } from "../core/types"
|
|
4
|
-
|
|
5
|
-
/** Minimal session shape the panel reads from the host SDK. */
|
|
6
|
-
export interface SessionLike {
|
|
7
|
-
id?: string
|
|
8
|
-
parentID?: string
|
|
9
|
-
agent?: string
|
|
10
|
-
cost?: number
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface SessionStatusLike {
|
|
14
|
-
type: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Standardized panel events. Adapter layers (V1/V2) translate host events
|
|
19
|
-
* into these shapes so the panel component never touches host APIs directly.
|
|
20
|
-
*/
|
|
21
|
-
export type PanelEventType = "part.updated" | "message.updated" | "session.idle" | "session.error"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
*
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
1
|
+
import type { KVApi } from "../core/kv"
|
|
2
|
+
import type { UsageReader, TodoStats } from "../core/usage"
|
|
3
|
+
import type { Lang, SortOrder, ScrollMode } from "../core/types"
|
|
4
|
+
|
|
5
|
+
/** Minimal session shape the panel reads from the host SDK. */
|
|
6
|
+
export interface SessionLike {
|
|
7
|
+
id?: string
|
|
8
|
+
parentID?: string
|
|
9
|
+
agent?: string
|
|
10
|
+
cost?: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SessionStatusLike {
|
|
14
|
+
type: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Standardized panel events. Adapter layers (V1/V2) translate host events
|
|
19
|
+
* into these shapes so the panel component never touches host APIs directly.
|
|
20
|
+
*/
|
|
21
|
+
export type PanelEventType = "part.updated" | "message.updated" | "session.idle" | "session.error"
|
|
22
|
+
export type PanelEventScope = "session" | "global"
|
|
23
|
+
|
|
24
|
+
export interface PanelEvent {
|
|
25
|
+
type: PanelEventType
|
|
26
|
+
/** Global V2 events need explicit session ownership checks. */
|
|
27
|
+
scope?: PanelEventScope
|
|
28
|
+
/** part payload for part.updated; session properties for idle/error. */
|
|
29
|
+
payload?: Record<string, unknown>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ToastOptions {
|
|
33
|
+
variant?: "success" | "warning" | "error"
|
|
34
|
+
title?: string
|
|
35
|
+
duration?: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The full surface the SubAgentPanel component consumes.
|
|
40
|
+
* V1 and V2 each implement this against their host SDK; the panel is agnostic.
|
|
41
|
+
* Theme is slot-scoped in both hosts, so it is passed as a panel prop,
|
|
42
|
+
* not part of the API.
|
|
43
|
+
*/
|
|
44
|
+
export interface PanelApi {
|
|
45
|
+
kv: KVApi
|
|
46
|
+
usage: UsageReader
|
|
47
|
+
session: {
|
|
48
|
+
get(sid: string): SessionLike | undefined
|
|
49
|
+
status(sid: string): SessionStatusLike | undefined
|
|
50
|
+
/** Raw message list for a session (scan / error extraction). */
|
|
51
|
+
messages(sid: string): unknown[] | undefined
|
|
52
|
+
/** Raw parts of a message (scan). */
|
|
53
|
+
part(messageID: string): unknown[] | undefined
|
|
54
|
+
}
|
|
55
|
+
event: {
|
|
56
|
+
on(type: PanelEventType, cb: (e: PanelEvent) => void): () => void
|
|
57
|
+
}
|
|
58
|
+
client: {
|
|
59
|
+
abort(input: { sessionID: string }): Promise<void>
|
|
60
|
+
}
|
|
61
|
+
route: {
|
|
62
|
+
navigateSession(sessionID: string): void
|
|
63
|
+
}
|
|
64
|
+
ui: {
|
|
65
|
+
toast(message: string, opts?: ToastOptions): void
|
|
66
|
+
}
|
|
67
|
+
settings: {
|
|
68
|
+
lang: () => Lang
|
|
69
|
+
maxEntries: () => number
|
|
70
|
+
sortOrder: () => SortOrder
|
|
71
|
+
scrollMode: () => ScrollMode
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type { TodoStats }
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SessionLike } from "./panel-api"
|
|
2
|
+
|
|
3
|
+
/** Whether an end event belongs to a child tracked by the current panel. */
|
|
4
|
+
export function isDirectChildSession(
|
|
5
|
+
currentSessionId: string,
|
|
6
|
+
eventSessionId: string,
|
|
7
|
+
eventSession: SessionLike | undefined,
|
|
8
|
+
): boolean {
|
|
9
|
+
return eventSessionId !== currentSessionId && eventSession?.parentID === currentSessionId
|
|
10
|
+
}
|
package/src/v2/v2-panel-api.ts
CHANGED
|
@@ -256,7 +256,8 @@ export function createPanelApi(context: Context, settings: PanelApi["settings"])
|
|
|
256
256
|
// 让面板只归账到正在查看的会话(V1 宿主已按会话 scope,无需该字段)。
|
|
257
257
|
const evt = (e as Record<string, any>).data as Record<string, any> | undefined
|
|
258
258
|
const sid = evt?.sessionID !== undefined ? String(evt.sessionID) : undefined
|
|
259
|
-
|
|
259
|
+
if (!sid) return
|
|
260
|
+
cb({ type, scope: "global", payload: { part, sessionID: sid } })
|
|
260
261
|
}))
|
|
261
262
|
}
|
|
262
263
|
return () => { for (const u of unsubs) u() }
|
|
@@ -275,7 +276,7 @@ export function createPanelApi(context: Context, settings: PanelApi["settings"])
|
|
|
275
276
|
for (const evt of ["session.execution.succeeded", "session.execution.interrupted"]) {
|
|
276
277
|
unsubs.push(context.data.on(evt, (e) => {
|
|
277
278
|
const sid = String(((e as Record<string, any>).data as Record<string, any> | undefined)?.sessionID ?? "")
|
|
278
|
-
if (sid) cb({ type, payload: { sessionID: sid } })
|
|
279
|
+
if (sid) cb({ type, scope: "global", payload: { sessionID: sid } })
|
|
279
280
|
}))
|
|
280
281
|
}
|
|
281
282
|
return () => { for (const u of unsubs) u() }
|
|
@@ -284,7 +285,7 @@ export function createPanelApi(context: Context, settings: PanelApi["settings"])
|
|
|
284
285
|
return context.data.on("session.execution.failed", (e) => {
|
|
285
286
|
const evt = (e as Record<string, any>).data as Record<string, any> | undefined
|
|
286
287
|
const sid = String(evt?.sessionID ?? "")
|
|
287
|
-
if (sid) cb({ type, payload: { sessionID: sid, error: evt?.error } })
|
|
288
|
+
if (sid) cb({ type, scope: "global", payload: { sessionID: sid, error: evt?.error } })
|
|
288
289
|
})
|
|
289
290
|
default:
|
|
290
291
|
return () => {}
|