@agentuity/opencode 1.0.12 → 1.0.13
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/agents/lead.d.ts +1 -1
- package/dist/agents/lead.d.ts.map +1 -1
- package/dist/agents/lead.js +9 -0
- package/dist/agents/lead.js.map +1 -1
- package/dist/agents/monitor.d.ts +1 -1
- package/dist/agents/monitor.d.ts.map +1 -1
- package/dist/agents/monitor.js +13 -0
- package/dist/agents/monitor.js.map +1 -1
- package/dist/background/manager.d.ts +4 -1
- package/dist/background/manager.d.ts.map +1 -1
- package/dist/background/manager.js +161 -3
- package/dist/background/manager.js.map +1 -1
- package/dist/background/types.d.ts +21 -0
- package/dist/background/types.d.ts.map +1 -1
- package/dist/plugin/hooks/cadence.d.ts +2 -1
- package/dist/plugin/hooks/cadence.d.ts.map +1 -1
- package/dist/plugin/hooks/cadence.js +57 -1
- package/dist/plugin/hooks/cadence.js.map +1 -1
- package/dist/plugin/plugin.d.ts.map +1 -1
- package/dist/plugin/plugin.js +196 -7
- package/dist/plugin/plugin.js.map +1 -1
- package/dist/sqlite/index.d.ts +3 -0
- package/dist/sqlite/index.d.ts.map +1 -0
- package/dist/sqlite/index.js +2 -0
- package/dist/sqlite/index.js.map +1 -0
- package/dist/sqlite/queries.d.ts +18 -0
- package/dist/sqlite/queries.d.ts.map +1 -0
- package/dist/sqlite/queries.js +41 -0
- package/dist/sqlite/queries.js.map +1 -0
- package/dist/sqlite/reader.d.ts +44 -0
- package/dist/sqlite/reader.d.ts.map +1 -0
- package/dist/sqlite/reader.js +526 -0
- package/dist/sqlite/reader.js.map +1 -0
- package/dist/sqlite/types.d.ts +110 -0
- package/dist/sqlite/types.d.ts.map +1 -0
- package/dist/sqlite/types.js +2 -0
- package/dist/sqlite/types.js.map +1 -0
- package/package.json +3 -3
- package/src/agents/lead.ts +9 -0
- package/src/agents/monitor.ts +13 -0
- package/src/background/manager.ts +174 -3
- package/src/background/types.ts +10 -0
- package/src/plugin/hooks/cadence.ts +72 -1
- package/src/plugin/plugin.ts +271 -23
- package/src/sqlite/index.ts +16 -0
- package/src/sqlite/queries.ts +50 -0
- package/src/sqlite/reader.ts +677 -0
- package/src/sqlite/types.ts +121 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentuity/opencode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"author": "Agentuity employees and contributors",
|
|
6
6
|
"description": "Agentuity Open Code plugin with specialized AI coding agents",
|
|
@@ -40,13 +40,13 @@
|
|
|
40
40
|
"prepublishOnly": "bun run clean && bun run build"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@agentuity/core": "1.0.
|
|
43
|
+
"@agentuity/core": "1.0.13",
|
|
44
44
|
"@opencode-ai/plugin": "^1.1.36",
|
|
45
45
|
"yaml": "^2.8.1",
|
|
46
46
|
"zod": "^4.3.5"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@agentuity/test-utils": "1.0.
|
|
49
|
+
"@agentuity/test-utils": "1.0.13",
|
|
50
50
|
"@types/bun": "latest",
|
|
51
51
|
"bun-types": "latest",
|
|
52
52
|
"typescript": "^5.9.0"
|
package/src/agents/lead.ts
CHANGED
|
@@ -504,6 +504,14 @@ agentuity_background_output({ task_id: "bg_xxx" })
|
|
|
504
504
|
agentuity_background_cancel({ task_id: "bg_xxx" })
|
|
505
505
|
\`\`\`
|
|
506
506
|
|
|
507
|
+
**Session Dashboard (Lead-of-Leads Monitoring):**
|
|
508
|
+
\`\`\`
|
|
509
|
+
agentuity_session_dashboard({ session_id: "ses_xxx" })
|
|
510
|
+
// Returns: hierarchy of child sessions with status, costs, active tools, and health summary
|
|
511
|
+
\`\`\`
|
|
512
|
+
|
|
513
|
+
Use \`agentuity_session_dashboard\` when orchestrating Lead-of-Leads to get a full view of all child sessions, their status, costs, and what they're currently doing — without needing to inspect each task individually.
|
|
514
|
+
|
|
507
515
|
**Example - Parallel Security Review:**
|
|
508
516
|
When asked to review multiple packages for security:
|
|
509
517
|
1. Launch \`agentuity_background_task\` for each package with Scout
|
|
@@ -1315,6 +1323,7 @@ You (Parent Lead):
|
|
|
1315
1323
|
- **No direct child-to-child communication** — Coordinate through PRD
|
|
1316
1324
|
- **Parent handles integration** — After children complete, parent does any glue work
|
|
1317
1325
|
- **Monitor watches tasks** — Use BackgroundMonitor to avoid polling loop exhausting context
|
|
1326
|
+
- **Session dashboard** — Use \`agentuity_session_dashboard\` to get a unified view of all child session states, costs, and health without inspecting each task individually
|
|
1318
1327
|
|
|
1319
1328
|
### Context Management
|
|
1320
1329
|
|
package/src/agents/monitor.ts
CHANGED
|
@@ -11,6 +11,19 @@ You are a background task monitor. Your ONLY job is to watch background tasks an
|
|
|
11
11
|
3. When ALL tasks complete (or error), you report back to Lead
|
|
12
12
|
4. You do NOT interpret results - just report completion status
|
|
13
13
|
|
|
14
|
+
## Enhanced Inspection
|
|
15
|
+
|
|
16
|
+
When you need deeper insight into a task, use \`agentuity_background_inspect\` which returns:
|
|
17
|
+
- Full message history (not truncated)
|
|
18
|
+
- Active tool calls with status
|
|
19
|
+
- Todo items and their status
|
|
20
|
+
- Cost summary (total cost + tokens)
|
|
21
|
+
- Child session count (for nested Lead-of-Leads)
|
|
22
|
+
|
|
23
|
+
Use inspect when a task has been running for many poll cycles without completing — it can reveal what the agent is stuck on.
|
|
24
|
+
|
|
25
|
+
For a full session tree with all child sessions, costs, and health summary, use \`agentuity_session_dashboard({ session_id: "..." })\`. This is especially useful when monitoring Lead-of-Leads scenarios with multiple parallel workstreams.
|
|
26
|
+
|
|
14
27
|
## Polling Behavior
|
|
15
28
|
|
|
16
29
|
- Poll every 10 seconds (wait between checks)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { PluginInput } from '@opencode-ai/plugin';
|
|
2
2
|
import { agents } from '../agents';
|
|
3
3
|
import type { AgentDefinition } from '../agents';
|
|
4
|
+
import type { DBTextPart, OpenCodeDBReader } from '../sqlite';
|
|
4
5
|
import type {
|
|
5
6
|
BackgroundTask,
|
|
6
7
|
BackgroundTaskConfig,
|
|
@@ -46,6 +47,7 @@ export class BackgroundManager {
|
|
|
46
47
|
private config: BackgroundTaskConfig;
|
|
47
48
|
private concurrency: ConcurrencyManager;
|
|
48
49
|
private callbacks?: BackgroundManagerCallbacks;
|
|
50
|
+
private dbReader?: OpenCodeDBReader;
|
|
49
51
|
private tasks = new Map<string, BackgroundTask>();
|
|
50
52
|
private tasksByParent = new Map<string, Set<string>>();
|
|
51
53
|
private tasksBySession = new Map<string, string>();
|
|
@@ -56,7 +58,8 @@ export class BackgroundManager {
|
|
|
56
58
|
constructor(
|
|
57
59
|
ctx: PluginInput,
|
|
58
60
|
config?: BackgroundTaskConfig,
|
|
59
|
-
callbacks?: BackgroundManagerCallbacks
|
|
61
|
+
callbacks?: BackgroundManagerCallbacks,
|
|
62
|
+
dbReader?: OpenCodeDBReader
|
|
60
63
|
) {
|
|
61
64
|
this.ctx = ctx;
|
|
62
65
|
this.config = { ...DEFAULT_BACKGROUND_CONFIG, ...config };
|
|
@@ -65,6 +68,7 @@ export class BackgroundManager {
|
|
|
65
68
|
limits: buildConcurrencyLimits(this.config),
|
|
66
69
|
});
|
|
67
70
|
this.callbacks = callbacks;
|
|
71
|
+
this.dbReader = dbReader;
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
async launch(input: LaunchInput): Promise<BackgroundTask> {
|
|
@@ -122,6 +126,64 @@ export class BackgroundManager {
|
|
|
122
126
|
if (!task?.sessionId) return undefined;
|
|
123
127
|
|
|
124
128
|
try {
|
|
129
|
+
if (this.dbReader?.isAvailable()) {
|
|
130
|
+
const session = this.dbReader.getSession(task.sessionId);
|
|
131
|
+
const messageCount = this.dbReader.getMessageCount(task.sessionId);
|
|
132
|
+
const messageLimit = messageCount > 0 ? messageCount : 100;
|
|
133
|
+
const messageRows = this.dbReader.getMessages(task.sessionId, {
|
|
134
|
+
limit: messageLimit,
|
|
135
|
+
offset: 0,
|
|
136
|
+
});
|
|
137
|
+
const textParts = this.dbReader.getTextParts(task.sessionId, {
|
|
138
|
+
limit: Math.max(messageLimit * 5, 200),
|
|
139
|
+
});
|
|
140
|
+
const partsByMessage = groupTextPartsByMessage(textParts);
|
|
141
|
+
const messages = messageRows
|
|
142
|
+
.sort((a, b) => a.timeCreated - b.timeCreated)
|
|
143
|
+
.map((message) => ({
|
|
144
|
+
info: {
|
|
145
|
+
role: message.role,
|
|
146
|
+
agent: message.agent,
|
|
147
|
+
model: message.model,
|
|
148
|
+
cost: message.cost,
|
|
149
|
+
tokens: message.tokens,
|
|
150
|
+
error: message.error,
|
|
151
|
+
timeCreated: message.timeCreated,
|
|
152
|
+
timeUpdated: message.timeUpdated,
|
|
153
|
+
},
|
|
154
|
+
parts: buildTextParts(partsByMessage.get(message.id)),
|
|
155
|
+
}));
|
|
156
|
+
const activeTools = this.dbReader.getActiveToolCalls(task.sessionId).map((tool) => ({
|
|
157
|
+
tool: tool.tool,
|
|
158
|
+
status: tool.status,
|
|
159
|
+
callId: tool.callId,
|
|
160
|
+
}));
|
|
161
|
+
const todos = this.dbReader.getTodos(task.sessionId).map((todo) => ({
|
|
162
|
+
content: todo.content,
|
|
163
|
+
status: todo.status,
|
|
164
|
+
priority: todo.priority,
|
|
165
|
+
}));
|
|
166
|
+
const cost = this.dbReader.getSessionCost(task.sessionId);
|
|
167
|
+
const childSessionCount = this.dbReader.getChildSessions(task.sessionId).length;
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
taskId: task.id,
|
|
171
|
+
sessionId: task.sessionId,
|
|
172
|
+
status: task.status,
|
|
173
|
+
session,
|
|
174
|
+
messages,
|
|
175
|
+
lastActivity: task.progress?.lastUpdate?.toISOString(),
|
|
176
|
+
messageCount,
|
|
177
|
+
activeTools,
|
|
178
|
+
todos,
|
|
179
|
+
costSummary: {
|
|
180
|
+
totalCost: cost.totalCost,
|
|
181
|
+
totalTokens: cost.totalTokens,
|
|
182
|
+
},
|
|
183
|
+
childSessionCount,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
125
187
|
// Get session details
|
|
126
188
|
const sessionResponse = await this.ctx.client.session.get({
|
|
127
189
|
path: { id: task.sessionId },
|
|
@@ -183,7 +245,8 @@ export class BackgroundManager {
|
|
|
183
245
|
throwOnError: false,
|
|
184
246
|
});
|
|
185
247
|
|
|
186
|
-
const
|
|
248
|
+
const rawChildren = unwrapResponse<Array<unknown>>(childrenResponse);
|
|
249
|
+
const children = Array.isArray(rawChildren) ? rawChildren : [];
|
|
187
250
|
for (const child of children) {
|
|
188
251
|
const childSession = child as { id?: string; status?: { type?: string } };
|
|
189
252
|
if (!childSession.id) continue;
|
|
@@ -235,12 +298,68 @@ export class BackgroundManager {
|
|
|
235
298
|
let recovered = 0;
|
|
236
299
|
|
|
237
300
|
try {
|
|
301
|
+
if (this.dbReader?.isAvailable()) {
|
|
302
|
+
const parentSessionId = process.env.AGENTUITY_OPENCODE_SESSION;
|
|
303
|
+
if (parentSessionId) {
|
|
304
|
+
const sessions = this.dbReader.getChildSessions(parentSessionId);
|
|
305
|
+
for (const sess of sessions) {
|
|
306
|
+
if (!sess.title?.startsWith('{')) continue;
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
const metadata = JSON.parse(sess.title) as {
|
|
310
|
+
taskId?: string;
|
|
311
|
+
agent?: string;
|
|
312
|
+
description?: string;
|
|
313
|
+
createdAt?: string;
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
if (!metadata.taskId || !metadata.taskId.startsWith('bg_')) continue;
|
|
317
|
+
if (this.tasks.has(metadata.taskId)) continue;
|
|
318
|
+
|
|
319
|
+
const agentName = metadata.agent ?? 'unknown';
|
|
320
|
+
const task: BackgroundTask = {
|
|
321
|
+
id: metadata.taskId,
|
|
322
|
+
sessionId: sess.id,
|
|
323
|
+
parentSessionId: sess.parentId ?? '',
|
|
324
|
+
agent: agentName,
|
|
325
|
+
description: metadata.description ?? '',
|
|
326
|
+
prompt: '',
|
|
327
|
+
status: this.mapDbStatusToTaskStatus(sess.id),
|
|
328
|
+
queuedAt: metadata.createdAt ? new Date(metadata.createdAt) : new Date(),
|
|
329
|
+
startedAt: metadata.createdAt ? new Date(metadata.createdAt) : new Date(),
|
|
330
|
+
concurrencyGroup: this.getConcurrencyGroup(agentName),
|
|
331
|
+
progress: {
|
|
332
|
+
toolCalls: 0,
|
|
333
|
+
lastUpdate: new Date(),
|
|
334
|
+
},
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
this.tasks.set(task.id, task);
|
|
338
|
+
this.tasksBySession.set(sess.id, task.id);
|
|
339
|
+
|
|
340
|
+
if (task.parentSessionId) {
|
|
341
|
+
const parentTasks =
|
|
342
|
+
this.tasksByParent.get(task.parentSessionId) ?? new Set();
|
|
343
|
+
parentTasks.add(task.id);
|
|
344
|
+
this.tasksByParent.set(task.parentSessionId, parentTasks);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
recovered++;
|
|
348
|
+
} catch {
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return recovered;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
238
356
|
// Get all sessions
|
|
239
357
|
const sessionsResponse = await this.ctx.client.session.list({
|
|
240
358
|
throwOnError: false,
|
|
241
359
|
});
|
|
242
360
|
|
|
243
|
-
const
|
|
361
|
+
const rawSessions = unwrapResponse<Array<unknown>>(sessionsResponse);
|
|
362
|
+
const sessions = Array.isArray(rawSessions) ? rawSessions : [];
|
|
244
363
|
|
|
245
364
|
for (const session of sessions) {
|
|
246
365
|
const sess = session as {
|
|
@@ -628,6 +747,18 @@ Use the agentuity_background_output tool with task_id "${task.id}" to view the r
|
|
|
628
747
|
|
|
629
748
|
private async fetchLatestResult(sessionId: string): Promise<string | undefined> {
|
|
630
749
|
try {
|
|
750
|
+
if (this.dbReader?.isAvailable()) {
|
|
751
|
+
const messages = this.dbReader.getMessages(sessionId, { limit: 100, offset: 0 });
|
|
752
|
+
const textParts = this.dbReader.getTextParts(sessionId, { limit: 300 });
|
|
753
|
+
const partsByMessage = groupTextPartsByMessage(textParts);
|
|
754
|
+
for (const message of messages) {
|
|
755
|
+
if (message.role !== 'assistant') continue;
|
|
756
|
+
const text = joinTextParts(partsByMessage.get(message.id));
|
|
757
|
+
if (text) return text;
|
|
758
|
+
}
|
|
759
|
+
return undefined;
|
|
760
|
+
}
|
|
761
|
+
|
|
631
762
|
const messagesResult = await this.ctx.client.session.messages({
|
|
632
763
|
path: { id: sessionId },
|
|
633
764
|
throwOnError: true,
|
|
@@ -647,6 +778,23 @@ Use the agentuity_background_output tool with task_id "${task.id}" to view the r
|
|
|
647
778
|
return undefined;
|
|
648
779
|
}
|
|
649
780
|
|
|
781
|
+
private mapDbStatusToTaskStatus(sessionId: string): BackgroundTaskStatus {
|
|
782
|
+
if (!this.dbReader) return 'pending';
|
|
783
|
+
const status = this.dbReader.getSessionStatus(sessionId).status;
|
|
784
|
+
switch (status) {
|
|
785
|
+
case 'idle':
|
|
786
|
+
case 'archived':
|
|
787
|
+
return 'completed';
|
|
788
|
+
case 'active':
|
|
789
|
+
case 'compacting':
|
|
790
|
+
return 'running';
|
|
791
|
+
case 'error':
|
|
792
|
+
return 'error';
|
|
793
|
+
default:
|
|
794
|
+
return 'pending';
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
650
798
|
private getConcurrencyGroup(agentName: string): string | undefined {
|
|
651
799
|
const model = getAgentModel(agentName);
|
|
652
800
|
if (!model) return undefined;
|
|
@@ -733,6 +881,29 @@ function extractTextFromParts(parts: Array<unknown>): string | undefined {
|
|
|
733
881
|
return textParts.join('\n');
|
|
734
882
|
}
|
|
735
883
|
|
|
884
|
+
function groupTextPartsByMessage(parts: DBTextPart[]): Map<string, DBTextPart[]> {
|
|
885
|
+
const grouped = new Map<string, DBTextPart[]>();
|
|
886
|
+
for (const part of parts) {
|
|
887
|
+
const list = grouped.get(part.messageId) ?? [];
|
|
888
|
+
list.push(part);
|
|
889
|
+
grouped.set(part.messageId, list);
|
|
890
|
+
}
|
|
891
|
+
for (const list of grouped.values()) {
|
|
892
|
+
list.sort((a, b) => a.timeCreated - b.timeCreated);
|
|
893
|
+
}
|
|
894
|
+
return grouped;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function buildTextParts(parts?: DBTextPart[]): Array<{ type: string; text: string }> {
|
|
898
|
+
if (!parts || parts.length === 0) return [];
|
|
899
|
+
return parts.map((part) => ({ type: 'text', text: part.text }));
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
function joinTextParts(parts?: DBTextPart[]): string | undefined {
|
|
903
|
+
if (!parts || parts.length === 0) return undefined;
|
|
904
|
+
return parts.map((part) => part.text).join('\n');
|
|
905
|
+
}
|
|
906
|
+
|
|
736
907
|
function unwrapResponse<T>(result: unknown): T | undefined {
|
|
737
908
|
if (typeof result === 'object' && result !== null && 'data' in result) {
|
|
738
909
|
return (result as { data?: T }).data;
|
package/src/background/types.ts
CHANGED
|
@@ -66,4 +66,14 @@ export interface TaskInspection {
|
|
|
66
66
|
messages: Array<{ info: unknown; parts: unknown[] }>;
|
|
67
67
|
/** Last activity timestamp from task progress */
|
|
68
68
|
lastActivity?: string;
|
|
69
|
+
/** Total message count when available */
|
|
70
|
+
messageCount?: number;
|
|
71
|
+
/** Active tools with status when available */
|
|
72
|
+
activeTools?: Array<{ tool: string; status: string; callId: string }>;
|
|
73
|
+
/** Todo items when available */
|
|
74
|
+
todos?: Array<{ content: string; status: string; priority: string }>;
|
|
75
|
+
/** Cost summary when available */
|
|
76
|
+
costSummary?: { totalCost: number; totalTokens: number };
|
|
77
|
+
/** Count of child sessions (nested LoL) when available */
|
|
78
|
+
childSessionCount?: number;
|
|
69
79
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { PluginInput } from '@opencode-ai/plugin';
|
|
2
2
|
import type { CoderConfig } from '../../types';
|
|
3
3
|
import type { BackgroundManager } from '../../background';
|
|
4
|
+
import type { OpenCodeDBReader, SessionTreeNode } from '../../sqlite';
|
|
4
5
|
|
|
5
6
|
/** Compacting hook input/output types */
|
|
6
7
|
type CompactingInput = { sessionID: string };
|
|
@@ -69,7 +70,8 @@ interface CadenceSessionState {
|
|
|
69
70
|
export function createCadenceHooks(
|
|
70
71
|
ctx: PluginInput,
|
|
71
72
|
_config: CoderConfig,
|
|
72
|
-
backgroundManager?: BackgroundManager
|
|
73
|
+
backgroundManager?: BackgroundManager,
|
|
74
|
+
dbReader?: OpenCodeDBReader
|
|
73
75
|
): CadenceHooks {
|
|
74
76
|
const activeCadenceSessions = new Map<string, CadenceSessionState>();
|
|
75
77
|
|
|
@@ -320,6 +322,7 @@ ${taskList}
|
|
|
320
322
|
|
|
321
323
|
**CRITICAL:** Task IDs and session IDs persist across compaction - these tasks are still running.
|
|
322
324
|
Use \`agentuity_background_output({ task_id: "..." })\` to check their status.
|
|
325
|
+
Use \`agentuity_session_dashboard({ session_id: "..." })\` to get a full session tree with status, costs, and health summary for Lead-of-Leads monitoring.
|
|
323
326
|
|
|
324
327
|
**Tip:** If you spawned child Leads for parallel work, delegate monitoring to BackgroundMonitor:
|
|
325
328
|
\`\`\`typescript
|
|
@@ -363,6 +366,11 @@ After compaction:
|
|
|
363
366
|
3. Lead will continue the loop from iteration ${state.iteration}
|
|
364
367
|
4. Use 5-Question Reboot to re-orient: Where am I? Where going? Goal? Learned? Done?
|
|
365
368
|
`);
|
|
369
|
+
|
|
370
|
+
const dashboardSummary = buildSqliteDashboardSummary(dbReader, sessionId);
|
|
371
|
+
if (dashboardSummary) {
|
|
372
|
+
output.context.push(dashboardSummary);
|
|
373
|
+
}
|
|
366
374
|
},
|
|
367
375
|
|
|
368
376
|
/**
|
|
@@ -455,6 +463,69 @@ function isCadenceStop(text: string): boolean {
|
|
|
455
463
|
);
|
|
456
464
|
}
|
|
457
465
|
|
|
466
|
+
function buildSqliteDashboardSummary(
|
|
467
|
+
dbReader: OpenCodeDBReader | undefined,
|
|
468
|
+
parentSessionId: string
|
|
469
|
+
): string | undefined {
|
|
470
|
+
if (!dbReader || !dbReader.isAvailable()) return undefined;
|
|
471
|
+
|
|
472
|
+
const dashboard = dbReader.getSessionDashboard(parentSessionId);
|
|
473
|
+
if (!dashboard.sessions.length) return undefined;
|
|
474
|
+
|
|
475
|
+
const lines: string[] = [];
|
|
476
|
+
for (const node of dashboard.sessions) {
|
|
477
|
+
appendDashboardNode(dbReader, node, 0, lines);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return `
|
|
481
|
+
## SQLite Session Dashboard
|
|
482
|
+
|
|
483
|
+
- Parent session: ${parentSessionId}
|
|
484
|
+
- Total child cost: ${dashboard.totalCost}
|
|
485
|
+
|
|
486
|
+
${lines.join('\n')}
|
|
487
|
+
`;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function appendDashboardNode(
|
|
491
|
+
reader: OpenCodeDBReader,
|
|
492
|
+
node: SessionTreeNode,
|
|
493
|
+
depth: number,
|
|
494
|
+
lines: string[]
|
|
495
|
+
): void {
|
|
496
|
+
const status = reader.getSessionStatus(node.session.id);
|
|
497
|
+
const todoSummary = node.todoSummary
|
|
498
|
+
? `${node.todoSummary.pending}/${node.todoSummary.total} pending`
|
|
499
|
+
: 'no todos';
|
|
500
|
+
const costSummary = node.costSummary
|
|
501
|
+
? `${node.costSummary.totalCost} (${node.costSummary.totalTokens} tokens)`
|
|
502
|
+
: '0 (0 tokens)';
|
|
503
|
+
const label = formatSessionLabel(node.session.title);
|
|
504
|
+
const indent = `${' '.repeat(depth)}-`;
|
|
505
|
+
|
|
506
|
+
lines.push(
|
|
507
|
+
`${indent} ${label} [${node.session.id}] status: ${status.status}, tools: ${node.activeToolCount}, todos: ${todoSummary}, messages: ${node.messageCount}, cost: ${costSummary}`
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
for (const child of node.children) {
|
|
511
|
+
appendDashboardNode(reader, child, depth + 1, lines);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function formatSessionLabel(title: string): string {
|
|
516
|
+
if (!title) return 'Session';
|
|
517
|
+
if (title.startsWith('{')) {
|
|
518
|
+
try {
|
|
519
|
+
const parsed = JSON.parse(title) as { description?: string; taskId?: string };
|
|
520
|
+
if (parsed.description) return parsed.description;
|
|
521
|
+
if (parsed.taskId) return parsed.taskId;
|
|
522
|
+
} catch {
|
|
523
|
+
return title;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return title;
|
|
527
|
+
}
|
|
528
|
+
|
|
458
529
|
function showToast(ctx: PluginInput, message: string): void {
|
|
459
530
|
try {
|
|
460
531
|
ctx.client.tui.showToast({ body: { message, variant: 'info' } });
|