@leo000001/opencode-quota-sidebar 2.0.20 → 2.0.23

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
@@ -65,8 +65,11 @@ Want to add support for another provider (Google Antigravity, Zhipu AI, Firmware
65
65
  - short windows (`5h`, `1d`, `Daily`) still show same-day resets as `HH:MM` and cross-day resets as `MM-DD HH:MM`; longer windows continue to show `MM-DD`
66
66
  - long quota content wraps across extra compact lines instead of dropping fields from the sidebar, and continuation lines align to the quota content column
67
67
  - Desktop automatically switches to a compact monitoring-style single-line title. It keeps recently used providers from the last `50` requests or last `60` minutes, expands all windows/balance for those selected providers in short form such as `OAI 5h80 R16:20 W70 R04-03` or `RC D88.9/60 B260`, and keeps only summary usage signals such as `Cd66%` and `Est$0.12`
68
- - TUI is always rendered as compact multiline; Desktop is always rendered as compact monitoring-style single-line. This behavior no longer depends on `sidebar.multilineTitle`
69
- - Web UI currently cannot be reliably detected by the plugin, so it follows the non-desktop multiline path
68
+ - Auto mode now prefers compact single-line titles everywhere except the actively selected TUI session. That means Desktop stays compact, Web UI / `serve` clients also use compact single-line titles, and the current TUI session keeps the compact multiline layout.
69
+ - `sidebar.titleMode` can force `auto`, `multiline`, or `compact` if the heuristic does not match your workflow.
70
+ - Auto-mode signals are intentionally minimal: Desktop is detected only from `OPENCODE_CLIENT=desktop`; TUI ownership comes from `tui.session.select`; `tui.command.execute` and `tui.prompt.append` only keep that last selected TUI session fresh.
71
+ - Auto-mode freshness lasts `15` minutes. If no new TUI activity arrives in that window, the tracked TUI session is refreshed back to compact; the next TUI activity re-enables multiline for the last selected TUI session.
72
+ - Multi-client caveat: the plugin tracks one global TUI-selected session and writes one shared `session.title`. In mixed TUI/Web or multi-TUI setups, the latest TUI selection wins for that session title. Use `sidebar.titleMode="compact"` or `"multiline"` if you need a stable forced policy.
70
73
  - Session-scoped usage/quota can include descendant subagent sessions (enabled by default via `sidebar.includeChildren=true`). Traversal is bounded by `childrenMaxDepth` (default 6), `childrenMaxSessions` (default 128), and `childrenConcurrency` (default 5); truncation is logged when `OPENCODE_QUOTA_DEBUG=1`. Day/week/month ranges never merge children — only session scope does.
71
74
  - Toast message can include four sections: `Token Usage`, `Cost as API` (per provider), `Provider Cache` (when provider-level cached ratios are available), and `Quota`
72
75
  - Expiry reminders are shown in a separate `Expiry Soon` toast section only for providers with real subscription expiry timestamps, and each session shows that auto-reminder at most once
@@ -249,7 +252,8 @@ Sidebar defaults:
249
252
 
250
253
  - `sidebar.enabled`: `true`
251
254
  - `sidebar.width`: `36` (clamped to `20`-`60`)
252
- - `sidebar.multilineTitle`: `true` (legacy compatibility field; TUI/Desktop display mode is now chosen automatically)
255
+ - `sidebar.titleMode`: `auto` (`auto`/`multiline`/`compact`)
256
+ - `sidebar.multilineTitle`: `true` (legacy compatibility field; title style is now chosen automatically)
253
257
  - `sidebar.showCost`: `true`
254
258
  - `sidebar.showQuota`: `true`
255
259
  - `sidebar.wrapQuotaLines`: `true`
@@ -257,8 +261,8 @@ Sidebar defaults:
257
261
  - `sidebar.childrenMaxDepth`: `6` (clamped to `1`-`32`)
258
262
  - `sidebar.childrenMaxSessions`: `128` (clamped to `0`-`2000`)
259
263
  - `sidebar.childrenConcurrency`: `5` (clamped to `1`-`10`)
260
- - `sidebar.desktopCompact.recentRequests`: `50` (desktop compact only)
261
- - `sidebar.desktopCompact.recentMinutes`: `60` (desktop compact only)
264
+ - `sidebar.desktopCompact.recentRequests`: `50` (compact single-line titles)
265
+ - `sidebar.desktopCompact.recentMinutes`: `60` (compact single-line titles)
262
266
 
263
267
  Quota defaults:
264
268
 
@@ -282,6 +286,7 @@ Other defaults:
282
286
  "sidebar": {
283
287
  "enabled": true,
284
288
  "width": 36,
289
+ "titleMode": "auto",
285
290
  "multilineTitle": true,
286
291
  "showCost": true,
287
292
  "showQuota": true,
@@ -323,13 +328,18 @@ Other defaults:
323
328
  - `sidebar.showCost` controls API-cost visibility in sidebar title, `quota_summary` markdown report, and toast message.
324
329
  - `quota_summary` follows the same reset compaction rules for short windows in its subscription section (`5h` / `1d` / `Daily` show time, long windows show date, RightCode `Exp` stays date-only).
325
330
  - `sidebar.width` is measured in terminal cells. CJK/emoji truncation is best-effort to avoid sidebar overflow.
326
- - `sidebar.multilineTitle` is kept for backward compatibility, but current rendering is fixed by client type: TUI uses multiline titles and Desktop uses compact monitoring-style single-line titles.
331
+ - `sidebar.titleMode` defaults to `auto`: Desktop is compact, the actively selected TUI session stays multiline, and everything else (including Web UI / `serve` clients) uses the compact single-line layout. Use `multiline` or `compact` to force one style.
332
+ - `auto` relies on positive TUI signals: `tui.session.select` chooses the tracked TUI session, and recent `tui.command.execute` / `tui.prompt.append` activity only refreshes that tracked session. If no `tui.session.select` has been seen, command/prompt activity alone will not promote a session to multiline.
333
+ - The TUI freshness window is `15` minutes. After that timeout, the tracked TUI session is refreshed back to compact; the next TUI activity re-promotes only the last selected TUI session.
334
+ - The plugin tracks one global `tuiSessionID` and one shared session title per plugin process, not one title per window/tab/client. In multi-client or shared-server setups, different TUI/Web viewers can influence each other. If you need predictable rendering, force `sidebar.titleMode`.
335
+ - `sidebar.multilineTitle` is kept for backward compatibility, but `sidebar.titleMode` now controls the active policy.
327
336
  - `sidebar.wrapQuotaLines` controls quota line wrapping and continuation indentation (default: `true`).
328
337
  - `sidebar.includeChildren` controls whether session-scoped usage/quota includes descendant subagent sessions (default: `true`).
329
338
  - `sidebar.childrenMaxDepth` limits how many levels of nested subagents are traversed (default: `6`, clamped 1–32).
330
339
  - `sidebar.childrenMaxSessions` caps the total number of descendant sessions aggregated (default: `128`, clamped 0–2000).
331
340
  - `sidebar.childrenConcurrency` controls parallel fetches for descendant session messages (default: `5`, clamped 1–10).
332
- - `sidebar.desktopCompact.recentRequests` and `sidebar.desktopCompact.recentMinutes` control which recently used providers remain visible in desktop compact titles.
341
+ - `sidebar.desktopCompact.recentRequests` and `sidebar.desktopCompact.recentMinutes` control which recently used providers remain visible in compact single-line titles.
342
+ - `sidebar.desktopCompact.recentRequests` and `sidebar.desktopCompact.recentMinutes` only control provider filtering inside compact titles; they do not change TUI detection or the 15-minute freshness timeout.
333
343
  - `output` includes reasoning tokens (`output = tokens.output + tokens.reasoning`). Reasoning is not rendered as a separate line.
334
344
  - API cost bills reasoning tokens at the output rate (same as completion tokens).
335
345
  - API cost is computed from OpenCode model pricing metadata, not from `message.cost`. This keeps subscription-backed providers such as OpenAI OAuth usable for API-equivalent cost estimation even when OpenCode's measured cost is `0`.
@@ -368,7 +378,27 @@ These examples show the quota block portion of the sidebar title.
368
378
 
369
379
  ### TUI layout
370
380
 
371
- This section describes the TUI layout. Desktop uses its own compact monitoring-style single-line format and Web UI currently follows the multiline path.
381
+ This section describes the multiline TUI layout. Desktop and Web UI / `serve` clients use the compact single-line format unless you force `sidebar.titleMode = "multiline"`.
382
+
383
+ ### Force modes
384
+
385
+ If you prefer a stable policy instead of the auto heuristic:
386
+
387
+ ```json
388
+ {
389
+ "sidebar": {
390
+ "titleMode": "compact"
391
+ }
392
+ }
393
+ ```
394
+
395
+ ```json
396
+ {
397
+ "sidebar": {
398
+ "titleMode": "multiline"
399
+ }
400
+ }
401
+ ```
372
402
 
373
403
  0 providers (no quota data):
374
404
 
package/dist/events.d.ts CHANGED
@@ -3,6 +3,8 @@ export declare function createEventDispatcher(handlers: {
3
3
  onSessionCreated: (session: Session) => Promise<void>;
4
4
  onSessionUpdated: (session: Session) => Promise<void>;
5
5
  onSessionDeleted: (session: Session) => Promise<void>;
6
+ onTuiActivity: () => Promise<void>;
7
+ onTuiSessionSelect: (sessionID: string) => Promise<void>;
6
8
  onMessageRemoved: (info: {
7
9
  sessionID: string;
8
10
  messageID?: string;
package/dist/events.js CHANGED
@@ -3,6 +3,7 @@ function isAssistantMessage(message) {
3
3
  }
4
4
  export function createEventDispatcher(handlers) {
5
5
  return async (event) => {
6
+ const tui = event;
6
7
  if (event.type === 'session.created') {
7
8
  await handlers.onSessionCreated(event.properties.info);
8
9
  return;
@@ -15,6 +16,17 @@ export function createEventDispatcher(handlers) {
15
16
  await handlers.onSessionDeleted(event.properties.info);
16
17
  return;
17
18
  }
19
+ if (tui.type === 'tui.prompt.append' || tui.type === 'tui.command.execute') {
20
+ await handlers.onTuiActivity();
21
+ return;
22
+ }
23
+ if (tui.type === 'tui.session.select') {
24
+ if (typeof tui.properties?.sessionID !== 'string')
25
+ return;
26
+ await handlers.onTuiSessionSelect(tui.properties.sessionID);
27
+ await handlers.onTuiActivity();
28
+ return;
29
+ }
18
30
  if (event.type === 'message.removed') {
19
31
  const props = event.properties;
20
32
  await handlers.onMessageRemoved({
package/dist/format.d.ts CHANGED
@@ -1,6 +1,15 @@
1
1
  import type { QuotaSidebarConfig, QuotaSnapshot } from './types.js';
2
2
  import { type UsageSummary } from './usage.js';
3
+ export type TitleView = 'multiline' | 'compact';
4
+ export declare const TUI_ACTIVE_MS: number;
3
5
  export declare function isDesktopClient(): boolean;
6
+ export declare function resolveTitleView(opts: {
7
+ config: QuotaSidebarConfig;
8
+ sessionID?: string;
9
+ tuiSessionID?: string;
10
+ tuiActiveAt?: number;
11
+ now?: number;
12
+ }): TitleView;
4
13
  export declare function selectDesktopCompactProviderIDs(usage: UsageSummary, config: QuotaSidebarConfig, now?: number): string[];
5
14
  /**
6
15
  * Render sidebar title with multi-line token breakdown.
@@ -13,7 +22,7 @@ export declare function selectDesktopCompactProviderIDs(usage: UsageSummary, con
13
22
  * $3.81 as API cost (only if showCost=true)
14
23
  * OpenAI Remaining 78% (only if quota available)
15
24
  */
16
- export declare function renderSidebarTitle(baseTitle: string, usage: UsageSummary, quotas: QuotaSnapshot[], config: QuotaSidebarConfig): string;
25
+ export declare function renderSidebarTitle(baseTitle: string, usage: UsageSummary, quotas: QuotaSnapshot[], config: QuotaSidebarConfig, view?: TitleView): string;
17
26
  export declare function renderMarkdownReport(period: string, usage: UsageSummary, quotas: QuotaSnapshot[], options?: {
18
27
  showCost?: boolean;
19
28
  }): string;
package/dist/format.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { getCacheCoverageMetrics, getProviderCacheCoverageMetrics, } from './usage.js';
2
2
  import { canonicalProviderID, collapseQuotaSnapshots, displayShortLabel, quotaDisplayLabel, } from './quota_render.js';
3
3
  import { stripAnsi } from './title.js';
4
+ export const TUI_ACTIVE_MS = 15 * 60_000;
4
5
  /** M6 fix: handle negative, NaN, Infinity gracefully. */
5
6
  function shortNumber(value, decimals = 1) {
6
7
  if (!Number.isFinite(value) || value < 0)
@@ -163,6 +164,20 @@ function formatRequestsLabel(value, short = false) {
163
164
  export function isDesktopClient() {
164
165
  return process.env.OPENCODE_CLIENT === 'desktop';
165
166
  }
167
+ export function resolveTitleView(opts) {
168
+ if (opts.config.sidebar.titleMode === 'compact')
169
+ return 'compact';
170
+ if (opts.config.sidebar.titleMode === 'multiline')
171
+ return 'multiline';
172
+ if (isDesktopClient())
173
+ return 'compact';
174
+ if (opts.sessionID &&
175
+ opts.sessionID === opts.tuiSessionID &&
176
+ (opts.now ?? Date.now()) - (opts.tuiActiveAt ?? 0) <= TUI_ACTIVE_MS) {
177
+ return 'multiline';
178
+ }
179
+ return 'compact';
180
+ }
166
181
  function desktopCompactSettings(config) {
167
182
  return {
168
183
  recentRequests: Math.max(1, config.sidebar.desktopCompact?.recentRequests ?? 50),
@@ -462,10 +477,11 @@ function renderSingleLineTitle(baseTitle, usage, quotas, config, width) {
462
477
  * $3.81 as API cost (only if showCost=true)
463
478
  * OpenAI Remaining 78% (only if quota available)
464
479
  */
465
- export function renderSidebarTitle(baseTitle, usage, quotas, config) {
480
+ export function renderSidebarTitle(baseTitle, usage, quotas, config, view) {
466
481
  const width = Math.max(8, Math.floor(config.sidebar.width || 36));
467
482
  const safeBaseTitle = stripAnsi(baseTitle || 'Session') || 'Session';
468
- if (isDesktopClient()) {
483
+ const mode = view || resolveTitleView({ config });
484
+ if (mode === 'compact') {
469
485
  const singleLineBase = safeBaseTitle.split(/\r?\n/, 1)[0] || 'Session';
470
486
  return renderDesktopCompactTitle(singleLineBase, usage, quotas, config, width);
471
487
  }
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path';
2
- import { renderMarkdownReport, renderSidebarTitle, renderToastMessage, } from './format.js';
2
+ import { renderMarkdownReport, resolveTitleView, renderSidebarTitle, renderToastMessage, TUI_ACTIVE_MS, } from './format.js';
3
3
  import { createQuotaRuntime } from './quota.js';
4
4
  import { authFilePath, dateKeyFromTimestamp, deleteSessionFromDayChunk, evictOldSessions, loadConfig, loadState, normalizeTimestampMs, resolveOpencodeConfigDir, resolveOpencodeDataDir, saveState, stateFilePath, } from './storage.js';
5
5
  import { debug, swallow } from './helpers.js';
@@ -126,6 +126,23 @@ export async function QuotaSidebarPlugin(input) {
126
126
  });
127
127
  const summarizeSessionUsageForDisplay = usageService.summarizeSessionUsageForDisplay;
128
128
  const summarizeForTool = usageService.summarizeForTool;
129
+ let tuiSessionID;
130
+ let tuiActiveAt = 0;
131
+ let tuiTimer;
132
+ const armTuiTimer = () => {
133
+ if (tuiTimer)
134
+ clearTimeout(tuiTimer);
135
+ if (!tuiSessionID)
136
+ return;
137
+ tuiTimer = setTimeout(() => {
138
+ const id = tuiSessionID;
139
+ tuiActiveAt = 0;
140
+ tuiTimer = undefined;
141
+ if (id)
142
+ scheduleTitleRefresh(id, 0);
143
+ }, TUI_ACTIVE_MS);
144
+ tuiTimer.unref?.();
145
+ };
129
146
  // title apply / refresh lifecycle
130
147
  let scheduleTitleRefresh = (sessionID, delay = 250) => {
131
148
  void sessionID;
@@ -161,6 +178,7 @@ export async function QuotaSidebarPlugin(input) {
161
178
  markDirty,
162
179
  scheduleSave,
163
180
  renderSidebarTitle,
181
+ getTitleView: (sessionID) => resolveTitleView({ config, sessionID, tuiSessionID, tuiActiveAt }),
164
182
  getQuotaSnapshots,
165
183
  summarizeSessionUsageForDisplay,
166
184
  scheduleParentRefreshIfSafe,
@@ -200,6 +218,8 @@ export async function QuotaSidebarPlugin(input) {
200
218
  .catch(swallow('startup:refreshAllTouchedTitles'));
201
219
  }
202
220
  const shutdown = async () => {
221
+ if (tuiTimer)
222
+ clearTimeout(tuiTimer);
203
223
  await Promise.race([
204
224
  startupTitleWork,
205
225
  new Promise((resolve) => setTimeout(resolve, 5_000)),
@@ -327,6 +347,14 @@ export async function QuotaSidebarPlugin(input) {
327
347
  },
328
348
  onSessionDeleted: async (session) => {
329
349
  await flushSave().catch(swallow('onSessionDeleted:flushSave'));
350
+ if (tuiSessionID === session.id) {
351
+ tuiSessionID = undefined;
352
+ tuiActiveAt = 0;
353
+ if (tuiTimer) {
354
+ clearTimeout(tuiTimer);
355
+ tuiTimer = undefined;
356
+ }
357
+ }
330
358
  descendantsResolver.invalidateForAncestors(session.parentID);
331
359
  descendantsResolver.invalidateForAncestors(session.id);
332
360
  usageService.forgetSession(session.id);
@@ -348,6 +376,23 @@ export async function QuotaSidebarPlugin(input) {
348
376
  titleRefresh.schedule(session.parentID, 0);
349
377
  }
350
378
  },
379
+ onTuiActivity: async () => {
380
+ const stale = Boolean(tuiSessionID) && Date.now() - tuiActiveAt > TUI_ACTIVE_MS;
381
+ tuiActiveAt = Date.now();
382
+ armTuiTimer();
383
+ if (stale && tuiSessionID) {
384
+ titleRefresh.schedule(tuiSessionID, 0);
385
+ }
386
+ },
387
+ onTuiSessionSelect: async (sessionID) => {
388
+ const prev = tuiSessionID;
389
+ tuiSessionID = sessionID;
390
+ armTuiTimer();
391
+ if (prev && prev !== sessionID) {
392
+ titleRefresh.schedule(prev, 0);
393
+ }
394
+ titleRefresh.schedule(sessionID, 0);
395
+ },
351
396
  onMessageRemoved: async (info) => {
352
397
  usageService.markForceRescan(info.sessionID);
353
398
  titleRefresh.schedule(info.sessionID, 0);
package/dist/storage.js CHANGED
@@ -11,6 +11,7 @@ export const defaultConfig = {
11
11
  sidebar: {
12
12
  enabled: true,
13
13
  width: 36,
14
+ titleMode: 'auto',
14
15
  multilineTitle: true,
15
16
  showCost: true,
16
17
  showQuota: true,
@@ -88,6 +89,11 @@ export async function loadConfig(paths) {
88
89
  sidebar: {
89
90
  enabled: asBoolean(sidebar.enabled, base.sidebar.enabled),
90
91
  width: Math.max(20, Math.min(60, asNumber(sidebar.width, base.sidebar.width))),
92
+ titleMode: sidebar.titleMode === 'compact' ||
93
+ sidebar.titleMode === 'multiline' ||
94
+ sidebar.titleMode === 'auto'
95
+ ? sidebar.titleMode
96
+ : (base.sidebar.titleMode ?? 'auto'),
91
97
  multilineTitle: asBoolean(sidebar.multilineTitle, base.sidebar.multilineTitle ?? true),
92
98
  showCost: asBoolean(sidebar.showCost, base.sidebar.showCost),
93
99
  showQuota: asBoolean(sidebar.showQuota, base.sidebar.showQuota),
@@ -1,6 +1,7 @@
1
1
  import type { PluginInput } from '@opencode-ai/plugin';
2
2
  import type { QuotaSidebarConfig, QuotaSidebarState, QuotaSnapshot, SessionState } from './types.js';
3
3
  import type { UsageSummary } from './usage.js';
4
+ import { type TitleView } from './format.js';
4
5
  export declare function createTitleApplicator(deps: {
5
6
  state: QuotaSidebarState;
6
7
  config: QuotaSidebarConfig;
@@ -9,7 +10,8 @@ export declare function createTitleApplicator(deps: {
9
10
  ensureSessionState: (sessionID: string, title: string, createdAt: number, parentID?: string | null) => SessionState;
10
11
  markDirty: (dateKey: string | undefined) => void;
11
12
  scheduleSave: () => void;
12
- renderSidebarTitle: (baseTitle: string, usage: UsageSummary, quotas: QuotaSnapshot[], config: QuotaSidebarConfig) => string;
13
+ renderSidebarTitle: (baseTitle: string, usage: UsageSummary, quotas: QuotaSnapshot[], config: QuotaSidebarConfig, view?: TitleView) => string;
14
+ getTitleView?: (sessionID: string) => TitleView;
13
15
  getQuotaSnapshots: (providerIDs: string[], options?: {
14
16
  allowDefault?: boolean;
15
17
  }) => Promise<QuotaSnapshot[]>;
@@ -1,6 +1,6 @@
1
1
  import { canonicalizeTitle, canonicalizeTitleForCompare, looksDecorated, normalizeBaseTitle, } from './title.js';
2
2
  import { swallow, debug, mapConcurrent } from './helpers.js';
3
- import { isDesktopClient, selectDesktopCompactProviderIDs } from './format.js';
3
+ import { resolveTitleView, selectDesktopCompactProviderIDs, } from './format.js';
4
4
  export function createTitleApplicator(deps) {
5
5
  const pendingAppliedTitle = new Map();
6
6
  const recentRestore = new Map();
@@ -73,13 +73,15 @@ export function createTitleApplicator(deps) {
73
73
  }
74
74
  }
75
75
  const usage = await deps.summarizeSessionUsageForDisplay(sessionID, deps.config.sidebar.includeChildren);
76
- const quotaProviders = Array.from(new Set(isDesktopClient()
76
+ const view = deps.getTitleView?.(sessionID) ??
77
+ resolveTitleView({ config: deps.config, sessionID });
78
+ const quotaProviders = Array.from(new Set(view === 'compact'
77
79
  ? selectDesktopCompactProviderIDs(usage, deps.config)
78
80
  : Object.keys(usage.providers)));
79
81
  const quotas = deps.config.sidebar.showQuota && quotaProviders.length > 0
80
82
  ? await deps.getQuotaSnapshots(quotaProviders)
81
83
  : [];
82
- const nextTitle = deps.renderSidebarTitle(sessionState.baseTitle, usage, quotas, deps.config);
84
+ const nextTitle = deps.renderSidebarTitle(sessionState.baseTitle, usage, quotas, deps.config, view);
83
85
  if (!deps.config.sidebar.enabled || !deps.state.titleEnabled)
84
86
  return false;
85
87
  if (canonicalizeTitleForCompare(nextTitle) ===
package/dist/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export type QuotaStatus = 'ok' | 'unavailable' | 'unsupported' | 'error';
2
+ export type SidebarTitleMode = 'auto' | 'multiline' | 'compact';
2
3
  export type QuotaWindow = {
3
4
  label: string;
4
5
  /** Set false when this window line should not render a trailing percentage. */
@@ -148,6 +149,11 @@ export type QuotaSidebarConfig = {
148
149
  sidebar: {
149
150
  enabled: boolean;
150
151
  width: number;
152
+ /**
153
+ * `auto`: compact by default, but keep the actively selected TUI session
154
+ * multiline when the plugin can positively identify it.
155
+ */
156
+ titleMode?: SidebarTitleMode;
151
157
  /**
152
158
  * Legacy switch retained for compatibility.
153
159
  * TUI keeps a compact multiline sidebar layout; Desktop keeps a compact
@@ -166,7 +172,7 @@ export type QuotaSidebarConfig = {
166
172
  childrenMaxSessions: number;
167
173
  /** Concurrency for fetching descendant session messages (bounded). */
168
174
  childrenConcurrency: number;
169
- /** Desktop-only compact title selection window by request count/time. */
175
+ /** Compact single-line title selection window by request count/time. */
170
176
  desktopCompact?: {
171
177
  recentRequests?: number;
172
178
  recentMinutes?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leo000001/opencode-quota-sidebar",
3
- "version": "2.0.20",
3
+ "version": "2.0.23",
4
4
  "description": "OpenCode plugin that shows quota and token usage in session titles",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -2,6 +2,7 @@
2
2
  "sidebar": {
3
3
  "enabled": true,
4
4
  "width": 36,
5
+ "titleMode": "auto",
5
6
  "multilineTitle": true,
6
7
  "showCost": true,
7
8
  "showQuota": true,