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/README.md +12 -1
- package/dist/_version.d.ts +1 -1
- package/dist/_version.js +1 -1
- package/dist/core/format.d.ts +7 -0
- package/dist/core/format.js +45 -0
- package/dist/core/kv.d.ts +22 -1
- package/dist/core/kv.js +34 -1
- package/dist/core/types.d.ts +20 -0
- package/dist/core/types.js +7 -0
- package/dist/i18n.d.ts +24 -0
- package/dist/i18n.js +96 -0
- package/dist/index.js +90 -2
- package/dist/panel/SubAgentPanel.d.ts +6 -1
- package/dist/panel/SubAgentPanel.js +645 -266
- package/dist/panel/entry-map.d.ts +17 -0
- package/dist/panel/entry-map.js +75 -1
- package/dist/panel/panel-api.d.ts +24 -0
- package/dist/tui.js +1230 -567
- package/dist/v2/commands.js +202 -44
- package/dist/v2/db.d.ts +60 -0
- package/dist/v2/db.js +341 -0
- package/dist/v2/index.js +28 -6
- package/dist/v2/settings-menu.d.ts +9 -0
- package/dist/v2/settings-menu.js +242 -0
- package/dist/v2/v2-panel-api.js +315 -44
- package/dist/v2.js +2139 -708
- package/package.json +1 -1
- package/src/_version.ts +1 -1
- package/src/core/format.ts +44 -0
- package/src/core/kv.ts +43 -2
- package/src/core/types.ts +25 -0
- package/src/i18n.ts +96 -0
- package/src/index.tsx +113 -2
- package/src/panel/SubAgentPanel.tsx +621 -238
- package/src/panel/entry-map.ts +79 -2
- package/src/panel/panel-api.ts +25 -0
- package/src/v2/commands.ts +201 -43
- package/src/v2/db.ts +346 -0
- package/src/v2/index.tsx +54 -7
- package/src/v2/settings-menu.ts +259 -0
- package/src/v2/types.ts +3 -1
- package/src/v2/v2-panel-api.ts +246 -33
package/dist/v2/v2-panel-api.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { SETTING_KEYS } from "../core/kv";
|
|
2
|
+
import { createSessionDbIndex, modelIdOf, statusOfChild } from "./db";
|
|
1
3
|
const SUBAGENT_TOOL_V2 = "subagent";
|
|
2
4
|
/** V2 tool metadata → V1 形状(V1 逻辑读 session_id/sessionId——双写保证命中)。 */
|
|
3
5
|
function normalizeMeta(meta) {
|
|
@@ -22,7 +24,12 @@ function toV1Part(p) {
|
|
|
22
24
|
.filter((t) => typeof t === "string" && t.length > 0)
|
|
23
25
|
.join("\n");
|
|
24
26
|
}
|
|
25
|
-
|
|
27
|
+
// TUI 数据层把子代理 metadata 放在 part 级,旧缓存形状可能嵌在 state 里——两处合并后再双写。
|
|
28
|
+
const meta = normalizeMeta({
|
|
29
|
+
...(st.metadata ?? {}),
|
|
30
|
+
...(p.metadata ?? {}),
|
|
31
|
+
});
|
|
32
|
+
return { type: "tool", id: p.id, tool: p.tool ?? p.name, state: st, subagent_type: p.subagent_type, metadata: meta };
|
|
26
33
|
}
|
|
27
34
|
if (p.type === "text")
|
|
28
35
|
return { type: "text", text: String(p.text ?? "") };
|
|
@@ -71,7 +78,73 @@ export function createPanelApi(context, settings) {
|
|
|
71
78
|
const [, mutate] = entry;
|
|
72
79
|
return mutate((d) => { d.value = value; });
|
|
73
80
|
};
|
|
81
|
+
// 原子 read-modify-write:宿主在存储锁内基于最新磁盘值
|
|
82
|
+
// 应用 updater,因此并发 TUI 实例会 merge,
|
|
83
|
+
// 而不是用过期快照互相覆盖。
|
|
84
|
+
const kvUpdate = (key, updater) => {
|
|
85
|
+
let entry = kvStore.get(key);
|
|
86
|
+
if (!entry) {
|
|
87
|
+
const created = context.storage.store(`subagent_magazine.${key}`, {
|
|
88
|
+
initial: { value: undefined },
|
|
89
|
+
});
|
|
90
|
+
entry = created;
|
|
91
|
+
kvStore.set(key, entry);
|
|
92
|
+
}
|
|
93
|
+
const [, mutate] = entry;
|
|
94
|
+
return mutate((d) => { d.value = updater(d.value); });
|
|
95
|
+
};
|
|
96
|
+
// 本地 opencode.db 索引:补全前台子代理缺失的 sessionId 与用量字段。
|
|
97
|
+
// 数据库不可用、库结构不符或设置项 dbSync 关闭时静默禁用(回退宿主 API)。
|
|
98
|
+
const dbIndex = createSessionDbIndex(() => kvGet(SETTING_KEYS.dbSync, true) !== false);
|
|
99
|
+
/** 事件 metadata 缺少子会话 id 时用本地库按 call id 回填(前台/后台通用)。 */
|
|
100
|
+
const withResolvedSid = (data, meta) => {
|
|
101
|
+
if (meta.sessionID !== undefined)
|
|
102
|
+
return meta;
|
|
103
|
+
try {
|
|
104
|
+
const parent = data.sessionID !== undefined ? String(data.sessionID) : "";
|
|
105
|
+
const callId = data.id !== undefined ? String(data.id) : "";
|
|
106
|
+
const sid = parent && callId ? dbIndex?.resolveCall(parent, callId) : undefined;
|
|
107
|
+
if (sid)
|
|
108
|
+
return normalizeMeta({ ...meta, sessionID: sid });
|
|
109
|
+
}
|
|
110
|
+
catch { }
|
|
111
|
+
return meta;
|
|
112
|
+
};
|
|
74
113
|
const isSubagentTool = (name) => name === SUBAGENT_TOOL_V2 || name === "task" || name === "delegate" || name === "call_omo_agent";
|
|
114
|
+
/** 事件未携带工具名/input 时,从宿主消息缓存中恢复。
|
|
115
|
+
* V2 的 called/progress/success 事件没有 `name`,且
|
|
116
|
+
* input.started 可能被错过(TUI 启动、插件重载、流抖动)——
|
|
117
|
+
* 没有这个回退,运行中的子代理要到完成时才会出现。 */
|
|
118
|
+
const findToolPart = (data) => {
|
|
119
|
+
const sid = data.sessionID;
|
|
120
|
+
const msgID = data.assistantMessageID;
|
|
121
|
+
const partID = data.id;
|
|
122
|
+
if (sid === undefined || msgID === undefined || partID === undefined)
|
|
123
|
+
return undefined;
|
|
124
|
+
try {
|
|
125
|
+
const raw = context.data.session.message.list(String(sid));
|
|
126
|
+
if (!raw)
|
|
127
|
+
return undefined;
|
|
128
|
+
for (let i = raw.length - 1; i >= 0; i--) {
|
|
129
|
+
const m = raw[i];
|
|
130
|
+
if (!m || String(m.id) !== String(msgID))
|
|
131
|
+
continue;
|
|
132
|
+
const content = Array.isArray(m.content) ? m.content : [];
|
|
133
|
+
for (const p of content) {
|
|
134
|
+
const pp = p;
|
|
135
|
+
if (pp && pp.type === "tool" && String(pp.id) === String(partID)) {
|
|
136
|
+
const st = (pp.state ?? {});
|
|
137
|
+
const input = (st.input && typeof st.input === "object") ? st.input : undefined;
|
|
138
|
+
const name = pp.name ?? pp.tool;
|
|
139
|
+
return { name: name !== undefined ? String(name) : undefined, input };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch { }
|
|
146
|
+
return undefined;
|
|
147
|
+
};
|
|
75
148
|
/** V2 tool 事件 → V1 ToolPart(非 subagent 工具返回 undefined——面板只关心子代理)。
|
|
76
149
|
* 识别规则(对齐 V2 官方 stream-v2.subagent.ts):
|
|
77
150
|
* - input.started/called:工具名在 subagent 集合(subagent/task/delegate/call_omo_agent)
|
|
@@ -100,47 +173,83 @@ export function createPanelApi(context, settings) {
|
|
|
100
173
|
return { type: "tool", tool: normName(String(data.name)), id: String(data.id), state: { status: "pending", input: {} } };
|
|
101
174
|
}
|
|
102
175
|
if (type === "session.tool.called") {
|
|
103
|
-
|
|
176
|
+
let name = data.name ?? info?.name;
|
|
177
|
+
let input = data.input ?? info?.input;
|
|
178
|
+
if (!name) {
|
|
179
|
+
const resolved = findToolPart(data);
|
|
180
|
+
if (resolved?.name) {
|
|
181
|
+
name = resolved.name;
|
|
182
|
+
if (input === undefined)
|
|
183
|
+
input = resolved.input;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
104
186
|
if (name && !isSubagentTool(name))
|
|
105
187
|
return undefined;
|
|
106
188
|
if (!name)
|
|
107
189
|
return undefined;
|
|
108
190
|
if (key)
|
|
109
|
-
toolInfo.set(key, { name: String(name), input:
|
|
191
|
+
toolInfo.set(key, { name: String(name), input: input ?? {} });
|
|
110
192
|
return {
|
|
111
|
-
type: "tool", tool: normName(String(name)), id: String(data.id), subagent_type: agentOf(
|
|
112
|
-
state: { status: "running", input:
|
|
193
|
+
type: "tool", tool: normName(String(name)), id: String(data.id), subagent_type: agentOf(input),
|
|
194
|
+
state: { status: "running", input: input ?? {}, metadata: withResolvedSid(data, {}) },
|
|
113
195
|
};
|
|
114
196
|
}
|
|
115
197
|
if (type === "session.tool.progress") {
|
|
116
|
-
|
|
198
|
+
let name = data.name ?? info?.name;
|
|
199
|
+
if (!name)
|
|
200
|
+
name = findToolPart(data)?.name;
|
|
117
201
|
if (name && !isSubagentTool(name))
|
|
118
202
|
return undefined;
|
|
119
203
|
if (!name)
|
|
120
204
|
return undefined;
|
|
121
205
|
return {
|
|
122
206
|
type: "tool", tool: normName(String(name)), id: String(data.id), subagent_type: agentOf(info?.input),
|
|
123
|
-
state: { status: "running", input: info?.input ?? {}, metadata: normalizeMeta(data.metadata) },
|
|
207
|
+
state: { status: "running", input: info?.input ?? {}, metadata: withResolvedSid(data, normalizeMeta(data.metadata)) },
|
|
124
208
|
};
|
|
125
209
|
}
|
|
126
210
|
if (type === "session.tool.success" || type === "session.tool.failed") {
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
return undefined;
|
|
211
|
+
const eventMeta = normalizeMeta(data.metadata);
|
|
212
|
+
const meta = withResolvedSid(data, eventMeta);
|
|
130
213
|
// 取消导致的工具失败:metadata.status 是工具被打断时的状态("running")——不是真错误。
|
|
131
214
|
// 忽略——最终状态由 execution.interrupted → settleOnIdle 裁定 cancelled
|
|
132
215
|
// (否则 handlePartUpdated 的 error 分支会把 cancel_requested 覆盖成 error)
|
|
133
216
|
if (type === "session.tool.failed" && meta.status === "running")
|
|
134
217
|
return undefined;
|
|
135
|
-
|
|
218
|
+
let name = data.name ?? info?.name;
|
|
219
|
+
let input = info?.input ?? data.input;
|
|
220
|
+
if (!name || input === undefined) {
|
|
221
|
+
const resolved = findToolPart(data);
|
|
222
|
+
if (!name && resolved?.name)
|
|
223
|
+
name = resolved.name;
|
|
224
|
+
if (input === undefined && resolved?.input)
|
|
225
|
+
input = resolved.input;
|
|
226
|
+
}
|
|
136
227
|
if (name && !isSubagentTool(name))
|
|
137
228
|
return undefined;
|
|
138
|
-
|
|
229
|
+
if (key && name)
|
|
230
|
+
toolInfo.set(key, { name: String(name), input: input ?? info?.input ?? {} });
|
|
231
|
+
const finalInput = input ?? {};
|
|
232
|
+
// 后台任务缺少子会话 metadata 时无法判定终态,交给扫描/轮询兜底;
|
|
233
|
+
// 前台任务没有 sessionID 也必须回传 completed,否则条目会一直停在 running。
|
|
234
|
+
if (meta.sessionID === undefined && (finalInput.background === true || finalInput.run_in_background === true)) {
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
// sid 来自本地库(事件没带)时以库里的 time_idle 为准:库里会话仍在运行
|
|
238
|
+
// 就不提前收尾,交给周期性 reconcile 按真实状态落定(无时间阈值猜测)。
|
|
239
|
+
let status = type === "session.tool.failed" ? "error" : "completed";
|
|
240
|
+
if (meta.sessionID !== undefined && meta.sessionID !== eventMeta.sessionID) {
|
|
241
|
+
try {
|
|
242
|
+
const childInfo = dbIndex?.info(String(meta.sessionID));
|
|
243
|
+
if (childInfo && statusOfChild(childInfo) === undefined)
|
|
244
|
+
status = "running";
|
|
245
|
+
}
|
|
246
|
+
catch { }
|
|
247
|
+
}
|
|
139
248
|
return {
|
|
140
|
-
type: "tool", tool: normName(String(name ?? "task")), id: String(data.id), subagent_type: agentOf(
|
|
249
|
+
type: "tool", tool: normName(String(name ?? "task")), id: String(data.id), subagent_type: agentOf(finalInput),
|
|
141
250
|
state: {
|
|
142
|
-
status
|
|
143
|
-
input,
|
|
251
|
+
status,
|
|
252
|
+
input: finalInput,
|
|
144
253
|
metadata: meta,
|
|
145
254
|
},
|
|
146
255
|
};
|
|
@@ -163,15 +272,66 @@ export function createPanelApi(context, settings) {
|
|
|
163
272
|
}
|
|
164
273
|
return out;
|
|
165
274
|
};
|
|
275
|
+
// ── usage enrichment(用量补全)──
|
|
276
|
+
// TUI 数据层只为已加载的会话缓存 messages。从 KV 恢复的
|
|
277
|
+
// 子会话通常没有缓存,因此读取依次回退到
|
|
278
|
+
// 会话级聚合值、最后是(限流的)message sync——
|
|
279
|
+
// 结果写入本地缓存,供面板的周期性 enrichment 读取。
|
|
280
|
+
const usageCache = new Map();
|
|
281
|
+
const syncRequestedAt = new Map();
|
|
282
|
+
const USAGE_CACHE_TTL_MS = 5000;
|
|
283
|
+
const SYNC_THROTTLE_MS = 30000;
|
|
284
|
+
const rememberUsage = (sid, patch) => {
|
|
285
|
+
const prev = usageCache.get(sid);
|
|
286
|
+
usageCache.set(sid, { ...prev, ...patch, at: Date.now() });
|
|
287
|
+
};
|
|
288
|
+
const cachedUsage = (sid) => {
|
|
289
|
+
const hit = usageCache.get(sid);
|
|
290
|
+
if (!hit)
|
|
291
|
+
return undefined;
|
|
292
|
+
if (Date.now() - hit.at > USAGE_CACHE_TTL_MS)
|
|
293
|
+
return undefined;
|
|
294
|
+
return hit;
|
|
295
|
+
};
|
|
296
|
+
const requestMessageSync = (sid) => {
|
|
297
|
+
const last = syncRequestedAt.get(sid) ?? 0;
|
|
298
|
+
if (Date.now() - last < SYNC_THROTTLE_MS)
|
|
299
|
+
return;
|
|
300
|
+
syncRequestedAt.set(sid, Date.now());
|
|
301
|
+
try {
|
|
302
|
+
const sync = context.data.session.message.sync;
|
|
303
|
+
if (typeof sync === "function")
|
|
304
|
+
void sync.call(context.data.session.message, sid).catch(() => { });
|
|
305
|
+
}
|
|
306
|
+
catch { }
|
|
307
|
+
};
|
|
308
|
+
const listedSession = (sid) => {
|
|
309
|
+
try {
|
|
310
|
+
return context.data.session.list()?.find((s) => s.id === sid);
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
return undefined;
|
|
314
|
+
}
|
|
315
|
+
};
|
|
166
316
|
return {
|
|
167
317
|
kv: {
|
|
168
318
|
get: kvGet,
|
|
169
319
|
set: kvSet,
|
|
320
|
+
update: kvUpdate,
|
|
170
321
|
},
|
|
171
322
|
usage: {
|
|
172
323
|
readSessionTokens: (sid) => {
|
|
173
324
|
if (!sid)
|
|
174
325
|
return undefined;
|
|
326
|
+
// 本地库优先:宿主数据层缺少该子会话缓存时也能给出用量。
|
|
327
|
+
try {
|
|
328
|
+
const dbTokens = dbIndex?.info(sid)?.tokens;
|
|
329
|
+
if (dbTokens !== undefined) {
|
|
330
|
+
rememberUsage(sid, { tokens: dbTokens });
|
|
331
|
+
return dbTokens;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
catch { }
|
|
175
335
|
try {
|
|
176
336
|
const msgs = context.data.session.message.list(sid);
|
|
177
337
|
if (msgs) {
|
|
@@ -187,10 +347,18 @@ export function createPanelApi(context, settings) {
|
|
|
187
347
|
(Number(t.reasoning) || 0) +
|
|
188
348
|
(Number(t.cache?.read) || 0) +
|
|
189
349
|
(Number(t.cache?.write) || 0);
|
|
190
|
-
if (ctx > 0)
|
|
350
|
+
if (ctx > 0) {
|
|
351
|
+
rememberUsage(sid, { tokens: ctx });
|
|
191
352
|
return ctx;
|
|
353
|
+
}
|
|
192
354
|
}
|
|
193
355
|
}
|
|
356
|
+
// 本地无消息(子会话尚未加载):使用最近同步的值,
|
|
357
|
+
// 否则请数据层同步,稍后重试。
|
|
358
|
+
const cached = cachedUsage(sid)?.tokens;
|
|
359
|
+
if (cached !== undefined)
|
|
360
|
+
return cached;
|
|
361
|
+
requestMessageSync(sid);
|
|
194
362
|
return undefined;
|
|
195
363
|
}
|
|
196
364
|
catch {
|
|
@@ -200,19 +368,56 @@ export function createPanelApi(context, settings) {
|
|
|
200
368
|
readSessionCost: (sid) => {
|
|
201
369
|
if (!sid)
|
|
202
370
|
return undefined;
|
|
371
|
+
try {
|
|
372
|
+
const dbCost = dbIndex?.info(sid)?.cost;
|
|
373
|
+
if (dbCost !== undefined) {
|
|
374
|
+
rememberUsage(sid, { cost: dbCost });
|
|
375
|
+
return dbCost;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
catch { }
|
|
379
|
+
try {
|
|
380
|
+
const direct = context.data.session.cost(sid);
|
|
381
|
+
if (typeof direct === "number" && direct > 0) {
|
|
382
|
+
rememberUsage(sid, { cost: direct });
|
|
383
|
+
return direct;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
catch { }
|
|
203
387
|
try {
|
|
204
388
|
const session = context.data.session.get(sid);
|
|
205
|
-
if (session?.cost != null && session.cost > 0)
|
|
389
|
+
if (session?.cost != null && session.cost > 0) {
|
|
390
|
+
rememberUsage(sid, { cost: session.cost });
|
|
206
391
|
return session.cost;
|
|
392
|
+
}
|
|
393
|
+
const listed = listedSession(sid);
|
|
394
|
+
if (listed?.cost != null && listed.cost > 0) {
|
|
395
|
+
rememberUsage(sid, { cost: listed.cost });
|
|
396
|
+
return listed.cost;
|
|
397
|
+
}
|
|
207
398
|
const msgs = context.data.session.message.list(sid);
|
|
208
|
-
if (
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
399
|
+
if (Array.isArray(msgs) && msgs.length > 0) {
|
|
400
|
+
let total = 0;
|
|
401
|
+
let sawAssistant = false;
|
|
402
|
+
for (const m of msgs) {
|
|
403
|
+
if (m.type !== "assistant")
|
|
404
|
+
continue;
|
|
405
|
+
sawAssistant = true;
|
|
406
|
+
if (typeof m.cost === "number")
|
|
407
|
+
total += m.cost;
|
|
408
|
+
}
|
|
409
|
+
// 已加载的历史是权威来源:assistant 消息 cost 为 0 表示
|
|
410
|
+
// 该会话确实免费(如 budget/test 模型),而非未知。
|
|
411
|
+
if (sawAssistant) {
|
|
412
|
+
rememberUsage(sid, { cost: total });
|
|
413
|
+
return total;
|
|
414
|
+
}
|
|
214
415
|
}
|
|
215
|
-
|
|
416
|
+
const cached = cachedUsage(sid)?.cost;
|
|
417
|
+
if (cached !== undefined)
|
|
418
|
+
return cached;
|
|
419
|
+
requestMessageSync(sid);
|
|
420
|
+
return undefined;
|
|
216
421
|
}
|
|
217
422
|
catch {
|
|
218
423
|
return undefined;
|
|
@@ -221,6 +426,14 @@ export function createPanelApi(context, settings) {
|
|
|
221
426
|
readSessionModel: (sid) => {
|
|
222
427
|
if (!sid)
|
|
223
428
|
return undefined;
|
|
429
|
+
try {
|
|
430
|
+
const dbModel = dbIndex?.info(sid)?.model;
|
|
431
|
+
if (dbModel !== undefined) {
|
|
432
|
+
rememberUsage(sid, { model: dbModel });
|
|
433
|
+
return dbModel;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
catch { }
|
|
224
437
|
try {
|
|
225
438
|
const msgs = context.data.session.message.list(sid);
|
|
226
439
|
if (msgs) {
|
|
@@ -228,13 +441,27 @@ export function createPanelApi(context, settings) {
|
|
|
228
441
|
const m = msgs[i];
|
|
229
442
|
if (m.type !== "assistant")
|
|
230
443
|
continue;
|
|
231
|
-
const
|
|
232
|
-
if (
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
444
|
+
const id = modelIdOf(m.model);
|
|
445
|
+
if (id) {
|
|
446
|
+
rememberUsage(sid, { model: id });
|
|
447
|
+
return id;
|
|
448
|
+
}
|
|
236
449
|
}
|
|
237
450
|
}
|
|
451
|
+
const sessionModel = modelIdOf(context.data.session.get(sid)?.model);
|
|
452
|
+
if (sessionModel) {
|
|
453
|
+
rememberUsage(sid, { model: sessionModel });
|
|
454
|
+
return sessionModel;
|
|
455
|
+
}
|
|
456
|
+
const listedModel = modelIdOf(listedSession(sid)?.model);
|
|
457
|
+
if (listedModel) {
|
|
458
|
+
rememberUsage(sid, { model: listedModel });
|
|
459
|
+
return listedModel;
|
|
460
|
+
}
|
|
461
|
+
const cached = cachedUsage(sid)?.model;
|
|
462
|
+
if (cached !== undefined)
|
|
463
|
+
return cached;
|
|
464
|
+
requestMessageSync(sid);
|
|
238
465
|
return undefined;
|
|
239
466
|
}
|
|
240
467
|
catch {
|
|
@@ -244,24 +471,48 @@ export function createPanelApi(context, settings) {
|
|
|
244
471
|
readSessionTodo: () => undefined, // V2 无 todo 数据源
|
|
245
472
|
},
|
|
246
473
|
session: {
|
|
247
|
-
get: (sid) => {
|
|
248
|
-
return context.data.session.get(sid);
|
|
249
|
-
}
|
|
250
|
-
catch {
|
|
251
|
-
return undefined;
|
|
252
|
-
} },
|
|
253
|
-
status: (sid) => {
|
|
474
|
+
get: (sid) => {
|
|
254
475
|
try {
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
476
|
+
const direct = context.data.session.get(sid);
|
|
477
|
+
if (direct)
|
|
478
|
+
return direct;
|
|
479
|
+
// 较旧的子会话可能未注入本地缓存;回退到
|
|
480
|
+
// 会话列表,保证基于 parentID 的路由继续工作。
|
|
481
|
+
const list = context.data.session.list?.();
|
|
482
|
+
if (Array.isArray(list)) {
|
|
483
|
+
const found = list.find((s) => s?.id === sid);
|
|
484
|
+
if (found)
|
|
485
|
+
return found;
|
|
486
|
+
}
|
|
487
|
+
return undefined;
|
|
260
488
|
}
|
|
261
489
|
catch {
|
|
262
490
|
return undefined;
|
|
263
491
|
}
|
|
264
492
|
},
|
|
493
|
+
status: (sid) => {
|
|
494
|
+
try {
|
|
495
|
+
const st = context.data.session.status(sid);
|
|
496
|
+
if (st) {
|
|
497
|
+
// V2 的 "running" 等价 V1 的 "busy"(cancelEntry 检查 `st.type !== "busy"`——
|
|
498
|
+
// 不映射会跳过取消流程直接标记 done)
|
|
499
|
+
const type = st === "running" ? "busy" : st;
|
|
500
|
+
return { type };
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
catch { }
|
|
504
|
+
// 宿主没有该子会话的实时状态(未加载缓存)时用本地库终态兜底:
|
|
505
|
+
// time_idle 存在且没有恢复活动即已结束;仍在运行则回报 busy。
|
|
506
|
+
try {
|
|
507
|
+
const info = dbIndex?.info(sid);
|
|
508
|
+
if (info?.active === true)
|
|
509
|
+
return { type: "busy" };
|
|
510
|
+
if (statusOfChild(info) !== undefined)
|
|
511
|
+
return { type: "idle" };
|
|
512
|
+
}
|
|
513
|
+
catch { }
|
|
514
|
+
return undefined;
|
|
515
|
+
},
|
|
265
516
|
messages: (sid) => { try {
|
|
266
517
|
return normalizeMessages(sid);
|
|
267
518
|
}
|
|
@@ -280,6 +531,25 @@ export function createPanelApi(context, settings) {
|
|
|
280
531
|
}
|
|
281
532
|
return msg.content.map((p) => toV1Part(p));
|
|
282
533
|
},
|
|
534
|
+
resolveChild: (input) => {
|
|
535
|
+
try {
|
|
536
|
+
const sid = input.callId ? dbIndex?.resolveCall(input.parentId, input.callId) : undefined;
|
|
537
|
+
if (sid)
|
|
538
|
+
return sid;
|
|
539
|
+
return dbIndex?.matchChild(input.parentId, input.agent, input.startedAt);
|
|
540
|
+
}
|
|
541
|
+
catch {
|
|
542
|
+
return undefined;
|
|
543
|
+
}
|
|
544
|
+
},
|
|
545
|
+
listChildren: (parentId) => {
|
|
546
|
+
try {
|
|
547
|
+
return dbIndex?.enabled() ? dbIndex.children(parentId) : undefined;
|
|
548
|
+
}
|
|
549
|
+
catch {
|
|
550
|
+
return undefined;
|
|
551
|
+
}
|
|
552
|
+
},
|
|
283
553
|
},
|
|
284
554
|
event: {
|
|
285
555
|
on: (type, cb) => {
|
|
@@ -289,12 +559,13 @@ export function createPanelApi(context, settings) {
|
|
|
289
559
|
for (const evt of ["session.tool.input.started", "session.tool.called", "session.tool.progress", "session.tool.success", "session.tool.failed"]) {
|
|
290
560
|
unsubs.push(context.data.on(evt, (e) => {
|
|
291
561
|
const part = toolEventToPart(e);
|
|
292
|
-
|
|
562
|
+
const evtData = e.data;
|
|
563
|
+
if (!part) {
|
|
293
564
|
return;
|
|
565
|
+
}
|
|
294
566
|
// V2 事件流是全局的(跨所有会话);把发起会话 ID 一并传给面板,
|
|
295
567
|
// 让面板只归账到正在查看的会话(V1 宿主已按会话 scope,无需该字段)。
|
|
296
|
-
const
|
|
297
|
-
const sid = evt?.sessionID !== undefined ? String(evt.sessionID) : undefined;
|
|
568
|
+
const sid = evtData?.sessionID !== undefined ? String(evtData.sessionID) : undefined;
|
|
298
569
|
if (!sid)
|
|
299
570
|
return;
|
|
300
571
|
cb({ type, scope: "global", payload: { part, sessionID: sid } });
|