@rejacky/opencode-insights 0.3.0 → 0.3.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/README.md CHANGED
@@ -95,11 +95,12 @@ The `uninstall` command removes plugin config entries and local Insights data; i
95
95
  - A collapsible session-wide `Token Usage` sidebar showing total tokens, response count, input/output/reasoning usage, cache read/write usage, and aggregate cache rate. It loads completed responses already present in the session and continues updating live.
96
96
  - An opt-in `Go Usage` sidebar showing OpenCode Go rolling/weekly/monthly usage limits for sessions that use the `opencode-go` provider.
97
97
  - Subagent status (running, done, failed, elapsed time, and token/context usage) in the sidebar.
98
+ - A collapsible `Session Analysis` sidebar showing the active session's aggregated activity (tool calls, skills, auto-compactions, warnings, model requests, subagent tree). Click it to open a detail dialog; its vertical scrollbar appears only when the content overflows the dialog.
98
99
  - Local capture of OpenCode hook/event data without redaction.
99
100
  - A local web viewer for reconstructed sessions, user turns, hidden request context, system/messages transforms, and assistant thinking/response sequences.
100
101
  - Native OpenCode footer components (project directory and version) remain visible — the plugin does not override `sidebar_footer` or `home_prompt_right` slots.
101
102
 
102
- The right sidebar contains the plugin sections: `Token Usage`, `Go Usage` (when enabled and the session uses `opencode-go`), and `Subagents`. Click any section header to collapse or expand it. Token usage is aggregated across the full session; prompt-right `used` and `cache` values continue to represent the latest completed assistant response.
103
+ The right sidebar contains the plugin sections: `Token Usage`, `Go Usage` (when enabled and the session uses `opencode-go`), `Subagents`, and `Session Analysis`. Click any section header to collapse or expand it. Token usage is aggregated across the full session; prompt-right `used` and `cache` values continue to represent the latest completed assistant response.
103
104
 
104
105
  ## TUI Metrics Configuration
105
106
 
@@ -78,7 +78,7 @@ declare function renderPromptRightMetricsText(state: MetricsState, sessionID: st
78
78
  metrics?: PromptRightMetric[];
79
79
  }): string;
80
80
  declare function getSessionTokenUsage(state: MetricsState, sessionID: string): SessionTokenUsage | undefined;
81
- declare function renderSessionTokenUsage(state: MetricsState, sessionID: string): string;
81
+ declare function renderSessionTokenUsage(state: MetricsState, sessionID: string, subagentTokens?: number): string;
82
82
 
83
83
  type CaptureKind = "chat.message" | "chat.params" | "chat.headers" | "experimental.chat.messages.transform" | "experimental.chat.system.transform" | "event" | "tool.execute.before" | "tool.execute.after";
