opencode-subagent-magazine 1.1.0 → 1.1.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/LICENSE +21 -21
- package/dist/_version.d.ts +1 -1
- package/dist/_version.js +1 -1
- package/dist/index.js +125 -1
- package/package.json +5 -1
- package/src/_version.ts +1 -1
- package/src/index.tsx +1508 -1379
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Hotakus
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hotakus
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/_version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const PLUGIN_VERSION = "1.1.
|
|
1
|
+
export declare const PLUGIN_VERSION = "1.1.1";
|
package/dist/_version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// auto-generated
|
|
2
|
-
export const PLUGIN_VERSION = "1.1.
|
|
2
|
+
export const PLUGIN_VERSION = "1.1.1";
|
package/dist/index.js
CHANGED
|
@@ -188,6 +188,9 @@ function safeErrorMsg(err) {
|
|
|
188
188
|
// ===================================================================
|
|
189
189
|
// Sidebar component
|
|
190
190
|
// ===================================================================
|
|
191
|
+
// 模块级缓存:保留各 session 的最新 entryMap,不随组件内 session 切换而丢失。
|
|
192
|
+
// 当用户在子 session 内部时,handleSessionEnd 可通过此缓存找到父 session 的 entries。
|
|
193
|
+
const globalEntryCache = new Map();
|
|
191
194
|
function SubAgentPanel(props) {
|
|
192
195
|
const t = (key) => I18N[props.lang()][key] ?? key;
|
|
193
196
|
// ── session data (single-key, true deletion on cleanup) ──
|
|
@@ -270,7 +273,30 @@ function SubAgentPanel(props) {
|
|
|
270
273
|
const setEntryMap = (arg) => {
|
|
271
274
|
setEntryMapRaw((prev) => {
|
|
272
275
|
const next = typeof arg === "function" ? arg(prev) : arg;
|
|
273
|
-
|
|
276
|
+
// 检测是否有 entry 从 running 变为 done/error → 立即写 KV
|
|
277
|
+
// 避免 session 切换时 KV 仍是旧状态,导致 scan 用 Date.now() 覆盖正确的 endedAt
|
|
278
|
+
let needsImmediateFlush = false;
|
|
279
|
+
for (const [id, entry] of next) {
|
|
280
|
+
const prevEntry = prev.get(id);
|
|
281
|
+
if (prevEntry?.status === "running" && (entry.status === "done" || entry.status === "error")) {
|
|
282
|
+
needsImmediateFlush = true;
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (needsImmediateFlush) {
|
|
287
|
+
clearTimeout(persistTimer);
|
|
288
|
+
try {
|
|
289
|
+
const data = loadSessionData();
|
|
290
|
+
data[props.sessionId] = { ...data[props.sessionId], ts: Date.now(), entries: [...next.values()] };
|
|
291
|
+
saveSessionData(data);
|
|
292
|
+
}
|
|
293
|
+
catch { }
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
persistEntries(props.sessionId, next);
|
|
297
|
+
}
|
|
298
|
+
// 同步到全局缓存,确保跨 session 查找时数据可用
|
|
299
|
+
globalEntryCache.set(props.sessionId, new Map(next));
|
|
274
300
|
return next;
|
|
275
301
|
});
|
|
276
302
|
};
|
|
@@ -511,6 +537,66 @@ function SubAgentPanel(props) {
|
|
|
511
537
|
}
|
|
512
538
|
}
|
|
513
539
|
catch { }
|
|
540
|
+
// 在给定的 entries Map 中查找并更新匹配的子代理 entry。
|
|
541
|
+
// 返回 true 表示找到并更新了,false 表示未找到。
|
|
542
|
+
const tryMatchAndUpdate = (entriesMap, targetSid, targetStatus, nowTs) => {
|
|
543
|
+
// 精确匹配:sessionId 对得上 + 状态为 running
|
|
544
|
+
for (const [, entry] of entriesMap) {
|
|
545
|
+
if (entry.sessionId === targetSid && entry.status === "running") {
|
|
546
|
+
entry.status = targetStatus;
|
|
547
|
+
entry.endedAt = nowTs;
|
|
548
|
+
entry.tokens = entry.tokens ?? sessionTokens;
|
|
549
|
+
entry.cost = entry.cost ?? sessionCost;
|
|
550
|
+
entry.model = entry.model ?? sessionModel;
|
|
551
|
+
entry.todoTotal = entry.todoTotal ?? sessionTodo?.total;
|
|
552
|
+
entry.todoDone = entry.todoDone ?? sessionTodo?.done;
|
|
553
|
+
entry.error = errorMsg || entry.error;
|
|
554
|
+
return true;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
// 回退:sessionId 未关联但 agent 名匹配 + 状态为 running
|
|
558
|
+
if (sessionAgent) {
|
|
559
|
+
const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9-]/g, "");
|
|
560
|
+
const saNorm = normalize(sessionAgent);
|
|
561
|
+
let best = null;
|
|
562
|
+
for (const [, entry] of entriesMap) {
|
|
563
|
+
if (entry.status !== "running")
|
|
564
|
+
continue;
|
|
565
|
+
const eaNorm = normalize(entry.agent);
|
|
566
|
+
if (!eaNorm || !saNorm)
|
|
567
|
+
continue;
|
|
568
|
+
if (!eaNorm.includes(saNorm) && !saNorm.includes(eaNorm))
|
|
569
|
+
continue;
|
|
570
|
+
const gap = nowTs - (entry.startedAt || 0);
|
|
571
|
+
if (!best || gap > best.gap)
|
|
572
|
+
best = { entry, gap };
|
|
573
|
+
}
|
|
574
|
+
if (!best) {
|
|
575
|
+
for (const [, entry] of entriesMap) {
|
|
576
|
+
if (entry.status !== "running")
|
|
577
|
+
continue;
|
|
578
|
+
if (entry.sessionId)
|
|
579
|
+
continue;
|
|
580
|
+
const gap = nowTs - (entry.startedAt || 0);
|
|
581
|
+
if (!best || gap > best.gap)
|
|
582
|
+
best = { entry, gap };
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
if (best) {
|
|
586
|
+
best.entry.status = targetStatus;
|
|
587
|
+
best.entry.endedAt = nowTs;
|
|
588
|
+
best.entry.tokens = best.entry.tokens ?? sessionTokens;
|
|
589
|
+
best.entry.cost = best.entry.cost ?? sessionCost;
|
|
590
|
+
best.entry.model = best.entry.model ?? sessionModel;
|
|
591
|
+
best.entry.todoTotal = best.entry.todoTotal ?? sessionTodo?.total;
|
|
592
|
+
best.entry.todoDone = best.entry.todoDone ?? sessionTodo?.done;
|
|
593
|
+
best.entry.sessionId = targetSid;
|
|
594
|
+
best.entry.error = errorMsg || best.entry.error;
|
|
595
|
+
return true;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return false;
|
|
599
|
+
};
|
|
514
600
|
setEntryMap((prev) => {
|
|
515
601
|
let changed = false;
|
|
516
602
|
const next = new Map(prev);
|
|
@@ -581,6 +667,44 @@ function SubAgentPanel(props) {
|
|
|
581
667
|
}
|
|
582
668
|
return changed ? next : prev;
|
|
583
669
|
});
|
|
670
|
+
// 跨 session 完成事件:用户正在查看子 session 内部时,其他子代理 done。
|
|
671
|
+
// 上面的 setEntryMap 在子 session 的 entries 中找不到父 session 的 entry。
|
|
672
|
+
// 通过全局缓存(切换前保留的父 session entries)查找并更新,再同步回 KV。
|
|
673
|
+
try {
|
|
674
|
+
const sessionObj = props.api.state.session.get(sid);
|
|
675
|
+
const parentSid = sessionObj?.parentID;
|
|
676
|
+
if (parentSid && parentSid !== props.sessionId) {
|
|
677
|
+
// 优先全局缓存——切换 session 时不会被替换,始终保留父 session 的最新 entries
|
|
678
|
+
const parentCache = globalEntryCache.get(parentSid);
|
|
679
|
+
const nowTs = Date.now();
|
|
680
|
+
let found = false;
|
|
681
|
+
if (parentCache) {
|
|
682
|
+
found = tryMatchAndUpdate(parentCache, sid, status, nowTs);
|
|
683
|
+
}
|
|
684
|
+
// 缓存未命中时回退到 KV(极端情况:session 切换前缓存未建立)
|
|
685
|
+
if (!found) {
|
|
686
|
+
const data = loadSessionData();
|
|
687
|
+
const rec = data[parentSid];
|
|
688
|
+
if (rec?.entries) {
|
|
689
|
+
const fallbackMap = new Map(rec.entries.map((e) => [e.id, e]));
|
|
690
|
+
found = tryMatchAndUpdate(fallbackMap, sid, status, nowTs);
|
|
691
|
+
if (found) {
|
|
692
|
+
// 回退命中了也要写回 KV,并回填缓存
|
|
693
|
+
data[parentSid] = { ...rec, ts: nowTs, entries: [...fallbackMap.values()] };
|
|
694
|
+
saveSessionData(data);
|
|
695
|
+
globalEntryCache.set(parentSid, fallbackMap);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
// KV 同步:以全局缓存(最新的内存状态)为准写回 KV
|
|
700
|
+
if (found && parentCache) {
|
|
701
|
+
const data = loadSessionData();
|
|
702
|
+
data[parentSid] = { ...data[parentSid], ts: nowTs, entries: [...parentCache.values()] };
|
|
703
|
+
saveSessionData(data);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
catch { }
|
|
584
708
|
// Delayed backfill: re-read data after state sync catches up, to capture the final
|
|
585
709
|
// token/cost values that may not have been available when session.idle fired.
|
|
586
710
|
setTimeout(() => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-subagent-magazine",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "OpenCode TUI plugin monitoring sub-agent invocation status in real time",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -39,6 +39,10 @@
|
|
|
39
39
|
"subagent",
|
|
40
40
|
"subtask"
|
|
41
41
|
],
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "git+https://github.com/Hotakus/opencode-subagent-magazine.git"
|
|
45
|
+
},
|
|
42
46
|
"license": "MIT",
|
|
43
47
|
"peerDependencies": {
|
|
44
48
|
"@opencode-ai/plugin": ">=1.14.0",
|
package/src/_version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// auto-generated
|
|
2
|
-
export const PLUGIN_VERSION="1.1.
|
|
2
|
+
export const PLUGIN_VERSION="1.1.1";
|