@leo000001/opencode-quota-sidebar 3.0.5 → 3.0.6

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/events.d.ts CHANGED
@@ -9,5 +9,5 @@ export declare function createEventDispatcher(handlers: {
9
9
  sessionID: string;
10
10
  messageID?: string;
11
11
  }) => Promise<void>;
12
- onAssistantMessageCompleted: (message: AssistantMessage) => Promise<void>;
12
+ onAssistantMessageUpdated: (message: AssistantMessage) => Promise<void>;
13
13
  }): (event: Event) => Promise<void>;
package/dist/events.js CHANGED
@@ -16,7 +16,8 @@ export function createEventDispatcher(handlers) {
16
16
  await handlers.onSessionDeleted(event.properties.info);
17
17
  return;
18
18
  }
19
- if (tui.type === 'tui.prompt.append' || tui.type === 'tui.command.execute') {
19
+ if (tui.type === 'tui.prompt.append' ||
20
+ tui.type === 'tui.command.execute') {
20
21
  await handlers.onTuiActivity();
21
22
  return;
22
23
  }
@@ -39,9 +40,6 @@ export function createEventDispatcher(handlers) {
39
40
  return;
40
41
  if (!isAssistantMessage(event.properties.info))
41
42
  return;
42
- const completed = event.properties.info.time.completed;
43
- if (typeof completed !== 'number' || !Number.isFinite(completed))
44
- return;
45
- await handlers.onAssistantMessageCompleted(event.properties.info);
43
+ await handlers.onAssistantMessageUpdated(event.properties.info);
46
44
  };
47
45
  }
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ import { createUsageService } from './usage_service.js';
13
13
  import { createTitleApplicator } from './title_apply.js';
14
14
  const SHUTDOWN_HOOK_KEY = Symbol.for('opencode-quota-sidebar.shutdown-hook');
15
15
  const SHUTDOWN_CALLBACKS_KEY = Symbol.for('opencode-quota-sidebar.shutdown-callbacks');
16
+ const SESSION_ACTIVE_GRACE_MS = 15_000;
16
17
  export async function QuotaSidebarPlugin(input) {
17
18
  const quotaRuntime = createQuotaRuntime();
18
19
  const config = await loadConfig(quotaConfigPaths(input.worktree, input.directory));
@@ -116,11 +117,33 @@ export async function QuotaSidebarPlugin(input) {
116
117
  });
117
118
  const summarizeSessionUsageForDisplay = usageService.summarizeSessionUsageForDisplay;
118
119
  const summarizeForTool = usageService.summarizeForTool;
120
+ const activeSessionUntil = new Map();
121
+ const markSessionActive = (sessionID, now = Date.now()) => {
122
+ activeSessionUntil.set(sessionID, now + SESSION_ACTIVE_GRACE_MS);
123
+ };
124
+ const clearSessionActivity = (sessionID) => {
125
+ activeSessionUntil.delete(sessionID);
126
+ };
127
+ const isSessionActive = (sessionID, now = Date.now()) => {
128
+ const expiresAt = activeSessionUntil.get(sessionID);
129
+ if (expiresAt === undefined)
130
+ return false;
131
+ if (expiresAt > now)
132
+ return true;
133
+ activeSessionUntil.delete(sessionID);
134
+ return false;
135
+ };
119
136
  // title apply / refresh lifecycle
120
137
  let scheduleTitleRefresh = (sessionID, delay = 250) => {
121
138
  void sessionID;
122
139
  void delay;
123
140
  };
141
+ const scheduleActiveTitleRefresh = (sessionID, delay = 250) => {
142
+ if (!isSessionActive(sessionID))
143
+ return false;
144
+ scheduleTitleRefresh(sessionID, delay);
145
+ return true;
146
+ };
124
147
  const scheduleParentRefreshIfSafe = (sessionID, parentID) => {
125
148
  if (!config.sidebar.includeChildren)
126
149
  return;
@@ -140,7 +163,7 @@ export async function QuotaSidebarPlugin(input) {
140
163
  seen.add(current);
141
164
  current = state.sessions[current]?.parentID;
142
165
  }
143
- scheduleTitleRefresh(parentID, 0);
166
+ scheduleActiveTitleRefresh(parentID, 0);
144
167
  };
