@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.
- package/CHANGELOG.md +14 -0
- package/README.md +7 -7
- package/dist/package/main.js +7424 -12241
- 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/performance-snapshot.json +2 -2
- package/release/release-notes.md +11 -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/submission-router.ts +0 -5
- package/src/main.ts +111 -89
- 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,475 +0,0 @@
|
|
|
1
|
-
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
2
|
-
import { BasePanel } from './base-panel.ts';
|
|
3
|
-
import { createEmptyLine, createStyledCell, type Line } from '../types/grid.ts';
|
|
4
|
-
import type { Orchestrator } from '../core/orchestrator';
|
|
5
|
-
import { evaluateSessionMaintenance } from '@/runtime/index.ts';
|
|
6
|
-
import type { UiReadModel, UiSessionSnapshot } from '../runtime/ui-read-models.ts';
|
|
7
|
-
import type { SessionMemoryQuery } from '../runtime/ui-service-queries.ts';
|
|
8
|
-
import {
|
|
9
|
-
buildEmptyState,
|
|
10
|
-
buildGuidanceLine,
|
|
11
|
-
buildStyledPanelLine,
|
|
12
|
-
buildPanelWorkspace,
|
|
13
|
-
resolveScrollablePanelSection,
|
|
14
|
-
DEFAULT_PANEL_PALETTE,
|
|
15
|
-
type PanelWorkspaceSection,
|
|
16
|
-
} from './polish.ts';
|
|
17
|
-
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
// Types
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
|
|
22
|
-
interface TurnUsage {
|
|
23
|
-
input: number;
|
|
24
|
-
output: number;
|
|
25
|
-
cacheRead: number;
|
|
26
|
-
cacheWrite: number;
|
|
27
|
-
/** Timestamp of the turn completion. */
|
|
28
|
-
ts: number;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
// Colors
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
|
|
35
|
-
const C = {
|
|
36
|
-
title: '#00ffff',
|
|
37
|
-
input: '#00ffff',
|
|
38
|
-
output: '#d000ff',
|
|
39
|
-
cacheRead: '#00d700',
|
|
40
|
-
cacheWrite: '#ffaf00',
|
|
41
|
-
barBg: '236',
|
|
42
|
-
label: '244',
|
|
43
|
-
value: '252',
|
|
44
|
-
sectionHeader: '238',
|
|
45
|
-
warnYellow: '#ffaf00',
|
|
46
|
-
warnRed: '#ff5f5f',
|
|
47
|
-
dim: '240',
|
|
48
|
-
good: '#5fd700',
|
|
49
|
-
turnHeader: '242',
|
|
50
|
-
} as const;
|
|
51
|
-
|
|
52
|
-
// Warning thresholds for context window usage
|
|
53
|
-
const WARN_YELLOW = 0.70;
|
|
54
|
-
const WARN_RED = 0.90;
|
|
55
|
-
|
|
56
|
-
// Maximum turns to keep in per-turn history
|
|
57
|
-
const MAX_TURN_HISTORY = 10;
|
|
58
|
-
|
|
59
|
-
// ---------------------------------------------------------------------------
|
|
60
|
-
// Helpers
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
|
-
|
|
63
|
-
/** Format a token count: up to 9999 shown as-is, then 10.0k, 1.2M, etc. */
|
|
64
|
-
function fmtTok(n: number): string {
|
|
65
|
-
if (n < 10_000) return String(n);
|
|
66
|
-
if (n < 1_000_000) return (n / 1000).toFixed(1) + 'k';
|
|
67
|
-
return (n / 1_000_000).toFixed(2) + 'M';
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// ---------------------------------------------------------------------------
|
|
71
|
-
// TokenBudgetPanel
|
|
72
|
-
// ---------------------------------------------------------------------------
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Real-time token usage breakdown panel.
|
|
76
|
-
*
|
|
77
|
-
* Displays:
|
|
78
|
-
* - Stacked bar: input / output / cache-read / cache-write
|
|
79
|
-
* - Context window fill percentage with progress bar
|
|
80
|
-
* - Per-turn token usage (last 10 turns)
|
|
81
|
-
* - Cumulative session totals
|
|
82
|
-
* - Warning threshold indicators (yellow at 70%, red at 90%)
|
|
83
|
-
*/
|
|
84
|
-
export class TokenBudgetPanel extends BasePanel {
|
|
85
|
-
/** Snapshot of cumulative usage from the Orchestrator after each turn. */
|
|
86
|
-
private sessionUsage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
87
|
-
/** lastInputTokens from the Orchestrator — current context window occupancy. */
|
|
88
|
-
private lastInputTokens = 0;
|
|
89
|
-
/** Context window size from the model config (0 = unknown). */
|
|
90
|
-
private contextWindow = 0;
|
|
91
|
-
/** Per-turn snapshots — at most MAX_TURN_HISTORY entries. */
|
|
92
|
-
private turnHistory: TurnUsage[] = [];
|
|
93
|
-
/** Previous cumulative snapshot to compute per-turn delta. */
|
|
94
|
-
private prevCumulative = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
95
|
-
|
|
96
|
-
private refreshTimer: ReturnType<typeof setInterval> | null = null;
|
|
97
|
-
private orchestrator: Orchestrator | null = null;
|
|
98
|
-
private getContextWindow: (() => number) | null = null;
|
|
99
|
-
private sessionReadModel: UiReadModel<UiSessionSnapshot> | null = null;
|
|
100
|
-
private readonly sessionMemoryStore: SessionMemoryQuery;
|
|
101
|
-
private readonly configManager: Pick<ConfigManager, 'get'>;
|
|
102
|
-
|
|
103
|
-
constructor(sessionMemoryStore: SessionMemoryQuery, configManager: Pick<ConfigManager, 'get'>) {
|
|
104
|
-
super('tokens', 'Tokens', 'T', 'monitoring');
|
|
105
|
-
this.sessionMemoryStore = sessionMemoryStore;
|
|
106
|
-
this.configManager = configManager;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// ---------------------------------------------------------------------------
|
|
110
|
-
// External wiring
|
|
111
|
-
// ---------------------------------------------------------------------------
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Wire the panel to live data sources. Call once after construction.
|
|
115
|
-
*
|
|
116
|
-
* @param orchestrator The main Orchestrator instance (for usage + lastInputTokens).
|
|
117
|
-
* @param getCtxWindow Callback returning the current model's contextWindow value.
|
|
118
|
-
*/
|
|
119
|
-
wire(
|
|
120
|
-
orchestrator: Orchestrator,
|
|
121
|
-
getCtxWindow: () => number,
|
|
122
|
-
sessionReadModel?: UiReadModel<UiSessionSnapshot>,
|
|
123
|
-
): void {
|
|
124
|
-
this.orchestrator = orchestrator;
|
|
125
|
-
this.getContextWindow = getCtxWindow;
|
|
126
|
-
this.sessionReadModel = sessionReadModel ?? null;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Snapshot a completed turn into per-turn history.
|
|
131
|
-
* Should be called from a `turn:complete` event listener.
|
|
132
|
-
*/
|
|
133
|
-
recordTurn(): void {
|
|
134
|
-
if (!this.orchestrator) return;
|
|
135
|
-
|
|
136
|
-
const cu = this.orchestrator.usage;
|
|
137
|
-
const delta: TurnUsage = {
|
|
138
|
-
input: cu.input - this.prevCumulative.input,
|
|
139
|
-
output: cu.output - this.prevCumulative.output,
|
|
140
|
-
cacheRead: cu.cacheRead - this.prevCumulative.cacheRead,
|
|
141
|
-
cacheWrite: cu.cacheWrite - this.prevCumulative.cacheWrite,
|
|
142
|
-
ts: Date.now(),
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
this.prevCumulative = { ...cu };
|
|
146
|
-
|
|
147
|
-
this.turnHistory.push(delta);
|
|
148
|
-
if (this.turnHistory.length > MAX_TURN_HISTORY) {
|
|
149
|
-
this.turnHistory.shift();
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
this.refresh();
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// ---------------------------------------------------------------------------
|
|
156
|
-
// Lifecycle
|
|
157
|
-
// ---------------------------------------------------------------------------
|
|
158
|
-
|
|
159
|
-
override onActivate(): void {
|
|
160
|
-
super.onActivate();
|
|
161
|
-
this.refresh();
|
|
162
|
-
// Poll every 2 s while active so the context bar stays current during streaming
|
|
163
|
-
if (this.refreshTimer !== null) clearInterval(this.refreshTimer);
|
|
164
|
-
this.refreshTimer = setInterval(() => this.refresh(), 2_000);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
override onDeactivate(): void {
|
|
168
|
-
super.onDeactivate();
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
override onDestroy(): void {
|
|
172
|
-
if (this.refreshTimer !== null) {
|
|
173
|
-
clearInterval(this.refreshTimer);
|
|
174
|
-
this.refreshTimer = null;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// ---------------------------------------------------------------------------
|
|
179
|
-
// Data refresh
|
|
180
|
-
// ---------------------------------------------------------------------------
|
|
181
|
-
|
|
182
|
-
private refresh(): void {
|
|
183
|
-
if (this.orchestrator) {
|
|
184
|
-
const u = this.orchestrator.usage;
|
|
185
|
-
this.sessionUsage.input = u.input ?? 0;
|
|
186
|
-
this.sessionUsage.output = u.output ?? 0;
|
|
187
|
-
this.sessionUsage.cacheRead = u.cacheRead ?? 0;
|
|
188
|
-
this.sessionUsage.cacheWrite = u.cacheWrite ?? 0;
|
|
189
|
-
this.lastInputTokens = this.orchestrator.lastInputTokens;
|
|
190
|
-
}
|
|
191
|
-
if (this.getContextWindow) {
|
|
192
|
-
this.contextWindow = this.getContextWindow();
|
|
193
|
-
}
|
|
194
|
-
this.markDirty();
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// ---------------------------------------------------------------------------
|
|
198
|
-
// Rendering
|
|
199
|
-
// ---------------------------------------------------------------------------
|
|
200
|
-
|
|
201
|
-
override render(width: number, height: number): Line[] {
|
|
202
|
-
return this.trackedRender(() => {
|
|
203
|
-
const sections: PanelWorkspaceSection[] = [];
|
|
204
|
-
|
|
205
|
-
if (this.contextWindow > 0) {
|
|
206
|
-
sections.push({
|
|
207
|
-
title: 'Context',
|
|
208
|
-
lines: this.renderContextBar(width),
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
sections.push({
|
|
213
|
-
title: 'Session',
|
|
214
|
-
lines: [
|
|
215
|
-
...this.renderStackedBar(width, this.sessionUsage, 'Session'),
|
|
216
|
-
...this.renderTotals(width),
|
|
217
|
-
],
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
if (this.turnHistory.length > 0) {
|
|
221
|
-
const priorSections = [...sections];
|
|
222
|
-
const turnsSection = resolveScrollablePanelSection(width, height, {
|
|
223
|
-
intro: 'Live context pressure, session token composition, cache usage, and recent turn deltas.',
|
|
224
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
225
|
-
beforeSections: priorSections,
|
|
226
|
-
section: {
|
|
227
|
-
title: 'Recent Turns',
|
|
228
|
-
scrollableLines: this.renderTurnHistory(width, this.turnHistory.length + 1),
|
|
229
|
-
scrollOffset: Math.max(0, this.turnHistory.length),
|
|
230
|
-
minRows: 6,
|
|
231
|
-
},
|
|
232
|
-
afterSections: [{
|
|
233
|
-
title: 'Maintenance',
|
|
234
|
-
lines: this.renderMaintenance(width),
|
|
235
|
-
}],
|
|
236
|
-
});
|
|
237
|
-
sections.push(turnsSection.section);
|
|
238
|
-
} else {
|
|
239
|
-
sections.push({
|
|
240
|
-
title: 'Recent Turns',
|
|
241
|
-
lines: buildEmptyState(
|
|
242
|
-
width,
|
|
243
|
-
' No turns recorded yet',
|
|
244
|
-
'Token deltas appear here after completed turns so you can see context growth and cache usage.',
|
|
245
|
-
[],
|
|
246
|
-
DEFAULT_PANEL_PALETTE,
|
|
247
|
-
),
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
sections.push({
|
|
252
|
-
title: 'Maintenance',
|
|
253
|
-
lines: this.renderMaintenance(width),
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
return buildPanelWorkspace(width, height, {
|
|
257
|
-
title: ' Token Budget',
|
|
258
|
-
intro: 'Live context pressure, session token composition, cache usage, and recent turn deltas.',
|
|
259
|
-
sections,
|
|
260
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
private renderMaintenance(width: number): Line[] {
|
|
266
|
-
const sessionSnapshot = this.sessionReadModel?.getSnapshot();
|
|
267
|
-
const status = evaluateSessionMaintenance({
|
|
268
|
-
configManager: this.configManager,
|
|
269
|
-
currentTokens: this.lastInputTokens,
|
|
270
|
-
contextWindow: this.contextWindow,
|
|
271
|
-
messageCount: sessionSnapshot?.totalTurns,
|
|
272
|
-
sessionMemoryCount: this.sessionMemoryStore.list().length,
|
|
273
|
-
session: sessionSnapshot?.session,
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
const lines: Line[] = [
|
|
277
|
-
this.paintTextLine(` ${status.summary}`, width, C.label),
|
|
278
|
-
];
|
|
279
|
-
|
|
280
|
-
if (status.reasons[0]) {
|
|
281
|
-
lines.push(this.paintTextLine(` ${status.reasons[0]}`, width, C.dim, { dim: true }));
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (status.guidanceMode !== 'off' && status.nextSteps.length > 0) {
|
|
285
|
-
lines.push(buildGuidanceLine(width, status.nextSteps[0]!, 'open the next maintenance action directly', DEFAULT_PANEL_PALETTE));
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return lines;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Renders a compact stacked proportion bar:
|
|
293
|
-
* [########............] (input=cyan, output=purple, cacheR=green, cacheW=yellow)
|
|
294
|
-
*/
|
|
295
|
-
private renderStackedBar(
|
|
296
|
-
width: number,
|
|
297
|
-
u: { input: number; output: number; cacheRead: number; cacheWrite: number },
|
|
298
|
-
label: string,
|
|
299
|
-
): Line[] {
|
|
300
|
-
const lines: Line[] = [];
|
|
301
|
-
|
|
302
|
-
const labelLine = this.paintTextLine(` ${label}:`, width, C.label);
|
|
303
|
-
lines.push(labelLine);
|
|
304
|
-
|
|
305
|
-
const total = u.input + u.output + u.cacheRead + u.cacheWrite;
|
|
306
|
-
if (total === 0) {
|
|
307
|
-
lines.push(this.paintTextLine(' (no data)', width, C.dim, { dim: true }));
|
|
308
|
-
return lines;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const BAR_W = Math.max(10, Math.min(40, width - 4));
|
|
312
|
-
const segments: Array<{ color: string; label: string; count: number; cells: number }> = [
|
|
313
|
-
{ color: C.input, label: 'In', count: u.input, cells: 0 },
|
|
314
|
-
{ color: C.output, label: 'Out', count: u.output, cells: 0 },
|
|
315
|
-
{ color: C.cacheRead, label: 'CR', count: u.cacheRead, cells: 0 },
|
|
316
|
-
{ color: C.cacheWrite, label: 'CW', count: u.cacheWrite, cells: 0 },
|
|
317
|
-
].filter(s => s.count > 0);
|
|
318
|
-
|
|
319
|
-
// Distribute cells proportionally, ensuring they sum to BAR_W
|
|
320
|
-
let assigned = 0;
|
|
321
|
-
for (const seg of segments) {
|
|
322
|
-
seg.cells = Math.floor((seg.count / total) * BAR_W);
|
|
323
|
-
assigned += seg.cells;
|
|
324
|
-
}
|
|
325
|
-
// Give remainder to the largest segment
|
|
326
|
-
const remainder = BAR_W - assigned;
|
|
327
|
-
if (remainder > 0 && segments.length > 0) {
|
|
328
|
-
const largest = segments.reduce((a, b) => (b.count > a.count ? b : a));
|
|
329
|
-
largest.cells += remainder;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// Build bar line
|
|
333
|
-
const usedCells = segments.reduce((sum, seg) => sum + seg.cells, 0);
|
|
334
|
-
lines.push(buildStyledPanelLine(width, [
|
|
335
|
-
{ text: ' [', fg: C.label },
|
|
336
|
-
...segments.map((seg) => ({ text: '#'.repeat(seg.cells), fg: seg.color })),
|
|
337
|
-
{ text: '.'.repeat(Math.max(0, BAR_W - usedCells)), fg: C.barBg },
|
|
338
|
-
{ text: ']', fg: C.label },
|
|
339
|
-
]));
|
|
340
|
-
|
|
341
|
-
// Legend line: In:12.3k Out:4.5k CR:6.7k CW:1.2k Total:24.7k
|
|
342
|
-
const allSegs = [
|
|
343
|
-
{ color: C.input, label: 'In', count: u.input },
|
|
344
|
-
{ color: C.output, label: 'Out', count: u.output },
|
|
345
|
-
{ color: C.cacheRead, label: 'CR', count: u.cacheRead },
|
|
346
|
-
{ color: C.cacheWrite, label: 'CW', count: u.cacheWrite },
|
|
347
|
-
];
|
|
348
|
-
lines.push(buildStyledPanelLine(width, [
|
|
349
|
-
{ text: ' ', fg: C.dim },
|
|
350
|
-
...allSegs.flatMap((seg) => ([
|
|
351
|
-
{ text: `${seg.label}:`, fg: seg.color },
|
|
352
|
-
{ text: `${fmtTok(seg.count)} `, fg: C.value },
|
|
353
|
-
])),
|
|
354
|
-
{ text: ` Total:${fmtTok(total)}`, fg: C.value, bold: true },
|
|
355
|
-
]));
|
|
356
|
-
|
|
357
|
-
return lines;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/** Renders a full-width progress bar for context window fill. */
|
|
361
|
-
private renderContextBar(width: number): Line[] {
|
|
362
|
-
const lines: Line[] = [];
|
|
363
|
-
const pct = this.contextWindow > 0
|
|
364
|
-
? Math.min(1, this.lastInputTokens / this.contextWindow)
|
|
365
|
-
: 0;
|
|
366
|
-
const pctInt = Math.round(pct * 100);
|
|
367
|
-
|
|
368
|
-
// Choose color based on thresholds
|
|
369
|
-
let barColor: string;
|
|
370
|
-
let warnSuffix = '';
|
|
371
|
-
if (pct >= WARN_RED) {
|
|
372
|
-
barColor = C.warnRed;
|
|
373
|
-
warnSuffix = ' ! CRITICAL';
|
|
374
|
-
} else if (pct >= WARN_YELLOW) {
|
|
375
|
-
barColor = C.warnYellow;
|
|
376
|
-
warnSuffix = ' ! HIGH';
|
|
377
|
-
} else {
|
|
378
|
-
barColor = C.good;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
const label = ' Context: ';
|
|
382
|
-
const suffix = ` ${fmtTok(this.lastInputTokens)}/${fmtTok(this.contextWindow)} (${pctInt}%)${warnSuffix}`;
|
|
383
|
-
const BAR_W = Math.max(8, width - label.length - suffix.length - 2);
|
|
384
|
-
const filled = Math.round(pct * BAR_W);
|
|
385
|
-
lines.push(buildStyledPanelLine(width, [
|
|
386
|
-
{ text: label, fg: C.label },
|
|
387
|
-
{ text: '#'.repeat(filled), fg: barColor, dim: pct < WARN_YELLOW },
|
|
388
|
-
{ text: '.'.repeat(Math.max(0, BAR_W - filled)), fg: C.barBg, dim: pct < WARN_YELLOW },
|
|
389
|
-
{ text: suffix, fg: barColor, dim: pct < WARN_YELLOW },
|
|
390
|
-
]));
|
|
391
|
-
return lines;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/** Session cumulative totals with color-coded labels. */
|
|
395
|
-
private renderTotals(width: number): Line[] {
|
|
396
|
-
const lines: Line[] = [];
|
|
397
|
-
lines.push(this.paintTextLine(' Session Totals:', width, C.label));
|
|
398
|
-
|
|
399
|
-
const u = this.sessionUsage;
|
|
400
|
-
const total = u.input + u.output + u.cacheRead + u.cacheWrite;
|
|
401
|
-
|
|
402
|
-
const rows: Array<[string, number, string]> = [
|
|
403
|
-
[' Input: ', u.input, C.input],
|
|
404
|
-
[' Output: ', u.output, C.output],
|
|
405
|
-
[' Cache Read: ', u.cacheRead, C.cacheRead],
|
|
406
|
-
[' Cache Write:', u.cacheWrite, C.cacheWrite],
|
|
407
|
-
[' Total: ', total, C.value],
|
|
408
|
-
];
|
|
409
|
-
|
|
410
|
-
for (const [lbl, val, color] of rows) {
|
|
411
|
-
if (lines.length >= 8) break; // guard — don't overflow the section
|
|
412
|
-
lines.push(buildStyledPanelLine(width, [
|
|
413
|
-
{ text: lbl, fg: C.label },
|
|
414
|
-
{ text: fmtTok(val), fg: color, bold: lbl.includes('Total') },
|
|
415
|
-
]));
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
return lines;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
/** Last N turns table: turn#, in, out, CR, CW, total. */
|
|
422
|
-
private renderTurnHistory(width: number, maxRows: number): Line[] {
|
|
423
|
-
const lines: Line[] = [];
|
|
424
|
-
|
|
425
|
-
// Column headers
|
|
426
|
-
const colLine = createEmptyLine(width);
|
|
427
|
-
const headers = [' #', ' Input', ' Output', ' CR', ' CW', ' Total'];
|
|
428
|
-
const cols = [3, 9, 9, 6, 6, 9];
|
|
429
|
-
let hx = 0;
|
|
430
|
-
for (let i = 0; i < headers.length; i++) {
|
|
431
|
-
const h = headers[i]!;
|
|
432
|
-
for (const ch of h.slice(0, cols[i]!)) {
|
|
433
|
-
if (hx >= width) break;
|
|
434
|
-
colLine[hx++] = createStyledCell(ch, { fg: C.turnHeader, dim: true });
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
lines.push(colLine);
|
|
438
|
-
|
|
439
|
-
const available = Math.max(0, maxRows - 1); // minus col header
|
|
440
|
-
const toShow = this.turnHistory.slice(-Math.max(0, available));
|
|
441
|
-
|
|
442
|
-
toShow.forEach((t, i) => {
|
|
443
|
-
if (lines.length >= maxRows) return;
|
|
444
|
-
const turnNum = this.turnHistory.length - toShow.length + i + 1;
|
|
445
|
-
const total = t.input + t.output + t.cacheRead + t.cacheWrite;
|
|
446
|
-
const cells: Array<[string, string]> = [
|
|
447
|
-
[String(turnNum).padStart(3), C.dim],
|
|
448
|
-
[fmtTok(t.input).padStart(9), C.input],
|
|
449
|
-
[fmtTok(t.output).padStart(9), C.output],
|
|
450
|
-
[fmtTok(t.cacheRead).padStart(6), C.cacheRead],
|
|
451
|
-
[fmtTok(t.cacheWrite).padStart(6), C.cacheWrite],
|
|
452
|
-
[fmtTok(total).padStart(9), C.value],
|
|
453
|
-
];
|
|
454
|
-
lines.push(buildStyledPanelLine(width, cells.map(([val, color]) => ({ text: val, fg: color }))));
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
return lines;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/** Paint a plain text string into a new Line. */
|
|
461
|
-
private paintTextLine(
|
|
462
|
-
text: string,
|
|
463
|
-
width: number,
|
|
464
|
-
fg: string,
|
|
465
|
-
opts: { bold?: boolean; dim?: boolean } = {},
|
|
466
|
-
): Line {
|
|
467
|
-
const line = createEmptyLine(width);
|
|
468
|
-
let x = 0;
|
|
469
|
-
for (const ch of text) {
|
|
470
|
-
if (x >= width) break;
|
|
471
|
-
line[x++] = createStyledCell(ch, { fg, bold: opts.bold, dim: opts.dim });
|
|
472
|
-
}
|
|
473
|
-
return line;
|
|
474
|
-
}
|
|
475
|
-
}
|