@pellux/goodvibes-agent 1.4.4 → 1.5.0

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.
Files changed (227) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +7 -7
  3. package/dist/package/main.js +7424 -12241
  4. package/docs/README.md +2 -2
  5. package/docs/channels-remote-and-api.md +1 -1
  6. package/docs/getting-started.md +2 -2
  7. package/docs/release-and-publishing.md +1 -1
  8. package/docs/tools-and-commands.md +5 -5
  9. package/package.json +4 -2
  10. package/release/performance-snapshot.json +2 -2
  11. package/release/release-notes.md +11 -7
  12. package/release/release-readiness.json +6 -6
  13. package/src/agent/behavior-discovery-summary.ts +4 -18
  14. package/src/agent/calendar-registry.ts +322 -0
  15. package/src/agent/competitive-feature-inventory.ts +268 -130
  16. package/src/agent/document-registry.ts +5 -1
  17. package/src/agent/email/email-service.ts +350 -0
  18. package/src/agent/email/imap-client.ts +596 -0
  19. package/src/agent/email/smtp-client.ts +453 -0
  20. package/src/agent/ics-calendar.ts +662 -0
  21. package/src/agent/ics-timezone.ts +172 -0
  22. package/src/agent/markdown-frontmatter.ts +31 -0
  23. package/src/agent/memory-safety.ts +1 -1
  24. package/src/agent/note-registry.ts +3 -9
  25. package/src/agent/persona-discovery.ts +3 -15
  26. package/src/agent/persona-registry.ts +1 -10
  27. package/src/agent/research-source-registry.ts +5 -1
  28. package/src/agent/routine-discovery.ts +3 -15
  29. package/src/agent/runtime-profile.ts +3 -0
  30. package/src/agent/skill-discovery.ts +3 -15
  31. package/src/agent/skill-draft-proposer.ts +203 -0
  32. package/src/agent/skill-draft-runner.ts +201 -0
  33. package/src/agent/skill-registry.ts +36 -1
  34. package/src/agent/skill-standard.ts +99 -0
  35. package/src/agent/vibe-file.ts +7 -22
  36. package/src/cli/completion.ts +1 -1
  37. package/src/cli/config-overrides.ts +10 -1
  38. package/src/cli/redaction.ts +17 -5
  39. package/src/config/provider-model.ts +2 -1
  40. package/src/config/secret-config.ts +13 -6
  41. package/src/core/activity-feed.ts +97 -0
  42. package/src/core/away-digest.ts +161 -0
  43. package/src/core/conversation-rendering.ts +7 -3
  44. package/src/core/conversation.ts +22 -15
  45. package/src/core/hardware-profile.ts +362 -0
  46. package/src/core/last-seen-store.ts +78 -0
  47. package/src/core/plain-language.ts +52 -0
  48. package/src/core/setup-incomplete-hint.ts +90 -0
  49. package/src/core/system-message-router.ts +38 -87
  50. package/src/input/agent-workspace-basic-command-editor-submission.ts +60 -220
  51. package/src/input/agent-workspace-basic-command-editors.ts +39 -141
  52. package/src/input/agent-workspace-categories.ts +40 -125
  53. package/src/input/agent-workspace-command-editor.ts +4 -0
  54. package/src/input/agent-workspace-host-category.ts +0 -5
  55. package/src/input/agent-workspace-local-editor-submission.ts +3 -1
  56. package/src/input/agent-workspace-navigation.ts +10 -3
  57. package/src/input/agent-workspace-onboarding-actions.ts +132 -0
  58. package/src/input/agent-workspace-onboarding-categories.ts +29 -26
  59. package/src/input/agent-workspace-onboarding-finish.ts +11 -4
  60. package/src/input/agent-workspace-onboarding-state.ts +111 -0
  61. package/src/input/agent-workspace-profile-editor-submission.ts +260 -0
  62. package/src/input/agent-workspace-profile-editors.ts +155 -0
  63. package/src/input/agent-workspace-subscription-editor.ts +7 -0
  64. package/src/input/agent-workspace-types.ts +5 -1
  65. package/src/input/agent-workspace.ts +65 -39
  66. package/src/input/command-registry.ts +18 -5
  67. package/src/input/commands/agent-runtime-profile-runtime.ts +2 -1
  68. package/src/input/commands/agent-skills-runtime.ts +60 -3
  69. package/src/input/commands/calendar-runtime.ts +209 -0
  70. package/src/input/commands/channels-runtime.ts +1 -0
  71. package/src/input/commands/command-error.ts +9 -0
  72. package/src/input/commands/compat-runtime.ts +1 -0
  73. package/src/input/commands/config.ts +1 -0
  74. package/src/input/commands/conversation-runtime.ts +1 -1
  75. package/src/input/commands/delegation-runtime.ts +5 -6
  76. package/src/input/commands/email-runtime.ts +387 -0
  77. package/src/input/commands/experience-runtime.ts +17 -4
  78. package/src/input/commands/guidance-runtime.ts +1 -1
  79. package/src/input/commands/health-runtime.ts +6 -1
  80. package/src/input/commands/knowledge-format.ts +73 -0
  81. package/src/input/commands/knowledge.ts +9 -74
  82. package/src/input/commands/local-provider-runtime.ts +2 -1
  83. package/src/input/commands/local-runtime.ts +10 -4
  84. package/src/input/commands/mcp-runtime.ts +7 -0
  85. package/src/input/commands/notify-runtime.ts +17 -1
  86. package/src/input/commands/onboarding-runtime.ts +1 -0
  87. package/src/input/commands/operator-actions-runtime.ts +1 -0
  88. package/src/input/commands/operator-runtime.ts +3 -0
  89. package/src/input/commands/personas-runtime.ts +1 -0
  90. package/src/input/commands/platform-access-runtime.ts +40 -17
  91. package/src/input/commands/product-runtime.ts +6 -1
  92. package/src/input/commands/provider-accounts-runtime.ts +3 -2
  93. package/src/input/commands/qrcode-runtime.ts +1 -0
  94. package/src/input/commands/routines-runtime.ts +1 -0
  95. package/src/input/commands/runtime-services.ts +0 -13
  96. package/src/input/commands/security-runtime.ts +1 -0
  97. package/src/input/commands/session-content.ts +1 -0
  98. package/src/input/commands/shell-core.ts +5 -2
  99. package/src/input/commands/subscription-runtime.ts +33 -9
  100. package/src/input/commands/support-bundle-runtime.ts +8 -1
  101. package/src/input/commands/tasks-runtime.ts +1 -0
  102. package/src/input/commands/tts-runtime.ts +1 -0
  103. package/src/input/commands/vibe-runtime.ts +1 -0
  104. package/src/input/commands.ts +4 -0
  105. package/src/input/feed-context-factory.ts +3 -12
  106. package/src/input/handler-command-route.ts +7 -60
  107. package/src/input/handler-feed-routes.ts +0 -194
  108. package/src/input/handler-feed.ts +2 -54
  109. package/src/input/handler-interactions.ts +0 -2
  110. package/src/input/handler-modal-stack.ts +0 -9
  111. package/src/input/handler-picker-routes.ts +24 -1
  112. package/src/input/handler-shortcuts.ts +11 -40
  113. package/src/input/handler-ui-state.ts +13 -0
  114. package/src/input/handler.ts +8 -35
  115. package/src/input/keybindings.ts +40 -17
  116. package/src/input/model-picker-local-fit.ts +130 -0
  117. package/src/input/submission-router.ts +0 -5
  118. package/src/main.ts +111 -89
  119. package/src/renderer/activity-sidebar.ts +186 -0
  120. package/src/renderer/agent-workspace-context-lines.ts +5 -48
  121. package/src/renderer/agent-workspace-style.ts +1 -1
  122. package/src/renderer/agent-workspace.ts +14 -2
  123. package/src/renderer/compositor.ts +37 -125
  124. package/src/renderer/conversation-overlays.ts +3 -3
  125. package/src/renderer/help-overlay.ts +7 -5
  126. package/src/renderer/model-workspace.ts +101 -86
  127. package/src/{panels → renderer}/polish.ts +3 -3
  128. package/src/renderer/shell-surface.ts +4 -5
  129. package/src/renderer/status-token.ts +31 -11
  130. package/src/renderer/tab-strip.ts +1 -1
  131. package/src/renderer/tool-call.ts +7 -8
  132. package/src/renderer/tool-labels.ts +92 -0
  133. package/src/renderer/ui-factory.ts +48 -96
  134. package/src/runtime/bootstrap-command-context.ts +0 -5
  135. package/src/runtime/bootstrap-command-parts.ts +6 -15
  136. package/src/runtime/bootstrap-core.ts +34 -13
  137. package/src/runtime/bootstrap-hook-bridge.ts +3 -1
  138. package/src/runtime/bootstrap-shell.ts +3 -50
  139. package/src/runtime/bootstrap.ts +3 -4
  140. package/src/runtime/context.ts +1 -1
  141. package/src/runtime/diagnostics/panels/index.ts +0 -1
  142. package/src/runtime/diagnostics/panels/policy.ts +1 -1
  143. package/src/runtime/execution-ledger.ts +16 -1
  144. package/src/runtime/index.ts +6 -1
  145. package/src/runtime/onboarding/index.ts +1 -0
  146. package/src/runtime/onboarding/onboarding-state.ts +200 -0
  147. package/src/{panels → runtime}/provider-account-snapshot.ts +5 -2
  148. package/src/runtime/services.ts +16 -4
  149. package/src/runtime/terminal-output-guard.ts +7 -0
  150. package/src/runtime/tool-permission-safety.ts +1 -1
  151. package/src/runtime/ui/model-picker/data-provider.ts +1 -1
  152. package/src/runtime/ui/model-picker/health-enrichment.ts +2 -2
  153. package/src/runtime/ui/model-picker/types.ts +2 -2
  154. package/src/runtime/ui/provider-health/data-provider.ts +3 -3
  155. package/src/runtime/ui/provider-health/types.ts +2 -2
  156. package/src/runtime/ui-services.ts +0 -2
  157. package/src/shell/agent-workspace-fullscreen.ts +0 -1
  158. package/src/shell/autonomy-surfacing.ts +272 -0
  159. package/src/shell/session-continuity-hints.ts +0 -6
  160. package/src/shell/startup-wiring.ts +221 -0
  161. package/src/shell/ui-openers.ts +18 -29
  162. package/src/tools/agent-context-policy.ts +1 -1
  163. package/src/tools/agent-harness-background-processes.ts +4 -4
  164. package/src/tools/agent-harness-browser-cockpit-route.ts +3 -3
  165. package/src/tools/agent-harness-command-runner.ts +6 -1
  166. package/src/tools/agent-harness-document-ops.ts +26 -53
  167. package/src/tools/agent-harness-execution-history.ts +1 -13
  168. package/src/tools/agent-harness-execution-posture.ts +0 -15
  169. package/src/tools/agent-harness-keybinding-metadata.ts +28 -29
  170. package/src/tools/agent-harness-local-model-cookbook.ts +24 -1
  171. package/src/tools/agent-harness-metadata.ts +16 -0
  172. package/src/tools/agent-harness-mode-catalog.ts +2 -9
  173. package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
  174. package/src/tools/agent-harness-setup-posture-utils.ts +1 -1
  175. package/src/tools/agent-harness-tool-schema.ts +13 -14
  176. package/src/tools/agent-harness-tool.ts +27 -23
  177. package/src/tools/agent-harness-ui-surface-metadata.ts +10 -20
  178. package/src/tools/agent-harness-workspace-action-runner.ts +3 -4
  179. package/src/tools/agent-workspace-tool.ts +2 -33
  180. package/src/utils/terminal-width.ts +9 -3
  181. package/src/version.ts +1 -1
  182. package/src/input/agent-workspace-panel-route.ts +0 -44
  183. package/src/input/panel-integration-actions.ts +0 -26
  184. package/src/panels/approval-panel.ts +0 -149
  185. package/src/panels/automation-control-panel.ts +0 -212
  186. package/src/panels/base-panel.ts +0 -254
  187. package/src/panels/builtin/agent.ts +0 -58
  188. package/src/panels/builtin/knowledge.ts +0 -26
  189. package/src/panels/builtin/operations.ts +0 -121
  190. package/src/panels/builtin/session.ts +0 -138
  191. package/src/panels/builtin/shared.ts +0 -275
  192. package/src/panels/builtin/usage.ts +0 -21
  193. package/src/panels/builtin-panels.ts +0 -23
  194. package/src/panels/confirm-state.ts +0 -61
  195. package/src/panels/context-visualizer-panel.ts +0 -204
  196. package/src/panels/cost-tracker-panel.ts +0 -444
  197. package/src/panels/docs-panel.ts +0 -285
  198. package/src/panels/index.ts +0 -25
  199. package/src/panels/knowledge-panel.ts +0 -417
  200. package/src/panels/memory-panel.ts +0 -226
  201. package/src/panels/panel-list-panel.ts +0 -464
  202. package/src/panels/panel-manager.ts +0 -570
  203. package/src/panels/provider-accounts-panel.ts +0 -233
  204. package/src/panels/provider-health-domains.ts +0 -208
  205. package/src/panels/provider-health-panel.ts +0 -720
  206. package/src/panels/provider-health-tracker.ts +0 -115
  207. package/src/panels/provider-stats-panel.ts +0 -366
  208. package/src/panels/qr-panel.ts +0 -207
  209. package/src/panels/schedule-panel.ts +0 -321
  210. package/src/panels/scrollable-list-panel.ts +0 -491
  211. package/src/panels/search-focus.ts +0 -32
  212. package/src/panels/security-panel.ts +0 -295
  213. package/src/panels/session-browser-panel.ts +0 -395
  214. package/src/panels/session-maintenance.ts +0 -125
  215. package/src/panels/subscription-panel.ts +0 -263
  216. package/src/panels/system-messages-panel.ts +0 -230
  217. package/src/panels/tasks-panel.ts +0 -344
  218. package/src/panels/thinking-panel.ts +0 -304
  219. package/src/panels/token-budget-panel.ts +0 -475
  220. package/src/panels/tool-inspector-panel.ts +0 -436
  221. package/src/panels/types.ts +0 -54
  222. package/src/renderer/panel-composite.ts +0 -158
  223. package/src/renderer/panel-tab-bar.ts +0 -69
  224. package/src/renderer/panel-workspace-bar.ts +0 -42
  225. package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
  226. package/src/tools/agent-harness-panel-metadata.ts +0 -211
  227. /package/src/runtime/perf/{panel-health-monitor.ts → component-health.ts} +0 -0