145
168
  const titleApplicator = createTitleApplicator({
146
169
  state,
@@ -159,6 +182,8 @@ export async function QuotaSidebarPlugin(input) {
159
182
  });
160
183
  const titleRefresh = createTitleRefreshScheduler({
161
184
  apply: async (sessionID) => {
185
+ if (!isSessionActive(sessionID))
186
+ return;
162
187
  await titleApplicator.applyTitle(sessionID);
163
188
  },
164
189
  onError: swallow('titleRefresh'),
@@ -186,10 +211,7 @@ export async function QuotaSidebarPlugin(input) {
186
211
  startupTitleWork = runStartupRestore().catch(swallow('startup:restoreAllVisibleTitles'));
187
212
  }
188
213
  else {
189
- startupTitleWork = Promise.allSettled([
190
- refreshAllVisibleTitles().catch(swallow('startup:refreshAllVisibleTitles')),
191
- refreshAllTouchedTitles().catch(swallow('startup:refreshAllTouchedTitles')),
192
- ]).then(() => undefined);
214
+ startupTitleWork = Promise.resolve();
193
215
  }
194
216
  const shutdown = async () => {
195
217
  await Promise.race([
@@ -318,7 +340,7 @@ export async function QuotaSidebarPlugin(input) {
318
340
  sessionID: session.id,
319
341
  incomingTitle: session.title,
320
342
  sessionState,
321
- scheduleRefresh: titleRefresh.schedule,
343
+ scheduleRefresh: scheduleActiveTitleRefresh,
322
344
  });
323
345
  },
324
346
  onSessionDeleted: async (session) => {
@@ -328,6 +350,7 @@ export async function QuotaSidebarPlugin(input) {
328
350
  usageService.forgetSession(session.id);
329
351
  titleApplicator.forgetSession(session.id);
330
352
  titleRefresh.cancel(session.id);
353
+ clearSessionActivity(session.id);
331
354
  const dateKey = state.sessionDateMap[session.id] ||
332
355
  dateKeyFromTimestamp(session.time.created);
333
356
  state.deletedSessionDateMap[session.id] = dateKey;
@@ -341,23 +364,28 @@ export async function QuotaSidebarPlugin(input) {
341
364
  scheduleSave();
342
365
  }
343
366
  if (config.sidebar.includeChildren && session.parentID) {
344
- titleRefresh.schedule(session.parentID, 0);
367
+ scheduleActiveTitleRefresh(session.parentID, 0);
345
368
  }
346
369
  },
347
370
  onTuiActivity: async () => {
348
371
  return;
349
372
  },
350
373
  onTuiSessionSelect: async (sessionID) => {
351
- titleRefresh.schedule(sessionID, 0);
374
+ scheduleActiveTitleRefresh(sessionID, 0);
352
375
  },
353
376
  onMessageRemoved: async (info) => {
354
377
  usageService.markForceRescan(info.sessionID);
355
- titleRefresh.schedule(info.sessionID, 0);
378
+ scheduleActiveTitleRefresh(info.sessionID, 0);
356
379
  scheduleParentRefreshIfSafe(info.sessionID, state.sessions[info.sessionID]?.parentID);
357
380
  },
358
- onAssistantMessageCompleted: async (message) => {
381
+ onAssistantMessageUpdated: async (message) => {
382
+ markSessionActive(message.sessionID);
383
+ const completed = message.time.completed;
384
+ if (typeof completed !== 'number' || !Number.isFinite(completed)) {
385
+ return;
386
+ }
359
387
  usageService.markSessionDirty(message.sessionID);
360
- titleRefresh.schedule(message.sessionID);
388
+ scheduleActiveTitleRefresh(message.sessionID);
361
389
  void maybeShowExpiryToast(message.sessionID);
362
390
  },
363
391
  });
@@ -378,7 +406,7 @@ export async function QuotaSidebarPlugin(input) {
378
406
  scheduleSave,
379
407
  flushSave,
380
408
  waitForStartupTitleWork: () => startupTitleWork,
381
- refreshSessionTitle: (sessionID, delay) => titleRefresh.schedule(sessionID, delay ?? 250),
409
+ refreshSessionTitle: (sessionID, delay) => scheduleActiveTitleRefresh(sessionID, delay ?? 250),
382
410
  cancelAllTitleRefreshes: () => titleRefresh.cancelAll(),
383
411
  flushScheduledTitleRefreshes: () => titleRefresh.flushScheduled(),
384
412
  waitForTitleRefreshIdle: () => titleRefresh.waitForIdle(),
package/dist/tools.js CHANGED
@@ -63,25 +63,16 @@ export function createQuotaSidebarTools(deps) {
63
63
  deps.setTitleEnabled(true);
64
64
  deps.scheduleSave();
65
65
  await deps.flushSave();
66
- const visible = await deps.refreshAllVisibleTitles();
67
- const touched = await deps.refreshAllTouchedTitles();
68
66
  deps.refreshSessionTitle(context.sessionID, 0);
69
67
  if (startupTimedOut) {
70
68
  void deps.waitForStartupTitleWork().then(() => {
71
69
  if (!deps.getTitleEnabled())
72
70
  return;
73
- void deps.refreshAllVisibleTitles();
74
- void deps.refreshAllTouchedTitles();
75
71
  deps.refreshSessionTitle(context.sessionID, 0);
76
72
  });
77
73
  }
78
74
  await deps.showToast('toggle', 'Sidebar usage display: ON');
79
- if (visible.listFailed ||
80
- visible.refreshed < visible.attempted ||
81
- touched.refreshed < touched.attempted) {
82
- return 'Sidebar usage display is now ON. Visible-session refresh failed, so only touched/current session titles are guaranteed to refresh immediately.';
83
- }
84
- return 'Sidebar usage display is now ON. Visible session titles are refreshing to show token usage and quota.';
75
+ return 'Sidebar usage display is now ON. Only assistant-active sessions will refresh shared titles.';
85
76
  }
86
77
  deps.setTitleEnabled(false);
87
78
  deps.scheduleSave();
@@ -96,8 +87,6 @@ export function createQuotaSidebarTools(deps) {
96
87
  deps.setTitleEnabled(true);
97
88
  deps.scheduleSave();
98
89
  await deps.flushSave();
99
- await deps.refreshAllVisibleTitles();
100
- await deps.refreshAllTouchedTitles();
101
90
  deps.refreshSessionTitle(context.sessionID, 0);
102
91
  await deps.showToast('toggle', 'Sidebar usage display: OFF failed');
103
92
  return 'Sidebar usage display remains ON because some touched session titles could not be restored. Try again after the session service recovers.';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leo000001/opencode-quota-sidebar",
3
- "version": "3.0.5",
3
+ "version": "3.0.6",
4
4
  "description": "OpenCode plugin that shows quota and token usage in TUI sidebar panels and compact session titles",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",