84
84
  type CaptureRecord = {
@@ -136,21 +136,28 @@ function renderPromptRightMetricsText(state, sessionID, options = {}) {
136
136
  function getSessionTokenUsage(state, sessionID) {
137
137
  return state.sessionTokenUsageByID[sessionID];
138
138
  }
139
- function renderSessionTokenUsage(state, sessionID) {
139
+ function renderSessionTokenUsage(state, sessionID, subagentTokens = 0) {
140
140
  const usage = getSessionTokenUsage(state, sessionID);
141
141
  if (!usage) return "";
142
+ const grandTotal = usage.totalTokens + subagentTokens;
142
143
  const cachePromptTokens = usage.inputTokens + usage.cacheReadTokens;
143
144
  const cacheRate = cachePromptTokens > 0 ? usage.cacheReadTokens / cachePromptTokens * 100 : void 0;
144
- return [
145
+ const lines = [
145
146
  "Token Usage",
146
- `${formatTokenCount(usage.totalTokens)} total \xB7 ${usage.responseCount} responses`,
147
+ `${formatTokenCount(grandTotal)} total \xB7 ${usage.responseCount} responses`
148
+ ];
149
+ if (subagentTokens > 0) {
150
+ lines.push(`${formatTokenCount(subagentTokens)} used by subagents`);
151
+ }
152
+ lines.push(
147
153
  `${formatTokenCount(usage.inputTokens)} input`,
148
154
  `${formatTokenCount(usage.outputTokens)} output`,
149
155
  `${formatTokenCount(usage.reasoningTokens)} reasoning`,
150
156
  `${formatTokenCount(usage.cacheReadTokens)} cache read`,
151
157
  `${formatTokenCount(usage.cacheWriteTokens)} cache write`,
152
158
  `${cacheRate === void 0 ? "-" : formatPercent(cacheRate)} cache rate`
153
- ].join("\n");
159
+ );
160
+ return lines.join("\n");
154
161
  }
155
162
  function pruneSamples(state, now = Date.now()) {
156
163
  for (const [sessionID, samples] of Object.entries(state.streamSamplesBySession)) {
@@ -225,6 +225,20 @@ function buildSessionAnalysisRows(state, rootSessionID) {
225
225
  function createSubagentState(activityStore) {
226
226
  return { children: {}, totalExecuted: 0, ...activityStore ? { activityStore } : {} };
227
227
  }
228
+ function recordSubagentFromSessionInfo(state, session) {
229
+ if (!session.parentID || !session.id || session.id === session.parentID) return;
230
+ if (state.children[session.id]) return;
231
+ const now = (/* @__PURE__ */ new Date()).toISOString();
232
+ state.children[session.id] = {
233
+ id: session.id,
234
+ parentID: session.parentID,
235
+ title: session.title ?? "subagent",
236
+ status: "done",
237
+ startedAt: now,
238
+ updatedAt: now
239
+ };
240
+ state.totalExecuted += 1;
241
+ }
228
242
  function applySubagentEvent(state, event) {
229
243
  const created = extractTaskToolSubagent(event) ?? extractSubagent(event) ?? updateExistingSubagent(state, event);
230
244
  if (!created) return false;
@@ -275,6 +289,9 @@ function getSubagentItems(state, parentID) {
275
289
  return b.startedAt.localeCompare(a.startedAt);
276
290
  });
277
291
  }
292
+ function sumSubagentTokens(state, parentID) {
293
+ return getSubagentItems(state, parentID).reduce((sum, child) => sum + (child.tokens?.total ?? 0), 0);
294
+ }
278
295
  function pruneStaleSubagents(state, options = {}) {
279
296
  const now = options.now ?? Date.now();
280
297
  const staleMs = options.staleMs ?? 18e4;
@@ -535,9 +552,11 @@ export {
535
552
  treeSubagentCount,
536
553
  buildSessionAnalysisRows,
537
554
  createSubagentState,
555
+ recordSubagentFromSessionInfo,
538
556
  applySubagentEvent,
539
557
  renderSubagentStatus,
540
558
  getSubagentItems,
559
+ sumSubagentTokens,
541
560
  pruneStaleSubagents,
542
561
  getSubagentSidebarModel,
543
562
  getSubagentSidebarRowAtLine,
package/dist/cli.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { C as CaptureRecord } from './capture-CwesXqmX.js';
2
+ import { C as CaptureRecord } from './capture-0knBP9m2.js';
3
3
 
4
4
  type HistoryMessage = {
5
5
  id: string;
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  readInsightsConfig,
5
5
  resolveCapturePath,
6
6
  resolveInsightsConfigPath
7
- } from "./chunk-RZGCLQ2M.js";
7
+ } from "./chunk-IXXHI7ZM.js";
8
8
 
9
9
  // src/cli.ts
10
10
  import { execFile as execFile2 } from "child_process";
@@ -217,21 +217,19 @@ function buildRequestHistory(records) {
217
217
  continue;
218
218
  }
219
219
  if (type === "message.part.updated" || type === "message.part.delta") {
220
+ if (type === "message.part.delta") continue;
220
221
  const part = isRecord(properties.part) ? properties.part : {};
221
222
  const sessionID = optionalString(part.sessionID) ?? optionalString(properties.sessionID);
222
223
  const messageID = optionalString(part.messageID) ?? optionalString(properties.messageID);
223
224
  if (!sessionID || !messageID) continue;
224
225
  const partType = optionalString(part.type);
225
- const field = optionalString(properties.field);
226
- const delta = optionalString(properties.delta);
227
226
  const text = optionalString(part.text);
228
227
  const reasonText = optionalString(part.text) ?? optionalString(part.markdown);
229
228
  const targetResponse = responses.get(`${sessionID}:${messageID}`);
230
229
  if (targetResponse) {
231
- targetResponse.events.push(record.payload);
230
+ if (partType === "tool") targetResponse.events.push(record.payload);
232
231
  if (partType === "text" && text !== void 0) targetResponse.text = text;
233
232
  if (partType === "reasoning" && reasonText !== void 0) targetResponse.reasoning = reasonText;
234
- if (type === "message.part.delta" && field === "text" && delta !== void 0) targetResponse.text += delta;
235
233
  continue;
236
234
  }
237
235
  if (partType !== "text" && partType !== "reasoning") continue;
@@ -376,7 +374,6 @@ function viewerCaptureSql(limit) {
376
374
  and event_type in (
377
375
  'message.updated',
378
376
  'message.part.updated',
379
- 'message.part.delta',
380
377
  'session.updated',
381
378
  'session.created'
382
379
  )
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Plugin } from '@opencode-ai/plugin';
2
2
  import { TuiPlugin } from '@opencode-ai/plugin/tui';
3
- export { A as AssistantResponseUsage, a as CaptureKind, C as CaptureRecord, b as CaptureStore, D as DEFAULT_PROMPT_RIGHT_METRICS, G as GoUsageConfig, I as InsightsConfig, c as InsightsOptions, J as JsonlCaptureStore, M as MessageTiming, d as MetricsState, P as PromptRightMetric, S as SessionAverage, e as SessionTokenUsage, f as SqliteCaptureStore, g as SqliteDb, h as StreamSample, i as createCaptureStore, j as createMetricsState, k as defaultDataDir, l as estimateStreamTokens, m as extractEventType, n as getSessionTokenUsage, o as insightsOptionsFromConfig, p as normalizeChatHeadersCapture, q as normalizeChatMessageCapture, r as normalizeChatParamsCapture, s as normalizeEventCapture, t as normalizeExperimentalChatMessagesTransformCapture, u as normalizeExperimentalChatSystemTransformCapture, v as normalizeToolCapture, w as openDatabase, x as readInsightsConfig, y as recordAssistantDelta, z as recordAssistantMessage, B as recordToolActivity, E as renderMetricsText, F as renderPromptRightMetricsText, H as renderResponseMetricsText, K as renderSessionTokenUsage, L as resolveCapturePath, N as resolveInsightsConfigPath, O as resolveLegacyInsightsConfigPath, Q as resolveRetentionDays } from './capture-CwesXqmX.js';
3
+ export { A as AssistantResponseUsage, a as CaptureKind, C as CaptureRecord, b as CaptureStore, D as DEFAULT_PROMPT_RIGHT_METRICS, G as GoUsageConfig, I as InsightsConfig, c as InsightsOptions, J as JsonlCaptureStore, M as MessageTiming, d as MetricsState, P as PromptRightMetric, S as SessionAverage, e as SessionTokenUsage, f as SqliteCaptureStore, g as SqliteDb, h as StreamSample, i as createCaptureStore, j as createMetricsState, k as defaultDataDir, l as estimateStreamTokens, m as extractEventType, n as getSessionTokenUsage, o as insightsOptionsFromConfig, p as normalizeChatHeadersCapture, q as normalizeChatMessageCapture, r as normalizeChatParamsCapture, s as normalizeEventCapture, t as normalizeExperimentalChatMessagesTransformCapture, u as normalizeExperimentalChatSystemTransformCapture, v as normalizeToolCapture, w as openDatabase, x as readInsightsConfig, y as recordAssistantDelta, z as recordAssistantMessage, B as recordToolActivity, E as renderMetricsText, F as renderPromptRightMetricsText, H as renderResponseMetricsText, K as renderSessionTokenUsage, L as resolveCapturePath, N as resolveInsightsConfigPath, O as resolveLegacyInsightsConfigPath, Q as resolveRetentionDays } from './capture-0knBP9m2.js';
4
4
 
5
5
  type SessionActivity = {
6
6
  toolCalls: number;
@@ -58,11 +58,17 @@ type SubagentSidebarModel = {
58
58
  rows: SubagentSidebarRow[];
59
59
  };
60
60
  declare function createSubagentState(activityStore?: ActivityState): SubagentState;
61
+ declare function recordSubagentFromSessionInfo(state: SubagentState, session: {
62
+ id: string;
63
+ parentID?: string;
64
+ title?: string;
65
+ }): void;
61
66
  declare function applySubagentEvent(state: SubagentState, event: unknown): boolean;
62
67
  declare function renderSubagentStatus(state: SubagentState, options?: {
63
68
  now?: number;
64
69
  }): string;
65
70
  declare function getSubagentItems(state: SubagentState, parentID?: string): SubagentInfo[];
71
+ declare function sumSubagentTokens(state: SubagentState, parentID: string): number;
66
72
  declare function pruneStaleSubagents(state: SubagentState, options?: {
67
73
  now?: number;
68
74
  staleMs?: number;
@@ -89,4 +95,4 @@ declare const _default: {
89
95
  server: Plugin;
90
96
  };
91
97
 
92
- export { OpenCodeInsights, type SubagentInfo, type SubagentSidebarModel, type SubagentSidebarRow, type SubagentState, type SubagentStatus, applySubagentEvent, createSubagentState, _default as default, getSubagentItems, getSubagentSidebarModel, getSubagentSidebarRowAtLine, id, pruneStaleSubagents, renderSubagentFooter, renderSubagentSidebar, renderSubagentStatus, server, rootTui as tui };
98
+ export { OpenCodeInsights, type SubagentInfo, type SubagentSidebarModel, type SubagentSidebarRow, type SubagentState, type SubagentStatus, applySubagentEvent, createSubagentState, _default as default, getSubagentItems, getSubagentSidebarModel, getSubagentSidebarRowAtLine, id, pruneStaleSubagents, recordSubagentFromSessionInfo, renderSubagentFooter, renderSubagentSidebar, renderSubagentStatus, server, sumSubagentTokens, rootTui as tui };
package/dist/index.js CHANGED
@@ -5,10 +5,12 @@ import {
5
5
  getSubagentSidebarModel,
6
6
  getSubagentSidebarRowAtLine,
7
7
  pruneStaleSubagents,
8
+ recordSubagentFromSessionInfo,
8
9
  renderSubagentFooter,
9
10
  renderSubagentSidebar,
10
- renderSubagentStatus
11
- } from "./chunk-SGXZJVYX.js";
11
+ renderSubagentStatus,
12
+ sumSubagentTokens
13
+ } from "./chunk-ODCIUSCV.js";
12
14
  import {
13
15
  DEFAULT_PROMPT_RIGHT_METRICS,
14
16
  JsonlCaptureStore,
@@ -40,7 +42,7 @@ import {
40
42
  resolveInsightsConfigPath,
41
43
  resolveLegacyInsightsConfigPath,
42
44
  resolveRetentionDays
43
- } from "./chunk-RZGCLQ2M.js";
45
+ } from "./chunk-IXXHI7ZM.js";
44
46
 
45
47
  // src/cli-shim.ts
46
48
  import { existsSync } from "fs";
@@ -173,6 +175,7 @@ export {
173
175
  readInsightsConfig,
174
176
  recordAssistantDelta,
175
177
  recordAssistantMessage,
178
+ recordSubagentFromSessionInfo,
176
179
  recordToolActivity,
177
180
  renderMetricsText,
178
181
  renderPromptRightMetricsText,
@@ -186,5 +189,6 @@ export {
186
189
  resolveLegacyInsightsConfigPath,
187
190
  resolveRetentionDays,
188
191
  server,
192
+ sumSubagentTokens,
189
193
  rootTui as tui
190
194
  };
package/dist/tui.js CHANGED
@@ -10,10 +10,11 @@ import {
10
10
  recordCompaction,
11
11
  recordStep,
12
12
  recordToolPart,
13
+ sumSubagentTokens,
13
14
  treeActivity,
14
15
  treeLoading,
15
16
  treeSubagentCount
16
- } from "./chunk-SGXZJVYX.js";
17
+ } from "./chunk-ODCIUSCV.js";
17
18
  import {
18
19
  createMetricsState,
19
20
  readInsightsConfig,
@@ -22,7 +23,7 @@ import {
22
23
  recordToolActivity,
23
24
  renderPromptRightMetricsText,
24
25
  renderSessionTokenUsage
25
- } from "./chunk-RZGCLQ2M.js";
26
+ } from "./chunk-IXXHI7ZM.js";
26
27
 
27
28
  // src/tui.tsx
28
29
  import { createTextAttributes, StyledText } from "@opentui/core";
@@ -312,7 +313,8 @@ function TokenUsageSidebar(props) {
312
313
  };
313
314
  const sync = () => {
314
315
  if (!text) return;
315
- const content = renderSessionTokenUsage(props.state, props.sessionID);
316
+ const subagentTokens = sumSubagentTokens(props.subagentState, props.sessionID);
317
+ const content = renderSessionTokenUsage(props.state, props.sessionID, subagentTokens);
316
318
  const next = {
317
319
  content: `${collapsed()}|${content}`,
318
320
  visible: content.length > 0,
@@ -546,7 +548,6 @@ function SessionAnalysisDialog(props) {
546
548
  /* @__PURE__ */ jsxs(
547
549
  "scrollbox",
548
550
  {
549
- verticalScrollbarOptions: { visible: true },
550
551
  maxHeight,
551
552
  flexGrow: 1,
552
553
  paddingTop: 1,
@@ -794,6 +795,7 @@ var tui = async (api, options) => {
794
795
  api,
795
796
  sessionID: props.session_id,
796
797
  state: metrics,
798
+ subagentState: subagents,
797
799
  subscribe: metricListeners.subscribe,
798
800
  hydrate: () => void hydrateSessionMetrics(props.session_id)
799
801
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@rejacky/opencode-insights",
4
- "version": "0.3.0",
4
+ "version": "0.3.2",
5
5
  "description": "OpenCode plugin for local request capture, TPS metrics, and subagent status visibility.",
6
6
  "type": "module",
7
7
  "author": "opencode-insights contributors",