@pellux/goodvibes-agent 1.4.4 → 1.5.1
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/CHANGELOG.md +22 -0
- package/README.md +52 -7
- package/dist/package/main.js +7491 -12253
- package/docs/README.md +2 -2
- package/docs/channels-remote-and-api.md +1 -1
- package/docs/getting-started.md +2 -2
- package/docs/release-and-publishing.md +1 -1
- package/docs/tools-and-commands.md +5 -5
- package/package.json +4 -2
- package/release/live-verification/live-verification.json +13 -13
- package/release/live-verification/live-verification.md +15 -15
- package/release/performance-snapshot.json +2 -2
- package/release/release-notes.md +5 -7
- package/release/release-readiness.json +6 -6
- package/src/agent/behavior-discovery-summary.ts +4 -18
- package/src/agent/calendar-registry.ts +322 -0
- package/src/agent/competitive-feature-inventory.ts +268 -130
- package/src/agent/document-registry.ts +5 -1
- package/src/agent/email/email-service.ts +350 -0
- package/src/agent/email/imap-client.ts +596 -0
- package/src/agent/email/smtp-client.ts +453 -0
- package/src/agent/ics-calendar.ts +662 -0
- package/src/agent/ics-timezone.ts +172 -0
- package/src/agent/markdown-frontmatter.ts +31 -0
- package/src/agent/memory-safety.ts +1 -1
- package/src/agent/note-registry.ts +3 -9
- package/src/agent/persona-discovery.ts +3 -15
- package/src/agent/persona-registry.ts +1 -10
- package/src/agent/research-source-registry.ts +5 -1
- package/src/agent/routine-discovery.ts +3 -15
- package/src/agent/runtime-profile.ts +3 -0
- package/src/agent/skill-discovery.ts +3 -15
- package/src/agent/skill-draft-proposer.ts +203 -0
- package/src/agent/skill-draft-runner.ts +201 -0
- package/src/agent/skill-registry.ts +36 -1
- package/src/agent/skill-standard.ts +99 -0
- package/src/agent/vibe-file.ts +7 -22
- package/src/cli/completion.ts +1 -1
- package/src/cli/config-overrides.ts +10 -1
- package/src/cli/redaction.ts +17 -5
- package/src/config/provider-model.ts +2 -1
- package/src/config/secret-config.ts +13 -6
- package/src/core/activity-feed.ts +97 -0
- package/src/core/away-digest.ts +161 -0
- package/src/core/conversation-rendering.ts +7 -3
- package/src/core/conversation.ts +22 -15
- package/src/core/hardware-profile.ts +362 -0
- package/src/core/last-seen-store.ts +78 -0
- package/src/core/plain-language.ts +52 -0
- package/src/core/setup-incomplete-hint.ts +90 -0
- package/src/core/system-message-router.ts +38 -87
- package/src/input/agent-workspace-basic-command-editor-submission.ts +60 -220
- package/src/input/agent-workspace-basic-command-editors.ts +39 -141
- package/src/input/agent-workspace-categories.ts +40 -125
- package/src/input/agent-workspace-command-editor.ts +4 -0
- package/src/input/agent-workspace-host-category.ts +0 -5
- package/src/input/agent-workspace-local-editor-submission.ts +3 -1
- package/src/input/agent-workspace-navigation.ts +10 -3
- package/src/input/agent-workspace-onboarding-actions.ts +132 -0
- package/src/input/agent-workspace-onboarding-categories.ts +29 -26
- package/src/input/agent-workspace-onboarding-finish.ts +11 -4
- package/src/input/agent-workspace-onboarding-state.ts +111 -0
- package/src/input/agent-workspace-profile-editor-submission.ts +260 -0
- package/src/input/agent-workspace-profile-editors.ts +155 -0
- package/src/input/agent-workspace-subscription-editor.ts +7 -0
- package/src/input/agent-workspace-types.ts +5 -1
- package/src/input/agent-workspace.ts +65 -39
- package/src/input/command-registry.ts +18 -5
- package/src/input/commands/agent-runtime-profile-runtime.ts +2 -1
- package/src/input/commands/agent-skills-runtime.ts +60 -3
- package/src/input/commands/calendar-runtime.ts +209 -0
- package/src/input/commands/channels-runtime.ts +1 -0
- package/src/input/commands/command-error.ts +9 -0
- package/src/input/commands/compat-runtime.ts +1 -0
- package/src/input/commands/config.ts +1 -0
- package/src/input/commands/conversation-runtime.ts +1 -1
- package/src/input/commands/delegation-runtime.ts +5 -6
- package/src/input/commands/email-runtime.ts +387 -0
- package/src/input/commands/experience-runtime.ts +17 -4
- package/src/input/commands/guidance-runtime.ts +1 -1
- package/src/input/commands/health-runtime.ts +6 -1
- package/src/input/commands/knowledge-format.ts +73 -0
- package/src/input/commands/knowledge.ts +9 -74
- package/src/input/commands/local-provider-runtime.ts +2 -1
- package/src/input/commands/local-runtime.ts +10 -4
- package/src/input/commands/mcp-runtime.ts +7 -0
- package/src/input/commands/notify-runtime.ts +17 -1
- package/src/input/commands/onboarding-runtime.ts +1 -0
- package/src/input/commands/operator-actions-runtime.ts +1 -0
- package/src/input/commands/operator-runtime.ts +3 -0
- package/src/input/commands/personas-runtime.ts +1 -0
- package/src/input/commands/platform-access-runtime.ts +40 -17
- package/src/input/commands/product-runtime.ts +6 -1
- package/src/input/commands/provider-accounts-runtime.ts +3 -2
- package/src/input/commands/qrcode-runtime.ts +1 -0
- package/src/input/commands/routines-runtime.ts +1 -0
- package/src/input/commands/runtime-services.ts +0 -13
- package/src/input/commands/security-runtime.ts +1 -0
- package/src/input/commands/session-content.ts +1 -0
- package/src/input/commands/shell-core.ts +5 -2
- package/src/input/commands/subscription-runtime.ts +33 -9
- package/src/input/commands/support-bundle-runtime.ts +8 -1
- package/src/input/commands/tasks-runtime.ts +1 -0
- package/src/input/commands/tts-runtime.ts +1 -0
- package/src/input/commands/vibe-runtime.ts +1 -0
- package/src/input/commands.ts +4 -0
- package/src/input/feed-context-factory.ts +3 -12
- package/src/input/handler-command-route.ts +7 -60
- package/src/input/handler-feed-routes.ts +0 -194
- package/src/input/handler-feed.ts +2 -54
- package/src/input/handler-interactions.ts +0 -2
- package/src/input/handler-modal-stack.ts +0 -9
- package/src/input/handler-picker-routes.ts +24 -1
- package/src/input/handler-shortcuts.ts +11 -40
- package/src/input/handler-ui-state.ts +13 -0
- package/src/input/handler.ts +8 -35
- package/src/input/keybindings.ts +40 -17
- package/src/input/model-picker-local-fit.ts +130 -0
- package/src/input/shell-passthrough.ts +58 -0
- package/src/input/submission-router.ts +0 -5
- package/src/main.ts +129 -90
- package/src/renderer/activity-sidebar.ts +186 -0
- package/src/renderer/agent-workspace-context-lines.ts +5 -48
- package/src/renderer/agent-workspace-style.ts +1 -1
- package/src/renderer/agent-workspace.ts +14 -2
- package/src/renderer/compositor.ts +37 -125
- package/src/renderer/conversation-overlays.ts +3 -3
- package/src/renderer/help-overlay.ts +7 -5
- package/src/renderer/model-workspace.ts +101 -86
- package/src/{panels → renderer}/polish.ts +3 -3
- package/src/renderer/shell-surface.ts +4 -5
- package/src/renderer/status-token.ts +31 -11
- package/src/renderer/tab-strip.ts +1 -1
- package/src/renderer/tool-call.ts +7 -8
- package/src/renderer/tool-labels.ts +92 -0
- package/src/renderer/ui-factory.ts +48 -96
- package/src/runtime/bootstrap-command-context.ts +0 -5
- package/src/runtime/bootstrap-command-parts.ts +6 -15
- package/src/runtime/bootstrap-core.ts +34 -13
- package/src/runtime/bootstrap-hook-bridge.ts +3 -1
- package/src/runtime/bootstrap-shell.ts +3 -50
- package/src/runtime/bootstrap.ts +3 -4
- package/src/runtime/context.ts +1 -1
- package/src/runtime/diagnostics/panels/index.ts +0 -1
- package/src/runtime/diagnostics/panels/policy.ts +1 -1
- package/src/runtime/execution-ledger.ts +16 -1
- package/src/runtime/index.ts +6 -1
- package/src/runtime/onboarding/index.ts +1 -0
- package/src/runtime/onboarding/onboarding-state.ts +200 -0
- package/src/{panels → runtime}/provider-account-snapshot.ts +5 -2
- package/src/runtime/services.ts +16 -4
- package/src/runtime/terminal-output-guard.ts +7 -0
- package/src/runtime/tool-permission-safety.ts +1 -1
- package/src/runtime/ui/model-picker/data-provider.ts +1 -1
- package/src/runtime/ui/model-picker/health-enrichment.ts +2 -2
- package/src/runtime/ui/model-picker/types.ts +2 -2
- package/src/runtime/ui/provider-health/data-provider.ts +3 -3
- package/src/runtime/ui/provider-health/types.ts +2 -2
- package/src/runtime/ui-services.ts +0 -2
- package/src/shell/agent-workspace-fullscreen.ts +0 -1
- package/src/shell/autonomy-surfacing.ts +272 -0
- package/src/shell/session-continuity-hints.ts +0 -6
- package/src/shell/startup-wiring.ts +221 -0
- package/src/shell/ui-openers.ts +18 -29
- package/src/tools/agent-context-policy.ts +1 -1
- package/src/tools/agent-harness-background-processes.ts +4 -4
- package/src/tools/agent-harness-browser-cockpit-route.ts +3 -3
- package/src/tools/agent-harness-command-runner.ts +6 -1
- package/src/tools/agent-harness-document-ops.ts +26 -53
- package/src/tools/agent-harness-execution-history.ts +1 -13
- package/src/tools/agent-harness-execution-posture.ts +0 -15
- package/src/tools/agent-harness-keybinding-metadata.ts +28 -29
- package/src/tools/agent-harness-local-model-cookbook.ts +24 -1
- package/src/tools/agent-harness-metadata.ts +16 -0
- package/src/tools/agent-harness-mode-catalog.ts +2 -9
- package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
- package/src/tools/agent-harness-setup-posture-utils.ts +1 -1
- package/src/tools/agent-harness-tool-schema.ts +13 -14
- package/src/tools/agent-harness-tool.ts +27 -23
- package/src/tools/agent-harness-ui-surface-metadata.ts +10 -20
- package/src/tools/agent-harness-workspace-action-runner.ts +3 -4
- package/src/tools/agent-workspace-tool.ts +2 -33
- package/src/utils/terminal-width.ts +9 -3
- package/src/version.ts +1 -1
- package/src/input/agent-workspace-panel-route.ts +0 -44
- package/src/input/panel-integration-actions.ts +0 -26
- package/src/panels/approval-panel.ts +0 -149
- package/src/panels/automation-control-panel.ts +0 -212
- package/src/panels/base-panel.ts +0 -254
- package/src/panels/builtin/agent.ts +0 -58
- package/src/panels/builtin/knowledge.ts +0 -26
- package/src/panels/builtin/operations.ts +0 -121
- package/src/panels/builtin/session.ts +0 -138
- package/src/panels/builtin/shared.ts +0 -275
- package/src/panels/builtin/usage.ts +0 -21
- package/src/panels/builtin-panels.ts +0 -23
- package/src/panels/confirm-state.ts +0 -61
- package/src/panels/context-visualizer-panel.ts +0 -204
- package/src/panels/cost-tracker-panel.ts +0 -444
- package/src/panels/docs-panel.ts +0 -285
- package/src/panels/index.ts +0 -25
- package/src/panels/knowledge-panel.ts +0 -417
- package/src/panels/memory-panel.ts +0 -226
- package/src/panels/panel-list-panel.ts +0 -464
- package/src/panels/panel-manager.ts +0 -570
- package/src/panels/provider-accounts-panel.ts +0 -233
- package/src/panels/provider-health-domains.ts +0 -208
- package/src/panels/provider-health-panel.ts +0 -720
- package/src/panels/provider-health-tracker.ts +0 -115
- package/src/panels/provider-stats-panel.ts +0 -366
- package/src/panels/qr-panel.ts +0 -207
- package/src/panels/schedule-panel.ts +0 -321
- package/src/panels/scrollable-list-panel.ts +0 -491
- package/src/panels/search-focus.ts +0 -32
- package/src/panels/security-panel.ts +0 -295
- package/src/panels/session-browser-panel.ts +0 -395
- package/src/panels/session-maintenance.ts +0 -125
- package/src/panels/subscription-panel.ts +0 -263
- package/src/panels/system-messages-panel.ts +0 -230
- package/src/panels/tasks-panel.ts +0 -344
- package/src/panels/thinking-panel.ts +0 -304
- package/src/panels/token-budget-panel.ts +0 -475
- package/src/panels/tool-inspector-panel.ts +0 -436
- package/src/panels/types.ts +0 -54
- package/src/renderer/panel-composite.ts +0 -158
- package/src/renderer/panel-tab-bar.ts +0 -69
- package/src/renderer/panel-workspace-bar.ts +0 -42
- package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
- package/src/tools/agent-harness-panel-metadata.ts +0 -211
- /package/src/runtime/perf/{panel-health-monitor.ts → component-health.ts} +0 -0
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// ContextVisualizerPanel — stacked bar showing context window composition.
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
|
|
5
|
-
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
6
|
-
import type { Line } from '../types/grid.ts';
|
|
7
|
-
import { BasePanel } from './base-panel.ts';
|
|
8
|
-
import { evaluateSessionMaintenance } from '@/runtime/index.ts';
|
|
9
|
-
import type { TurnEvent } from '@/runtime/index.ts';
|
|
10
|
-
import type { UiEventFeed } from '../runtime/ui-events.ts';
|
|
11
|
-
import type { UiReadModel, UiSessionSnapshot } from '../runtime/ui-read-models.ts';
|
|
12
|
-
import type { SessionMemoryQuery } from '../runtime/ui-service-queries.ts';
|
|
13
|
-
import {
|
|
14
|
-
buildEmptyState,
|
|
15
|
-
buildGuidanceLine,
|
|
16
|
-
buildMeterLine,
|
|
17
|
-
buildPanelLine,
|
|
18
|
-
buildStyledPanelLine,
|
|
19
|
-
buildPanelWorkspace,
|
|
20
|
-
DEFAULT_PANEL_PALETTE,
|
|
21
|
-
} from './polish.ts';
|
|
22
|
-
|
|
23
|
-
const C = {
|
|
24
|
-
convFg: '#cc99ff',
|
|
25
|
-
overFg: '#ff6666',
|
|
26
|
-
barEmpty: '#333344',
|
|
27
|
-
labelFg: '#8888bb',
|
|
28
|
-
} as const;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
interface ContextSnapshot {
|
|
33
|
-
input: number;
|
|
34
|
-
limit: number;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function formatK(n: number): string {
|
|
38
|
-
if (n >= 1000) return `${(n / 1000).toFixed(1)}k`;
|
|
39
|
-
return String(n);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export class ContextVisualizerPanel extends BasePanel {
|
|
43
|
-
private snapshot: ContextSnapshot = { input: 0, limit: 0 };
|
|
44
|
-
private unsubs: Array<() => void> = [];
|
|
45
|
-
private readonly sessionMemoryStore: SessionMemoryQuery;
|
|
46
|
-
|
|
47
|
-
constructor(
|
|
48
|
-
private readonly turnEvents: UiEventFeed<TurnEvent>,
|
|
49
|
-
sessionMemoryStore: SessionMemoryQuery,
|
|
50
|
-
private readonly configManager: Pick<ConfigManager, 'get'>,
|
|
51
|
-
private getUsage?: () => { input: number; output: number; cacheRead: number; cacheWrite: number; model?: string },
|
|
52
|
-
private contextLimit?: number,
|
|
53
|
-
private sessionReadModel?: UiReadModel<UiSessionSnapshot>,
|
|
54
|
-
) {
|
|
55
|
-
super('context', 'Context', 'C', 'ai');
|
|
56
|
-
this.sessionMemoryStore = sessionMemoryStore;
|
|
57
|
-
this._attachBus();
|
|
58
|
-
this._refresh();
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
override onActivate(): void {
|
|
62
|
-
this.needsRender = true;
|
|
63
|
-
this._refresh();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
override onDeactivate(): void {
|
|
67
|
-
super.onDeactivate();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
override onDestroy(): void {
|
|
71
|
-
this._detachBus();
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
render(width: number, height: number): Line[] {
|
|
75
|
-
return this.trackedRender(() => {
|
|
76
|
-
if (height <= 0 || width <= 0) return [];
|
|
77
|
-
|
|
78
|
-
const input = this.snapshot.input;
|
|
79
|
-
const limit = this.snapshot.limit;
|
|
80
|
-
const pct = limit > 0 ? Math.min(100, Math.round((input / limit) * 100)) : 0;
|
|
81
|
-
const barWidth = Math.max(1, width - 2);
|
|
82
|
-
const overLimit = limit > 0 && input > limit;
|
|
83
|
-
const fg = overLimit ? C.overFg : C.convFg;
|
|
84
|
-
if (limit <= 0) {
|
|
85
|
-
return buildPanelWorkspace(width, height, {
|
|
86
|
-
title: ' Context Usage',
|
|
87
|
-
intro: 'Visualize current input-token pressure against the active model context window.',
|
|
88
|
-
sections: [
|
|
89
|
-
{
|
|
90
|
-
lines: buildEmptyState(
|
|
91
|
-
width,
|
|
92
|
-
' Context limit unavailable',
|
|
93
|
-
'Select a model with a known context window and submit or complete a turn to populate live context usage.',
|
|
94
|
-
[],
|
|
95
|
-
DEFAULT_PANEL_PALETTE,
|
|
96
|
-
),
|
|
97
|
-
},
|
|
98
|
-
],
|
|
99
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return buildPanelWorkspace(width, height, {
|
|
104
|
-
title: ' Context Usage',
|
|
105
|
-
intro: 'Visualize current input-token pressure against the active model context window.',
|
|
106
|
-
sections: [
|
|
107
|
-
{
|
|
108
|
-
title: 'Summary',
|
|
109
|
-
lines: [
|
|
110
|
-
buildPanelLine(width, [
|
|
111
|
-
[' Input ', DEFAULT_PANEL_PALETTE.label],
|
|
112
|
-
[formatK(input), DEFAULT_PANEL_PALETTE.value],
|
|
113
|
-
[' Limit ', DEFAULT_PANEL_PALETTE.label],
|
|
114
|
-
[formatK(limit), DEFAULT_PANEL_PALETTE.info],
|
|
115
|
-
[' Fill ', DEFAULT_PANEL_PALETTE.label],
|
|
116
|
-
[`${pct}%`, overLimit ? DEFAULT_PANEL_PALETTE.bad : DEFAULT_PANEL_PALETTE.good],
|
|
117
|
-
]),
|
|
118
|
-
],
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
title: 'Usage',
|
|
122
|
-
lines: [
|
|
123
|
-
this._renderBar(width, barWidth, input, limit),
|
|
124
|
-
this._renderSegLine(width, 'Input tokens', input, pct, fg),
|
|
125
|
-
buildPanelLine(width, [[` ${formatK(input)} / ${formatK(limit)} tokens (${pct}%) Refreshes each LLM call`, DEFAULT_PANEL_PALETTE.dim]]),
|
|
126
|
-
],
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
title: 'Maintenance',
|
|
130
|
-
lines: this._renderMaintenance(width),
|
|
131
|
-
},
|
|
132
|
-
],
|
|
133
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
private _renderBar(width: number, barWidth: number, input: number, limit: number): Line {
|
|
139
|
-
const filled = limit > 0 ? Math.min(barWidth, Math.round((input / limit) * barWidth)) : 0;
|
|
140
|
-
const overLimit = limit > 0 && input > limit;
|
|
141
|
-
const barFg = overLimit ? C.overFg : C.convFg;
|
|
142
|
-
return buildMeterLine(width, filled, barWidth, {
|
|
143
|
-
filled: barFg,
|
|
144
|
-
empty: C.barEmpty,
|
|
145
|
-
label: DEFAULT_PANEL_PALETTE.dim,
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
private _renderSegLine(width: number, label: string, val: number, pct: number, fg: string): Line {
|
|
150
|
-
const labelPadded = ` ${label}`.padEnd(22);
|
|
151
|
-
const valStr = formatK(val).padStart(7);
|
|
152
|
-
const pctStr = `${pct}%`.padStart(5);
|
|
153
|
-
return buildStyledPanelLine(width, [
|
|
154
|
-
{ text: labelPadded, fg: C.labelFg },
|
|
155
|
-
{ text: valStr, fg },
|
|
156
|
-
{ text: ' ', fg: DEFAULT_PANEL_PALETTE.dim },
|
|
157
|
-
{ text: pctStr, fg },
|
|
158
|
-
]);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
private _refresh(): void {
|
|
162
|
-
const usage = this.getUsage?.();
|
|
163
|
-
if (usage) {
|
|
164
|
-
this.snapshot.input = usage.input;
|
|
165
|
-
this.snapshot.limit = this.contextLimit ?? 0;
|
|
166
|
-
}
|
|
167
|
-
this.markDirty();
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
private _renderMaintenance(width: number): Line[] {
|
|
171
|
-
const status = evaluateSessionMaintenance({
|
|
172
|
-
configManager: this.configManager,
|
|
173
|
-
currentTokens: this.snapshot.input,
|
|
174
|
-
contextWindow: this.snapshot.limit,
|
|
175
|
-
sessionMemoryCount: this.sessionMemoryStore.list().length,
|
|
176
|
-
session: this.sessionReadModel?.getSnapshot().session,
|
|
177
|
-
});
|
|
178
|
-
const lines: Line[] = [
|
|
179
|
-
buildPanelLine(width, [[` ${status.summary}`, DEFAULT_PANEL_PALETTE.label]]),
|
|
180
|
-
];
|
|
181
|
-
if (status.reasons[0]) {
|
|
182
|
-
lines.push(buildPanelLine(width, [[` ${status.reasons[0]}`, DEFAULT_PANEL_PALETTE.dim]]));
|
|
183
|
-
}
|
|
184
|
-
if (status.guidanceMode !== 'off' && status.nextSteps[0]) {
|
|
185
|
-
lines.push(buildGuidanceLine(width, status.nextSteps[0], 'open the suggested maintenance action', DEFAULT_PANEL_PALETTE));
|
|
186
|
-
}
|
|
187
|
-
return lines;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
private _attachBus(): void {
|
|
191
|
-
if (this.unsubs.length > 0) return;
|
|
192
|
-
this.unsubs.push(this.turnEvents.on('TURN_COMPLETED', () => {
|
|
193
|
-
this._refresh();
|
|
194
|
-
}));
|
|
195
|
-
this.unsubs.push(this.turnEvents.on('TURN_SUBMITTED', () => {
|
|
196
|
-
this._refresh();
|
|
197
|
-
}));
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
private _detachBus(): void {
|
|
201
|
-
for (const unsub of this.unsubs) unsub();
|
|
202
|
-
this.unsubs = [];
|
|
203
|
-
}
|
|
204
|
-
}
|
|
@@ -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
|
-
}
|