@@ -1,444 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // CostTrackerPanel — per-session / per-agent / per-plan cost estimates
3
- // ---------------------------------------------------------------------------
4
-
5
- import type { Line } from '../types/grid.ts';
6
- import { createStyledCell, createEmptyLine } from '../types/grid.ts';
7
- import { BasePanel } from './base-panel.ts';
8
- import type { AgentEvent, TurnEvent } from '@/runtime/index.ts';
9
- import type { UiEventFeed } from '../runtime/ui-events.ts';
10
- import {
11
- buildEmptyState,
12
- buildPanelLine,
13
- buildStyledPanelLine,
14
- buildPanelWorkspace,
15
- resolveScrollablePanelSection,
16
- DEFAULT_PANEL_PALETTE,
17
- type PanelWorkspaceSection,
18
- } from './polish.ts';
19
-
20
- // ---------------------------------------------------------------------------
21
- // Pricing table (USD per 1M tokens)
22
- // ---------------------------------------------------------------------------
23
-
24
- interface ModelPricing {
25
- input: number;
26
- output: number;
27
- }
28
-
29
- const MODEL_PRICING: Record<string, ModelPricing> = {
30
- // Free tier
31
- 'openrouter/free': { input: 0, output: 0 },
32
-
33
- // InceptionLabs
34
- 'mercury-2': { input: 0.50, output: 1.50 },
35
- 'mercury-edit': { input: 0.50, output: 1.50 },
36
-
37
- // OpenAI
38
- 'gpt-5.4': { input: 5, output: 15 },
39
- 'gpt-5.3-chat-latest': { input: 3, output: 10 },
40
- 'gpt-5-mini': { input: 0.15, output: 0.60 },
41
- 'gpt-5-nano': { input: 0.05, output: 0.20 },
42
- 'gpt-oss-120b': { input: 0, output: 0 },
43
-
44
- // Anthropic (correct registry IDs)
45
- 'claude-opus-4-6': { input: 15, output: 75 },
46
- 'claude-sonnet-4-6': { input: 3, output: 15 },
47
- 'claude-haiku-4-5': { input: 0.80, output: 4 },
48
-
49
- // Google
50
- 'gemini-3.1-pro': { input: 1.25, output: 5 },
51
- 'gemini-3-flash': { input: 0.075, output: 0.30 },
52
- 'gemini-3.1-flash-lite': { input: 0.02, output: 0.10 },
53
- 'gemini-2.5-pro': { input: 1.25, output: 5 },
54
- };
55
-
56
- /**
57
- * Look up pricing from the model catalog.
58
- * Returns { input: 0, output: 0 } for free models and unknown models.
59
- */
60
- function getCostFromCatalogForPanel(modelId: string): ModelPricing {
61
- if (modelId.endsWith(':free')) return { input: 0, output: 0 };
62
- return { input: 0, output: 0 };
63
- }
64
-
65
- function getPricing(modelId: string): ModelPricing {
66
- // 2. Hardcoded table — exact match
67
- if (MODEL_PRICING[modelId]) return MODEL_PRICING[modelId]!;
68
- // 1. OpenRouter :free suffix — treat as free
69
- if (modelId.endsWith(':free')) return { input: 0, output: 0 };
70
- // 2. Prefix match (e.g. "openrouter/free:..." or "claude-sonnet-4-6-20..")
71
- for (const [key, pricing] of Object.entries(MODEL_PRICING)) {
72
- if (modelId.startsWith(key) || modelId.includes(key)) return pricing;
73
- }
74
- // 3. Unknown model — default to free-ish safe fallback
75
- return getCostFromCatalogForPanel(modelId);
76
- }
77
-
78
- function calcCost(inputTokens: number, outputTokens: number, pricing: ModelPricing): number {
79
- return (inputTokens * pricing.input + outputTokens * pricing.output) / 1_000_000;
80
- }
81
-
82
- function formatCost(usd: number): string {
83
- if (usd === 0) return '$0.00';
84
- if (usd < 0.0001) return '<$0.0001';
85
- if (usd < 0.01) return `$${usd.toFixed(4)}`;
86
- return `$${usd.toFixed(3)}`;
87
- }
88
-
89
- function formatTokens(n: number): string {
90
- if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
91
- if (n >= 1_000) return `${(n / 1_000).toFixed(1)}k`;
92
- return String(n);
93
- }
94
-
95
- // ---------------------------------------------------------------------------
96
- // Internal types
97
- // ---------------------------------------------------------------------------
98
-
99
- interface UsageSnapshot {
100
- input: number;
101
- output: number;
102
- cacheRead: number;
103
- cacheWrite: number;
104
- }
105
-
106
- interface AgentEntry {
107
- id: string; // short form, e.g. first 8 chars
108
- task: string;
109
- model: string;
110
- inputTokens: number;
111
- outputTokens: number;
112
- cost: number;
113
- status: 'running' | 'done' | 'failed';
114
- }
115
-
116
- // Sparkline history (rolling last N cost-per-turn values)
117
- const SPARKLINE_BARS = '._-:=+*#';
118
- const SPARKLINE_LEN = 16;
119
-
120
- function buildSparkline(history: number[]): string {
121
- if (history.length === 0) return '';
122
- const max = Math.max(...history);
123
- if (max === 0) return '.'.repeat(history.length);
124
- return history
125
- .map(v => {
126
- const idx = Math.round((v / max) * (SPARKLINE_BARS.length - 1));
127
- return SPARKLINE_BARS[idx] ?? '.';
128
- })
129
- .join('');
130
- }
131
-
132
- // ---------------------------------------------------------------------------
133
- // Colour palette
134
- // ---------------------------------------------------------------------------
135
-
136
- const C = {
137
- header: '#ffffff',
138
- label: '#aaaaaa',
139
- value: '#00ff88',
140
- cost: '#ffdd44',
141
- alert: '#ff4444',
142
- dim: '#555555',
143
- model: '#88aaff',
144
- running: '#88aaff',
145
- done: '#00ff88',
146
- failed: '#ff4444',
147
- separator: '#333333',
148
- bg: '',
149
- } as const;
150
-
151
- // ---------------------------------------------------------------------------
152
- // CostTrackerPanel
153
- // ---------------------------------------------------------------------------
154
-
155
- export class CostTrackerPanel extends BasePanel {
156
- // Session-level usage (from main orchestrator)
157
- private sessionUsage: UsageSnapshot = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
158
- private sessionModel = 'unknown';
159
-
160
- // Per-turn cost history for sparkline
161
- private costHistory: number[] = [];
162
- private lastSessionCost = 0;
163
-
164
- // Per-agent tracking (keyed by agent id)
165
- private agents = new Map<string, AgentEntry>();
166
-
167
- // Budget alert threshold in USD (0 = disabled)
168
- private budgetThreshold: number;
169
-
170
- // Scroll offset for agent list
171
- private scrollOffset = 0;
172
-
173
- // Unsubscribe functions
174
- private unsubs: Array<() => void> = [];
175
-
176
- // Getter for live orchestrator usage
177
- private readonly getOrchestratorUsage: () => UsageSnapshot & { model?: string };
178
-
179
- constructor(
180
- turnEvents: UiEventFeed<TurnEvent>,
181
- agentEvents: UiEventFeed<AgentEvent>,
182
- getOrchestratorUsage: () => UsageSnapshot & { model?: string },
183
- opts: { budgetThreshold?: number } = {},
184
- ) {
185
- super('cost', 'Cost', '$', 'monitoring');
186
- this.getOrchestratorUsage = getOrchestratorUsage;
187
- this.budgetThreshold = opts.budgetThreshold ?? 0;
188
- this.attachEvents(turnEvents, agentEvents);
189
- }
190
-
191
- // -------------------------------------------------------------------------
192
- // Bus wiring
193
- // -------------------------------------------------------------------------
194
-
195
- private attachEvents(turnEvents: UiEventFeed<TurnEvent>, agentEvents: UiEventFeed<AgentEvent>): void {
196
- // Refresh after every completed turn
197
- this.unsubs.push(
198
- turnEvents.on('TURN_COMPLETED', () => this.onTurnComplete()),
199
- );
200
-
201
- // Track runtime worker events for cost attribution only.
202
- this.unsubs.push(
203
- agentEvents.on('AGENT_SPAWNING', (payload) => {
204
- this.agents.set(payload.agentId, {
205
- id: payload.agentId.slice(0, 8),
206
- task: payload.task.length > 40 ? payload.task.slice(0, 37) + '...' : payload.task,
207
- model: 'unknown',
208
- inputTokens: 0,
209
- outputTokens: 0,
210
- cost: 0,
211
- status: 'running',
212
- });
213
- this.markDirty();
214
- }),
215
- );
216
-
217
- // Agent completed — capture token data from result if available
218
- this.unsubs.push(
219
- agentEvents.on('AGENT_COMPLETED', (payload) => {
220
- const entry = this.agents.get(payload.agentId);
221
- if (entry) {
222
- entry.status = 'done';
223
- this.markDirty();
224
- }
225
- }),
226
- );
227
-
228
- // Agent error
229
- this.unsubs.push(
230
- agentEvents.on('AGENT_FAILED', (payload) => {
231
- const entry = this.agents.get(payload.agentId);
232
- if (entry) {
233
- entry.status = 'failed';
234
- this.markDirty();
235
- }
236
- }),
237
- );
238
- }
239
-
240
- private onTurnComplete(): void {
241
- const usage = this.getOrchestratorUsage();
242
- this.sessionUsage = {
243
- input: usage.input,
244
- output: usage.output,
245
- cacheRead: usage.cacheRead,
246
- cacheWrite: usage.cacheWrite,
247
- };
248
- if (usage.model) this.sessionModel = usage.model;
249
-
250
- // Record cost delta for sparkline
251
- const sessionProvider = this.sessionModel.includes('/') ? this.sessionModel.split('/')[0]! : '';
252
- const pricing = getPricing(this.sessionModel);
253
- const totalCost = calcCost(usage.input + usage.cacheRead + usage.cacheWrite, usage.output, pricing);
254
- const delta = Math.max(0, totalCost - this.lastSessionCost);
255
- this.lastSessionCost = totalCost;
256
- this.costHistory.push(delta);
257
- if (this.costHistory.length > SPARKLINE_LEN) {
258
- this.costHistory.shift();
259
- }
260
-
261
- this.markDirty();
262
- }
263
-
264
- // -------------------------------------------------------------------------
265
- // Lifecycle
266
- // -------------------------------------------------------------------------
267
-
268
- override onActivate(): void {
269
- // Sync latest usage on activation
270
- const usage = this.getOrchestratorUsage();
271
- this.sessionUsage = {
272
- input: usage.input,
273
- output: usage.output,
274
- cacheRead: usage.cacheRead,
275
- cacheWrite: usage.cacheWrite,
276
- };
277
- this.needsRender = true;
278
- }
279
-
280
- override onDestroy(): void {
281
- for (const unsub of this.unsubs) unsub();
282
- this.unsubs = [];
283
- }
284
-
285
- // -------------------------------------------------------------------------
286
- // Input
287
- // -------------------------------------------------------------------------
288
-
289
- handleInput(key: string): boolean {
290
- switch (key) {
291
- case 'up': return this.scroll(-1);
292
- case 'down': return this.scroll(1);
293
- case 'pageup': return this.scroll(-10);
294
- case 'pagedown': return this.scroll(10);
295
- default: return false;
296
- }
297
- }
298
-
299
- private scroll(delta: number): boolean {
300
- const prev = this.scrollOffset;
301
- this.scrollOffset = Math.max(0, this.scrollOffset + delta);
302
- if (this.scrollOffset !== prev) this.markDirty();
303
- return true;
304
- }
305
-
306
- // -------------------------------------------------------------------------
307
- // Render
308
- // -------------------------------------------------------------------------
309
-
310
- render(width: number, height: number): Line[] {
311
- if (height <= 0 || width <= 0) return [];
312
-
313
- const sessionProvider = this.sessionModel.includes('/') ? this.sessionModel.split('/')[0]! : '';
314
- const pricing = getPricing(this.sessionModel);
315
- const totalInputTokens = this.sessionUsage.input + this.sessionUsage.cacheRead + this.sessionUsage.cacheWrite;
316
- const sessionCost = calcCost(this.sessionUsage.input + this.sessionUsage.cacheRead + this.sessionUsage.cacheWrite, this.sessionUsage.output, pricing);
317
- const overBudget = this.budgetThreshold > 0 && sessionCost > this.budgetThreshold;
318
- const sparkline = buildSparkline(this.costHistory);
319
- const costStr = formatCost(sessionCost);
320
- const costFg = overBudget ? C.alert : C.cost;
321
- const budgetStr = this.budgetThreshold > 0
322
- ? ` / ${formatCost(this.budgetThreshold)}`
323
- : '';
324
- const alertStr = overBudget ? ' ! OVER BUDGET' : '';
325
- const sessionLines: Line[] = [
326
- this.renderKeyValue(width, ' Total', `${costStr}${budgetStr}${alertStr}`, costFg),
327
- ];
328
- if (sparkline.length > 0) sessionLines.push(this.renderLabeledLine(width, ' Trend', sparkline, C.value));
329
- sessionLines.push(this.renderKeyValue(width, ' Input', formatTokens(this.sessionUsage.input), C.label));
330
- sessionLines.push(this.renderKeyValue(width, ' Output', formatTokens(this.sessionUsage.output), C.label));
331
- if (this.sessionUsage.cacheRead > 0 || this.sessionUsage.cacheWrite > 0) {
332
- sessionLines.push(this.renderKeyValue(width, ' Cache W', formatTokens(this.sessionUsage.cacheWrite), C.dim));
333
- sessionLines.push(this.renderKeyValue(width, ' Cache R', formatTokens(this.sessionUsage.cacheRead), C.dim));
334
- }
335
- sessionLines.push(this.renderKeyValue(width, ' Total T', formatTokens(totalInputTokens + this.sessionUsage.output), C.label));
336
- sessionLines.push(this.renderKeyValue(width, ' Model', this.sessionModel, C.model));
337
-
338
- const sections: PanelWorkspaceSection[] = [
339
- {
340
- title: 'Session Cost',
341
- lines: sessionLines,
342
- },
343
- ];
344
-
345
- const agentList = Array.from(this.agents.values());
346
- if (agentList.length > 0) {
347
- const planCost = agentList.reduce((sum, a) => sum + a.cost, 0);
348
- const agentRows: Line[] = [
349
- this.renderKeyValue(width, ' Plan total', formatCost(planCost + sessionCost), C.cost),
350
- this.renderDivider(width),
351
- ];
352
- for (const agent of agentList) {
353
- const statusFg = agent.status === 'running' ? C.running
354
- : agent.status === 'failed' ? C.failed
355
- : C.done;
356
- const statusIcon = agent.status === 'running' ? '…'
357
- : agent.status === 'failed' ? '✕'
358
- : '✓';
359
-
360
- const agentLabel = `${statusIcon} ${agent.id}`;
361
- const taskText = agent.task;
362
- agentRows.push(this.renderAgent(width, agentLabel, taskText, statusFg));
363
-
364
- if (agent.inputTokens > 0 || agent.outputTokens > 0) {
365
- const tokenInfo = ` in:${formatTokens(agent.inputTokens)} out:${formatTokens(agent.outputTokens)} ${formatCost(agent.cost)}`;
366
- agentRows.push(this.renderLabeledLine(width, '', tokenInfo, C.dim));
367
- }
368
- }
369
- const sessionSection: PanelWorkspaceSection = sections[0]!;
370
- const agentsSection = resolveScrollablePanelSection(width, height, {
371
- intro: 'Track per-session and per-agent token spend using model pricing and live usage snapshots.',
372
- footerLines: [
373
- buildPanelLine(width, [[' Up/Down', DEFAULT_PANEL_PALETTE.info], [' scroll agents', DEFAULT_PANEL_PALETTE.dim]]),
374
- ],
375
- palette: DEFAULT_PANEL_PALETTE,
376
- beforeSections: [sessionSection],
377
- section: {
378
- title: 'Agents',
379
- scrollableLines: agentRows,
380
- scrollOffset: this.scrollOffset,
381
- minRows: 4,
382
- },
383
- });
384
- this.scrollOffset = agentsSection.scrollOffset;
385
- sections.push(agentsSection.section);
386
- } else {
387
- sections.push({
388
- title: 'Agents',
389
- lines: buildEmptyState(
390
- width,
391
- ' No delegated agent cost records',
392
- 'Agent-level cost estimates appear only for explicit delegated build/review session records.',
393
- [],
394
- DEFAULT_PANEL_PALETTE,
395
- ),
396
- });
397
- }
398
-
399
- return buildPanelWorkspace(width, height, {
400
- title: ' Cost Tracker',
401
- intro: 'Track per-session and per-agent token spend using model pricing and live usage snapshots.',
402
- sections,
403
- footerLines: [
404
- buildPanelLine(width, [[' Up/Down', DEFAULT_PANEL_PALETTE.info], [' scroll agents', DEFAULT_PANEL_PALETTE.dim]]),
405
- ],
406
- palette: DEFAULT_PANEL_PALETTE,
407
- });
408
- }
409
-
410
- // -------------------------------------------------------------------------
411
- // Render helpers
412
- // -------------------------------------------------------------------------
413
-
414
- private renderKeyValue(width: number, label: string, value: string, valueFg: string): Line {
415
- const LABEL_W = 10;
416
- return buildStyledPanelLine(width, [
417
- { text: label.padEnd(LABEL_W).slice(0, LABEL_W), fg: C.label, bg: C.bg },
418
- { text: ': ', fg: C.dim, bg: C.bg },
419
- { text: value, fg: valueFg, bg: C.bg, bold: true },
420
- ]);
421
- }
422
-
423
- private renderLabeledLine(width: number, label: string, value: string, valueFg: string): Line {
424
- return buildStyledPanelLine(width, [
425
- ...(label.length > 0 ? [{ text: `${label.slice(0, 10).padEnd(10)} `, fg: C.label }] : []),
426
- { text: value, fg: valueFg },
427
- ]);
428
- }
429
-
430
- private renderAgent(width: number, label: string, task: string, fg: string): Line {
431
- const LABEL_W = 12;
432
- const remaining = width - LABEL_W - 1;
433
- const trimmed = task.length > remaining ? task.slice(0, remaining - 3) + '...' : task;
434
- return buildStyledPanelLine(width, [
435
- { text: `${label.padEnd(LABEL_W).slice(0, LABEL_W)} `, fg, bold: true },
436
- { text: trimmed, fg: C.label },
437
- ]);
438
- }
439
-
440
- private renderDivider(width: number): Line {
441
- return buildStyledPanelLine(width, [{ text: '─'.repeat(width), fg: C.separator }]);
442
- }
443
-
444
- }