@pellux/goodvibes-tui 1.1.0 → 1.9.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 +85 -21
- package/README.md +28 -18
- package/docs/foundation-artifacts/operator-contract.json +4045 -1763
- package/package.json +2 -2
- package/src/audio/spoken-turn-controller.ts +12 -2
- package/src/audio/spoken-turn-wiring.ts +2 -1
- package/src/cli/help.ts +8 -1
- package/src/cli/service-posture.ts +2 -1
- package/src/cli/status.ts +2 -1
- package/src/cli/surface-command.ts +2 -2
- package/src/core/alert-gating.ts +67 -0
- package/src/core/approval-alert.ts +72 -0
- package/src/core/budget-breach-notifier.ts +106 -0
- package/src/core/composer-state.ts +11 -3
- package/src/core/conversation-line-cache.ts +27 -3
- package/src/core/conversation-rendering.ts +90 -14
- package/src/core/conversation.ts +18 -0
- package/src/core/focus-tracker.ts +41 -0
- package/src/core/long-task-notifier.ts +33 -6
- package/src/core/stream-event-wiring.ts +20 -1
- package/src/core/system-message-noise.ts +87 -0
- package/src/core/system-message-router.ts +104 -51
- package/src/core/turn-cancellation.ts +25 -0
- package/src/core/turn-event-wiring.ts +72 -3
- package/src/daemon/cli.ts +29 -2
- package/src/daemon/handlers/register.ts +8 -1
- package/src/daemon/service-commands.ts +329 -0
- package/src/export/cost-utils.ts +36 -0
- package/src/input/autocomplete.ts +27 -1
- package/src/input/command-registry.ts +84 -5
- package/src/input/commands/checkpoint-runtime.ts +280 -0
- package/src/input/commands/codebase-runtime.ts +232 -0
- package/src/input/commands/config.ts +43 -3
- package/src/input/commands/eval.ts +1 -1
- package/src/input/commands/health-runtime.ts +10 -2
- package/src/input/commands/image-runtime.ts +112 -0
- package/src/input/commands/intelligence-runtime.ts +11 -1
- package/src/input/commands/local-auth-runtime.ts +4 -1
- package/src/input/commands/local-runtime.ts +9 -3
- package/src/input/commands/marketplace-runtime.ts +2 -2
- package/src/input/commands/memory.ts +73 -35
- package/src/input/commands/operator-panel-runtime.ts +71 -31
- package/src/input/commands/planning-runtime.ts +116 -4
- package/src/input/commands/platform-sandbox-runtime.ts +1 -6
- package/src/input/commands/plugin-runtime.ts +2 -2
- package/src/input/commands/policy-dispatch.ts +21 -0
- package/src/input/commands/provider-accounts-runtime.ts +1 -1
- package/src/input/commands/qrcode-runtime.ts +26 -6
- package/src/input/commands/recall-review.ts +35 -0
- package/src/input/commands/remote-runtime-setup.ts +5 -3
- package/src/input/commands/remote-runtime.ts +3 -3
- package/src/input/commands/runtime-services.ts +54 -13
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-content.ts +20 -9
- package/src/input/commands/session-workflow.ts +16 -1
- package/src/input/commands/settings-sync-runtime.ts +16 -4
- package/src/input/commands/shell-core.ts +25 -8
- package/src/input/commands/skills-runtime.ts +2 -8
- package/src/input/commands/subscription-runtime.ts +2 -2
- package/src/input/commands/test-runtime.ts +277 -0
- package/src/input/commands/websearch-runtime.ts +101 -0
- package/src/input/commands/work-plan-runtime.ts +36 -10
- package/src/input/commands/workstream-runtime.ts +488 -0
- package/src/input/commands.ts +12 -0
- package/src/input/config-modal-types.ts +175 -0
- package/src/input/config-modal.ts +592 -0
- package/src/input/feed-context-factory.ts +10 -8
- package/src/input/handler-command-route.ts +37 -20
- package/src/input/handler-content-actions.ts +17 -2
- package/src/input/handler-feed-routes.ts +74 -114
- package/src/input/handler-feed.ts +79 -27
- package/src/input/handler-interactions.ts +1 -3
- package/src/input/handler-modal-routes.ts +136 -6
- package/src/input/handler-modal-stack.ts +14 -7
- package/src/input/handler-modal-token-routes.ts +16 -49
- package/src/input/handler-onboarding-daemon-adopt.ts +149 -0
- package/src/input/handler-onboarding.ts +71 -59
- package/src/input/handler-picker-routes.ts +26 -102
- package/src/input/handler-shortcuts.ts +74 -14
- package/src/input/handler-types.ts +2 -6
- package/src/input/handler-ui-state.ts +10 -22
- package/src/input/handler.ts +12 -29
- package/src/input/keybindings.ts +23 -8
- package/src/input/model-picker-types.ts +24 -6
- package/src/input/model-picker.ts +77 -2
- package/src/input/onboarding/onboarding-runtime-status.ts +10 -1
- package/src/input/onboarding/onboarding-wizard-apply.ts +8 -1
- package/src/input/onboarding/onboarding-wizard-constants.ts +4 -1
- package/src/input/onboarding/onboarding-wizard-network-adopt.ts +136 -0
- package/src/input/onboarding/onboarding-wizard-steps.ts +9 -6
- package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
- package/src/input/panel-integration-actions.ts +9 -170
- package/src/input/panel-mouse-geometry.ts +97 -0
- package/src/input/panel-paste-flood-guard.ts +86 -0
- package/src/input/selection-modal.ts +11 -0
- package/src/input/session-picker-modal.ts +44 -4
- package/src/input/settings-modal-data.ts +146 -2
- package/src/input/settings-modal-mutations.ts +6 -4
- package/src/input/settings-modal-types.ts +4 -2
- package/src/main.ts +49 -51
- package/src/panels/agent-inspector-shared.ts +2 -1
- package/src/panels/base-panel.ts +22 -1
- package/src/panels/builtin/agent.ts +21 -107
- package/src/panels/builtin/development.ts +15 -61
- package/src/panels/builtin/knowledge.ts +8 -32
- package/src/panels/builtin/operations.ts +84 -428
- package/src/panels/builtin/session.ts +21 -112
- package/src/panels/builtin/shared.ts +18 -46
- package/src/panels/builtin-modals.ts +218 -0
- package/src/panels/builtin-panels.ts +5 -0
- package/src/panels/cost-tracker-panel.ts +36 -10
- package/src/panels/diff-panel.ts +12 -43
- package/src/panels/eval-registry.ts +60 -0
- package/src/panels/fleet-deep-link.ts +31 -0
- package/src/panels/fleet-panel-format.ts +62 -0
- package/src/panels/fleet-panel-worktree-detail.ts +48 -0
- package/src/panels/fleet-panel.ts +758 -0
- package/src/panels/fleet-read-model.ts +520 -0
- package/src/panels/fleet-steer.ts +125 -0
- package/src/panels/fleet-stop.ts +153 -0
- package/src/panels/fleet-tabs.ts +230 -0
- package/src/panels/fleet-transcript.ts +356 -0
- package/src/panels/index.ts +7 -31
- package/src/panels/modals/hooks-modal.ts +187 -0
- package/src/panels/modals/keybindings-modal.ts +166 -0
- package/src/panels/modals/knowledge-modal.ts +158 -0
- package/src/panels/modals/local-auth-modal.ts +132 -0
- package/src/panels/modals/marketplace-modal.ts +218 -0
- package/src/panels/modals/memory-modal.ts +180 -0
- package/src/panels/modals/modal-surface-helpers.ts +54 -0
- package/src/panels/modals/modal-theme.ts +42 -0
- package/src/panels/modals/pairing-modal.ts +163 -0
- package/src/panels/modals/planning-modal.ts +304 -0
- package/src/panels/modals/plugins-modal.ts +174 -0
- package/src/panels/modals/policy-modal.ts +256 -0
- package/src/panels/modals/provider-health-modal.ts +136 -0
- package/src/panels/modals/remote-modal.ts +115 -0
- package/src/panels/modals/sandbox-modal.ts +150 -0
- package/src/panels/modals/security-modal.ts +217 -0
- package/src/panels/modals/services-modal.ts +179 -0
- package/src/panels/modals/settings-sync-modal.ts +142 -0
- package/src/panels/modals/skills-modal.ts +189 -0
- package/src/panels/modals/subscription-modal.ts +172 -0
- package/src/panels/modals/work-plan-modal.ts +177 -0
- package/src/panels/panel-confirm-overlay.ts +72 -0
- package/src/panels/panel-manager.ts +123 -9
- package/src/panels/polish-core.ts +38 -25
- package/src/panels/project-planning-answer-actions.ts +8 -11
- package/src/panels/skills-panel.ts +7 -51
- package/src/panels/types.ts +37 -0
- package/src/permissions/hunk-selection.ts +179 -0
- package/src/permissions/prompt.ts +216 -13
- package/src/renderer/autocomplete-overlay.ts +28 -2
- package/src/renderer/compaction-history-modal.ts +19 -3
- package/src/renderer/compaction-preview.ts +13 -2
- package/src/renderer/compaction-quality.ts +100 -0
- package/src/renderer/compositor.ts +2 -3
- package/src/renderer/config-modal.ts +95 -0
- package/src/renderer/conversation-overlays.ts +10 -17
- package/src/renderer/fleet-tab-strip.ts +56 -0
- package/src/renderer/footer-tips.ts +15 -3
- package/src/renderer/fullscreen-primitives.ts +32 -22
- package/src/renderer/git-status.ts +43 -1
- package/src/renderer/help-overlay.ts +2 -2
- package/src/renderer/layout.ts +0 -4
- package/src/renderer/markdown.ts +7 -3
- package/src/renderer/modal-factory.ts +25 -20
- package/src/renderer/model-workspace.ts +62 -4
- package/src/renderer/overlay-box.ts +21 -17
- package/src/renderer/panel-workspace-bar.ts +8 -2
- package/src/renderer/process-indicator.ts +14 -3
- package/src/renderer/selection-modal-overlay.ts +6 -1
- package/src/renderer/session-picker-modal.ts +196 -3
- package/src/renderer/settings-modal-helpers.ts +2 -0
- package/src/renderer/settings-modal.ts +7 -0
- package/src/renderer/shell-surface.ts +8 -1
- package/src/renderer/status-glyphs.ts +14 -15
- package/src/renderer/system-message.ts +15 -3
- package/src/renderer/terminal-bg-probe.ts +339 -0
- package/src/renderer/terminal-escapes.ts +20 -0
- package/src/renderer/theme-mode-config.ts +67 -0
- package/src/renderer/theme.ts +91 -1
- package/src/renderer/thinking.ts +11 -3
- package/src/renderer/tool-call.ts +15 -9
- package/src/renderer/tool-result-summary.ts +148 -0
- package/src/renderer/turn-injection.ts +133 -0
- package/src/renderer/ui-factory.ts +154 -85
- package/src/renderer/ui-primitives.ts +30 -129
- package/src/runtime/bootstrap-command-context.ts +27 -1
- package/src/runtime/bootstrap-command-parts.ts +41 -10
- package/src/runtime/bootstrap-core.ts +47 -15
- package/src/runtime/bootstrap-hook-bridge.ts +7 -0
- package/src/runtime/bootstrap-shell.ts +48 -17
- package/src/runtime/bootstrap.ts +129 -22
- package/src/runtime/code-index-services.ts +135 -0
- package/src/runtime/legacy-daemon-migration.ts +516 -0
- package/src/runtime/memory-fold.ts +26 -0
- package/src/runtime/onboarding/derivation.ts +7 -2
- package/src/runtime/onboarding/snapshot.ts +27 -1
- package/src/runtime/onboarding/types.ts +31 -1
- package/src/runtime/operator-token-cleanup.ts +82 -1
- package/src/runtime/orchestrator-core-services.ts +59 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- package/src/runtime/resume-notice.ts +209 -0
- package/src/runtime/services.ts +98 -46
- package/src/runtime/session-inbound-inputs.ts +252 -0
- package/src/runtime/session-spine-transport.ts +64 -0
- package/src/runtime/terminal-output-guard.ts +15 -8
- package/src/runtime/ui-services.ts +27 -3
- package/src/runtime/workstream-services.ts +327 -0
- package/src/runtime/wrfc-persistence.ts +124 -17
- package/src/shell/blocking-input.ts +68 -4
- package/src/shell/recovery-input-helpers.ts +170 -1
- package/src/shell/ui-openers.ts +171 -26
- package/src/utils/format-duration.ts +8 -18
- package/src/utils/splash-lines.ts +1 -1
- package/src/utils/terminal-width.ts +52 -0
- package/src/version.ts +1 -1
- package/src/panels/agent-inspector-panel.ts +0 -786
- package/src/panels/approval-panel.ts +0 -252
- package/src/panels/automation-control-panel.ts +0 -479
- package/src/panels/cockpit-panel.ts +0 -481
- package/src/panels/cockpit-read-model.ts +0 -238
- package/src/panels/communication-panel.ts +0 -256
- package/src/panels/control-plane-panel.ts +0 -470
- package/src/panels/debug-panel.ts +0 -615
- package/src/panels/docs-panel.ts +0 -384
- package/src/panels/eval-panel.ts +0 -627
- package/src/panels/file-explorer-panel.ts +0 -673
- package/src/panels/file-preview-panel.ts +0 -517
- package/src/panels/hooks-panel.ts +0 -401
- package/src/panels/incident-review-panel.ts +0 -406
- package/src/panels/intelligence-panel.ts +0 -383
- package/src/panels/knowledge-graph-panel.ts +0 -515
- package/src/panels/marketplace-panel.ts +0 -399
- package/src/panels/memory-panel.ts +0 -558
- package/src/panels/ops-control-panel.ts +0 -329
- package/src/panels/ops-strategy-panel.ts +0 -321
- package/src/panels/orchestration-panel.ts +0 -465
- package/src/panels/panel-list-panel.ts +0 -566
- package/src/panels/plan-dashboard-panel.ts +0 -707
- package/src/panels/policy-panel.ts +0 -517
- package/src/panels/project-planning-panel.ts +0 -731
- package/src/panels/provider-health-panel.ts +0 -678
- package/src/panels/provider-health-tracker.ts +0 -310
- package/src/panels/provider-health-views.ts +0 -567
- package/src/panels/qr-panel.ts +0 -280
- package/src/panels/remote-panel.ts +0 -534
- package/src/panels/routes-panel.ts +0 -241
- package/src/panels/sandbox-panel.ts +0 -456
- package/src/panels/security-panel.ts +0 -447
- package/src/panels/services-panel.ts +0 -329
- package/src/panels/session-browser-panel.ts +0 -496
- package/src/panels/settings-sync-panel.ts +0 -398
- package/src/panels/subscription-panel.ts +0 -342
- package/src/panels/symbol-outline-panel.ts +0 -619
- package/src/panels/system-messages-panel.ts +0 -364
- package/src/panels/tasks-panel.ts +0 -608
- package/src/panels/thinking-panel.ts +0 -333
- package/src/panels/tool-inspector-panel.ts +0 -537
- package/src/panels/work-plan-panel.ts +0 -540
- package/src/panels/worktree-panel.ts +0 -360
- package/src/panels/wrfc-panel.ts +0 -790
- package/src/renderer/agent-detail-modal.ts +0 -466
- package/src/renderer/live-tail-modal.ts +0 -156
- package/src/renderer/process-modal.ts +0 -671
- package/src/renderer/qr-renderer.ts +0 -120
|
@@ -1,615 +0,0 @@
|
|
|
1
|
-
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
2
|
-
import { createEmptyLine, type Line } from '../types/grid.ts';
|
|
3
|
-
import { fitDisplay, truncateDisplay } from '../utils/terminal-width.ts';
|
|
4
|
-
import type { TurnEvent } from '@/runtime/index.ts';
|
|
5
|
-
import type { UiEventFeed } from '../runtime/ui-events.ts';
|
|
6
|
-
import type { Orchestrator } from '../core/orchestrator';
|
|
7
|
-
import {
|
|
8
|
-
buildAlignedRow,
|
|
9
|
-
buildBodyText,
|
|
10
|
-
buildEmptyState,
|
|
11
|
-
buildKeyboardHints,
|
|
12
|
-
buildKeyValueLine,
|
|
13
|
-
buildPanelLine,
|
|
14
|
-
buildPanelWorkspace,
|
|
15
|
-
buildStyledPanelLine,
|
|
16
|
-
resolvePrimaryScrollableSection,
|
|
17
|
-
DEFAULT_PANEL_PALETTE,
|
|
18
|
-
extendPalette,
|
|
19
|
-
type ColumnSpec,
|
|
20
|
-
type PanelWorkspaceSection,
|
|
21
|
-
} from './polish.ts';
|
|
22
|
-
import { type ConfirmState, handleConfirmInput, renderConfirmLines } from './confirm-state.ts';
|
|
23
|
-
import { abbreviateCount } from '../utils/format-number.ts';
|
|
24
|
-
import { formatLatencyMs } from '../utils/format-duration.ts';
|
|
25
|
-
import { calcSessionCost, isModelPriced } from '../export/cost-utils.ts';
|
|
26
|
-
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
// Types
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
|
|
31
|
-
export type ApiCallStatus = 'ok' | 'error';
|
|
32
|
-
|
|
33
|
-
export interface ApiCallEntry {
|
|
34
|
-
/** Wall-clock timestamp when the call completed. */
|
|
35
|
-
ts: number;
|
|
36
|
-
/** Provider name (e.g. "anthropic"). */
|
|
37
|
-
provider: string;
|
|
38
|
-
/** Model id (e.g. "claude-sonnet-4-5"). */
|
|
39
|
-
model: string;
|
|
40
|
-
/** Input tokens for this call. */
|
|
41
|
-
inputTokens: number;
|
|
42
|
-
/** Output tokens for this call. */
|
|
43
|
-
outputTokens: number;
|
|
44
|
-
/** End-to-end latency in ms (stream-start → llm-response, or turn-start → llm-response). */
|
|
45
|
-
latencyMs: number;
|
|
46
|
-
/**
|
|
47
|
-
* HTTP-level status code hint. Only ever set from a real signal (scraped
|
|
48
|
-
* from an error message); 0 means no real code is known. Never fabricated
|
|
49
|
-
* (WO-137 dropped the old always-200-on-success convention — this layer
|
|
50
|
-
* doesn't actually observe HTTP status codes on success).
|
|
51
|
-
*/
|
|
52
|
-
statusCode: number;
|
|
53
|
-
/** 'ok' | 'error' */
|
|
54
|
-
status: ApiCallStatus;
|
|
55
|
-
/** Trimmed error message when status === 'error'. */
|
|
56
|
-
errorMessage?: string;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// ---------------------------------------------------------------------------
|
|
60
|
-
// Constants / limits
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
|
-
|
|
63
|
-
const MAX_CALL_LOG = 50;
|
|
64
|
-
const MAX_ERROR_LOG = 20;
|
|
65
|
-
const AGES_REFRESH_MS = 1_000;
|
|
66
|
-
|
|
67
|
-
// ---------------------------------------------------------------------------
|
|
68
|
-
// Colors
|
|
69
|
-
// ---------------------------------------------------------------------------
|
|
70
|
-
|
|
71
|
-
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
72
|
-
provName: '#e2e8f0',
|
|
73
|
-
input: '#00ffff',
|
|
74
|
-
output: '#d000ff',
|
|
75
|
-
colHdr: '242',
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
const LATENCY_WARN_MS = 2_000;
|
|
79
|
-
const LATENCY_BAD_MS = 5_000;
|
|
80
|
-
|
|
81
|
-
// ---------------------------------------------------------------------------
|
|
82
|
-
// Helpers
|
|
83
|
-
// ---------------------------------------------------------------------------
|
|
84
|
-
|
|
85
|
-
function fmtTok(n: number): string {
|
|
86
|
-
return abbreviateCount(n, { guard: 10_000, decimals: 1, mDecimals: 2 });
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function fmtMs(ms: number): string {
|
|
90
|
-
return formatLatencyMs(ms);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function fmtAgo(ts: number): string {
|
|
94
|
-
const sec = Math.max(0, Math.floor((Date.now() - ts) / 1000));
|
|
95
|
-
if (sec < 60) return `${sec}s ago`;
|
|
96
|
-
if (sec < 3600) return `${Math.floor(sec / 60)}m ago`;
|
|
97
|
-
return `${Math.floor(sec / 3600)}h ago`;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/** unpriced=true renders an honest "unpriced" marker instead of a $0 that could be mistaken for a real zero cost. */
|
|
101
|
-
function fmtUsd(value: number, unpriced = false): string {
|
|
102
|
-
if (unpriced) return 'unpriced';
|
|
103
|
-
if (value <= 0) return '$0';
|
|
104
|
-
return value >= 1 ? `$${value.toFixed(2)}` : `$${value.toFixed(4)}`;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function callCost(entry: ApiCallEntry): number {
|
|
108
|
-
return calcSessionCost(entry.inputTokens, entry.outputTokens, 0, 0, entry.model);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function latColor(ms: number): string {
|
|
112
|
-
if (ms >= LATENCY_BAD_MS) return C.bad;
|
|
113
|
-
if (ms >= LATENCY_WARN_MS) return C.warn;
|
|
114
|
-
return C.good;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function statusCodeFromError(msg: string): number {
|
|
118
|
-
const m = msg.match(/\b(4\d{2}|5\d{2})\b/);
|
|
119
|
-
return m ? parseInt(m[1]!, 10) : 0;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const CALL_COLUMNS: ColumnSpec[] = [
|
|
123
|
-
{ width: 8 }, // time
|
|
124
|
-
{ width: 1 }, // status glyph
|
|
125
|
-
{ width: 12 }, // provider
|
|
126
|
-
{ width: 26 }, // model (+ scraped status code suffix)
|
|
127
|
-
{ width: 8, align: 'right' }, // in
|
|
128
|
-
{ width: 8, align: 'right' }, // out
|
|
129
|
-
{ width: 8, align: 'right' }, // latency
|
|
130
|
-
{ width: 9, align: 'right' }, // cost
|
|
131
|
-
];
|
|
132
|
-
|
|
133
|
-
// ---------------------------------------------------------------------------
|
|
134
|
-
// DebugPanel
|
|
135
|
-
// ---------------------------------------------------------------------------
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Real-time API debug panel.
|
|
139
|
-
*
|
|
140
|
-
* Migrated onto ScrollableListPanel<ApiCallEntry> (WO-137) so nav
|
|
141
|
-
* (up/down/j/k/pageup/pagedown/g/G), the '/' inline filter, and a
|
|
142
|
-
* selected-row detail section come from the shared base class instead of a
|
|
143
|
-
* bespoke render (SystemMessagesPanel precedent).
|
|
144
|
-
*
|
|
145
|
-
* Shows per-call log (model, provider, input/output tokens, latency, status,
|
|
146
|
-
* cost), running session call total, and error history. Subscribes to typed
|
|
147
|
-
* turn runtime events.
|
|
148
|
-
*/
|
|
149
|
-
export class DebugPanel extends ScrollableListPanel<ApiCallEntry> {
|
|
150
|
-
private _unsubs: Array<() => void> = [];
|
|
151
|
-
|
|
152
|
-
// Timing state
|
|
153
|
-
private _turnStartMs: number | null = null;
|
|
154
|
-
private _streamStartMs: number | null = null;
|
|
155
|
-
|
|
156
|
-
// Token delta tracking (requires wired orchestrator)
|
|
157
|
-
private _orchestrator: Orchestrator | null = null;
|
|
158
|
-
private _prevInput = 0;
|
|
159
|
-
private _prevOutput = 0;
|
|
160
|
-
|
|
161
|
-
// Real provider/model attribution for TURN_ERROR rows: the provider/model
|
|
162
|
-
// named on the in-flight LLM request is the authoritative attribution
|
|
163
|
-
// target when the turn subsequently errors (provider-health-panel.ts
|
|
164
|
-
// precedent) — never a fabricated 'unknown'.
|
|
165
|
-
private _inflightProvider: string | null = null;
|
|
166
|
-
private _inflightModel: string | null = null;
|
|
167
|
-
|
|
168
|
-
// Session data
|
|
169
|
-
private _calls: ApiCallEntry[] = [];
|
|
170
|
-
private _errors: ApiCallEntry[] = [];
|
|
171
|
-
private _totalCalls = 0;
|
|
172
|
-
private _totalErrors = 0;
|
|
173
|
-
|
|
174
|
-
// c=clear confirmation
|
|
175
|
-
private confirm: ConfirmState<'clear'> | null = null;
|
|
176
|
-
|
|
177
|
-
// Re-render timer so "Xs ago" ages stay live even without new events.
|
|
178
|
-
private agesTimerId: ReturnType<typeof setInterval> | null = null;
|
|
179
|
-
|
|
180
|
-
constructor(
|
|
181
|
-
private readonly turnEvents: UiEventFeed<TurnEvent>,
|
|
182
|
-
private readonly requestRender: () => void = () => {},
|
|
183
|
-
) {
|
|
184
|
-
super('debug', 'Debug', '▧', 'incidents-diagnostics');
|
|
185
|
-
this.filterEnabled = true;
|
|
186
|
-
this.filterLabel = 'Filter calls';
|
|
187
|
-
this._subscribe();
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// -------------------------------------------------------------------------
|
|
191
|
-
// External wiring
|
|
192
|
-
// -------------------------------------------------------------------------
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Optionally wire to the main Orchestrator so per-call token deltas can be
|
|
196
|
-
* computed. Call once after construction.
|
|
197
|
-
*/
|
|
198
|
-
wireOrchestrator(orchestrator: Orchestrator): void {
|
|
199
|
-
this._orchestrator = orchestrator;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// -------------------------------------------------------------------------
|
|
203
|
-
// Event subscription
|
|
204
|
-
// -------------------------------------------------------------------------
|
|
205
|
-
|
|
206
|
-
private _subscribe(): void {
|
|
207
|
-
this._unsubs.push(
|
|
208
|
-
this.turnEvents.on('TURN_SUBMITTED', () => {
|
|
209
|
-
this._turnStartMs = Date.now();
|
|
210
|
-
this._streamStartMs = null;
|
|
211
|
-
}),
|
|
212
|
-
);
|
|
213
|
-
|
|
214
|
-
this._unsubs.push(
|
|
215
|
-
this.turnEvents.on('STREAM_START', () => {
|
|
216
|
-
this._streamStartMs = Date.now();
|
|
217
|
-
}),
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
this._unsubs.push(
|
|
221
|
-
this.turnEvents.on('LLM_REQUEST_STARTED', (env) => {
|
|
222
|
-
this._inflightProvider = env.provider;
|
|
223
|
-
this._inflightModel = env.model;
|
|
224
|
-
}),
|
|
225
|
-
);
|
|
226
|
-
|
|
227
|
-
this._unsubs.push(
|
|
228
|
-
this.turnEvents.on('LLM_RESPONSE_RECEIVED', (env) => {
|
|
229
|
-
const now = Date.now();
|
|
230
|
-
const latencyMs =
|
|
231
|
-
this._streamStartMs !== null
|
|
232
|
-
? now - this._streamStartMs
|
|
233
|
-
: this._turnStartMs !== null
|
|
234
|
-
? now - this._turnStartMs
|
|
235
|
-
: 0;
|
|
236
|
-
this._streamStartMs = null;
|
|
237
|
-
this._inflightProvider = null;
|
|
238
|
-
this._inflightModel = null;
|
|
239
|
-
|
|
240
|
-
// Compute per-call token delta if orchestrator is wired
|
|
241
|
-
let inputTokens = env.inputTokens + (env.cacheReadTokens ?? 0) + (env.cacheWriteTokens ?? 0);
|
|
242
|
-
let outputTokens = env.outputTokens;
|
|
243
|
-
if (this._orchestrator) {
|
|
244
|
-
const cu = this._orchestrator.usage;
|
|
245
|
-
inputTokens = Math.max(0, cu.input - this._prevInput);
|
|
246
|
-
outputTokens = Math.max(0, cu.output - this._prevOutput);
|
|
247
|
-
this._prevInput = cu.input;
|
|
248
|
-
this._prevOutput = cu.output;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const entry: ApiCallEntry = {
|
|
252
|
-
ts: now,
|
|
253
|
-
provider: env.provider,
|
|
254
|
-
model: env.model,
|
|
255
|
-
inputTokens,
|
|
256
|
-
outputTokens,
|
|
257
|
-
latencyMs,
|
|
258
|
-
// WO-137: no fabricated 200 — this layer never observes a real
|
|
259
|
-
// HTTP status code on success, so 0 means "no code, just OK".
|
|
260
|
-
statusCode: 0,
|
|
261
|
-
status: 'ok',
|
|
262
|
-
};
|
|
263
|
-
this._pushCall(entry);
|
|
264
|
-
this.markDirty();
|
|
265
|
-
this.requestRender();
|
|
266
|
-
}),
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
this._unsubs.push(
|
|
270
|
-
this.turnEvents.on('TURN_ERROR', (env) => {
|
|
271
|
-
this._streamStartMs = null;
|
|
272
|
-
this._turnStartMs = null;
|
|
273
|
-
|
|
274
|
-
const msg = env.error;
|
|
275
|
-
const code = statusCodeFromError(msg);
|
|
276
|
-
|
|
277
|
-
const entry: ApiCallEntry = {
|
|
278
|
-
ts: Date.now(),
|
|
279
|
-
provider: this._inflightProvider ?? 'n/a',
|
|
280
|
-
model: this._inflightModel ?? 'n/a',
|
|
281
|
-
inputTokens: 0,
|
|
282
|
-
outputTokens: 0,
|
|
283
|
-
latencyMs: 0,
|
|
284
|
-
statusCode: code,
|
|
285
|
-
status: 'error',
|
|
286
|
-
errorMessage: msg.slice(0, 120),
|
|
287
|
-
};
|
|
288
|
-
this._inflightProvider = null;
|
|
289
|
-
this._inflightModel = null;
|
|
290
|
-
this._pushCall(entry);
|
|
291
|
-
this._pushError(entry);
|
|
292
|
-
this.markDirty();
|
|
293
|
-
this.requestRender();
|
|
294
|
-
}),
|
|
295
|
-
);
|
|
296
|
-
|
|
297
|
-
this._unsubs.push(
|
|
298
|
-
this.turnEvents.on('TURN_COMPLETED', () => {
|
|
299
|
-
this._inflightProvider = null;
|
|
300
|
-
this._inflightModel = null;
|
|
301
|
-
}),
|
|
302
|
-
);
|
|
303
|
-
|
|
304
|
-
this._unsubs.push(
|
|
305
|
-
this.turnEvents.on('TURN_CANCEL', () => {
|
|
306
|
-
this._inflightProvider = null;
|
|
307
|
-
this._inflightModel = null;
|
|
308
|
-
}),
|
|
309
|
-
);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
private _pushCall(entry: ApiCallEntry): void {
|
|
313
|
-
this._totalCalls++;
|
|
314
|
-
// Follow-mode: only auto-jump to the new row when the selection was
|
|
315
|
-
// already at (or past) the previous tail — mirrors the SystemMessagesPanel
|
|
316
|
-
// "don't yank the cursor while the user is reviewing history" contract.
|
|
317
|
-
const wasAtTail = this._calls.length === 0 || this.selectedIndex >= this._calls.length - 1;
|
|
318
|
-
this._calls.push(entry);
|
|
319
|
-
if (this._calls.length > MAX_CALL_LOG) {
|
|
320
|
-
this._calls.shift();
|
|
321
|
-
if (this.selectedIndex > 0) this.selectedIndex--;
|
|
322
|
-
}
|
|
323
|
-
if (wasAtTail) this.selectedIndex = this._calls.length - 1;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
private _pushError(entry: ApiCallEntry): void {
|
|
327
|
-
this._totalErrors++;
|
|
328
|
-
this._errors.push(entry);
|
|
329
|
-
if (this._errors.length > MAX_ERROR_LOG) this._errors.shift();
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// -------------------------------------------------------------------------
|
|
333
|
-
// Lifecycle
|
|
334
|
-
// -------------------------------------------------------------------------
|
|
335
|
-
|
|
336
|
-
override onActivate(): void {
|
|
337
|
-
super.onActivate();
|
|
338
|
-
this._startAgesTimer();
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
override onDeactivate(): void {
|
|
342
|
-
this._stopAgesTimer();
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
override onDestroy(): void {
|
|
346
|
-
this._stopAgesTimer();
|
|
347
|
-
for (const unsub of this._unsubs) unsub();
|
|
348
|
-
this._unsubs = [];
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
private _startAgesTimer(): void {
|
|
352
|
-
if (this.agesTimerId) return;
|
|
353
|
-
this.agesTimerId = this.registerTimer(setInterval(() => {
|
|
354
|
-
if (this._calls.length === 0) return;
|
|
355
|
-
this.markDirty();
|
|
356
|
-
this.requestRender();
|
|
357
|
-
}, AGES_REFRESH_MS));
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
private _stopAgesTimer(): void {
|
|
361
|
-
if (this.agesTimerId) {
|
|
362
|
-
this.clearTimer(this.agesTimerId);
|
|
363
|
-
this.agesTimerId = null;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// -------------------------------------------------------------------------
|
|
368
|
-
// ScrollableListPanel contract
|
|
369
|
-
// -------------------------------------------------------------------------
|
|
370
|
-
|
|
371
|
-
protected getItems(): readonly ApiCallEntry[] {
|
|
372
|
-
return this._calls;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
protected override getPalette() { return C; }
|
|
376
|
-
protected override getEmptyStateMessage() { return ' No calls yet'; }
|
|
377
|
-
|
|
378
|
-
protected override filterMatches(entry: ApiCallEntry, q: string): boolean {
|
|
379
|
-
return entry.provider.toLowerCase().includes(q)
|
|
380
|
-
|| entry.model.toLowerCase().includes(q)
|
|
381
|
-
|| entry.status.includes(q)
|
|
382
|
-
|| (entry.errorMessage?.toLowerCase().includes(q) ?? false);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
protected renderItem(entry: ApiCallEntry, _index: number, selected: boolean, width: number): Line {
|
|
386
|
-
const statusChar = entry.status === 'ok' ? '✓' : '✕';
|
|
387
|
-
const statusFg = entry.status === 'ok' ? C.good : C.bad;
|
|
388
|
-
const codeSuffix = entry.status === 'error' && entry.statusCode > 0 ? ` [${entry.statusCode}]` : '';
|
|
389
|
-
return buildAlignedRow(
|
|
390
|
-
width,
|
|
391
|
-
[
|
|
392
|
-
{ text: fmtAgo(entry.ts), fg: C.dim },
|
|
393
|
-
{ text: statusChar, fg: statusFg },
|
|
394
|
-
{ text: entry.provider, fg: C.provName },
|
|
395
|
-
{ text: entry.model + codeSuffix, fg: C.value },
|
|
396
|
-
{ text: fmtTok(entry.inputTokens), fg: C.input },
|
|
397
|
-
{ text: fmtTok(entry.outputTokens), fg: C.output },
|
|
398
|
-
{ text: fmtMs(entry.latencyMs), fg: latColor(entry.latencyMs) },
|
|
399
|
-
{ text: fmtUsd(callCost(entry), !isModelPriced(entry.model)), fg: C.value },
|
|
400
|
-
],
|
|
401
|
-
CALL_COLUMNS,
|
|
402
|
-
{ selected, selectedBg: C.selectBg },
|
|
403
|
-
);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// -------------------------------------------------------------------------
|
|
407
|
-
// Input
|
|
408
|
-
// -------------------------------------------------------------------------
|
|
409
|
-
|
|
410
|
-
handleInput(key: string): boolean {
|
|
411
|
-
const confirmResult = handleConfirmInput(this.confirm, key);
|
|
412
|
-
if (confirmResult === 'confirmed') {
|
|
413
|
-
this.confirm = null;
|
|
414
|
-
this._calls = [];
|
|
415
|
-
this._errors = [];
|
|
416
|
-
this._totalCalls = 0;
|
|
417
|
-
this._totalErrors = 0;
|
|
418
|
-
this.selectedIndex = 0;
|
|
419
|
-
this.markDirty();
|
|
420
|
-
return true;
|
|
421
|
-
}
|
|
422
|
-
if (confirmResult === 'cancelled') {
|
|
423
|
-
this.confirm = null;
|
|
424
|
-
this.markDirty();
|
|
425
|
-
return true;
|
|
426
|
-
}
|
|
427
|
-
if (confirmResult === 'absorbed') return true;
|
|
428
|
-
|
|
429
|
-
if (!this.filterActive && key === 'c') {
|
|
430
|
-
if (this._calls.length === 0) return false;
|
|
431
|
-
this.confirm = { subject: 'clear', label: `${this._calls.length} API call(s)`, verb: 'Clear' };
|
|
432
|
-
this.markDirty();
|
|
433
|
-
return true;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
return super.handleInput(key);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// -------------------------------------------------------------------------
|
|
440
|
-
// Rendering
|
|
441
|
-
// -------------------------------------------------------------------------
|
|
442
|
-
|
|
443
|
-
override render(width: number, height: number): Line[] {
|
|
444
|
-
this.clampSelection();
|
|
445
|
-
|
|
446
|
-
if (this.confirm) {
|
|
447
|
-
const lines = buildPanelWorkspace(width, height, {
|
|
448
|
-
title: ' API Debug',
|
|
449
|
-
sections: [{ title: 'Confirmation', lines: renderConfirmLines(width, this.confirm) }],
|
|
450
|
-
palette: C,
|
|
451
|
-
});
|
|
452
|
-
while (lines.length < height) lines.push(createEmptyLine(width));
|
|
453
|
-
return lines.slice(0, height);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
const intro = 'Recent provider calls, token deltas, latency, status codes, and cost, with error history.';
|
|
457
|
-
|
|
458
|
-
if (this._calls.length === 0) {
|
|
459
|
-
const lines = buildPanelWorkspace(width, height, {
|
|
460
|
-
title: ' API Debug',
|
|
461
|
-
intro,
|
|
462
|
-
sections: [{
|
|
463
|
-
title: 'Session',
|
|
464
|
-
lines: buildEmptyState(
|
|
465
|
-
width,
|
|
466
|
-
this.getEmptyStateMessage(),
|
|
467
|
-
'Completed provider calls and API failures will appear here with timing, token counts, status, and cost.',
|
|
468
|
-
[],
|
|
469
|
-
C,
|
|
470
|
-
),
|
|
471
|
-
}],
|
|
472
|
-
palette: C,
|
|
473
|
-
});
|
|
474
|
-
while (lines.length < height) lines.push(createEmptyLine(width));
|
|
475
|
-
return lines.slice(0, height);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
const summarySection: PanelWorkspaceSection = { title: 'Session', lines: this._renderSummary(width) };
|
|
479
|
-
const filterSection: PanelWorkspaceSection = { lines: [this.buildFilterLine(width)] };
|
|
480
|
-
|
|
481
|
-
const visible = this.getVisibleItems();
|
|
482
|
-
this.selectedIndex = Math.max(0, Math.min(this.selectedIndex, Math.max(0, visible.length - 1)));
|
|
483
|
-
const selected = this.getSelectedItem();
|
|
484
|
-
|
|
485
|
-
const rows: Line[] = visible.length > 0
|
|
486
|
-
? visible.map((entry, index) => this.renderItem(entry, index, index === this.selectedIndex, width))
|
|
487
|
-
: [buildPanelLine(width, [[` No calls match "${this.filterQuery.trim()}" (Esc to clear)`, C.dim]])];
|
|
488
|
-
|
|
489
|
-
const detailSection: PanelWorkspaceSection = selected
|
|
490
|
-
? {
|
|
491
|
-
title: 'Selected Call',
|
|
492
|
-
lines: [
|
|
493
|
-
buildKeyValueLine(width, [
|
|
494
|
-
{ label: 'provider', value: selected.provider, valueColor: C.provName },
|
|
495
|
-
{ label: 'model', value: selected.model, valueColor: C.value },
|
|
496
|
-
{ label: 'status', value: selected.status === 'ok' ? 'OK' : 'ERROR', valueColor: selected.status === 'ok' ? C.good : C.bad },
|
|
497
|
-
{ label: 'when', value: fmtAgo(selected.ts), valueColor: C.dim },
|
|
498
|
-
], C),
|
|
499
|
-
buildKeyValueLine(width, [
|
|
500
|
-
{ label: 'in', value: String(selected.inputTokens), valueColor: C.input },
|
|
501
|
-
{ label: 'out', value: String(selected.outputTokens), valueColor: C.output },
|
|
502
|
-
{ label: 'latency', value: fmtMs(selected.latencyMs), valueColor: latColor(selected.latencyMs) },
|
|
503
|
-
{ label: 'cost', value: fmtUsd(callCost(selected), !isModelPriced(selected.model)), valueColor: C.value },
|
|
504
|
-
], C),
|
|
505
|
-
...(selected.errorMessage
|
|
506
|
-
? buildBodyText(width, selected.errorMessage, C, C.bad)
|
|
507
|
-
: []),
|
|
508
|
-
],
|
|
509
|
-
}
|
|
510
|
-
: { title: 'Selected Call', lines: [] };
|
|
511
|
-
|
|
512
|
-
const latestError = this._errors[this._errors.length - 1];
|
|
513
|
-
const hints = this.filterActive
|
|
514
|
-
? [
|
|
515
|
-
{ keys: 'type', label: 'filter calls' },
|
|
516
|
-
{ keys: 'Enter', label: 'apply' },
|
|
517
|
-
{ keys: 'Esc', label: 'clear' },
|
|
518
|
-
]
|
|
519
|
-
: [
|
|
520
|
-
{ keys: '↑/↓', label: 'select call' },
|
|
521
|
-
{ keys: '/', label: 'filter' },
|
|
522
|
-
{ keys: 'c', label: 'clear log' },
|
|
523
|
-
];
|
|
524
|
-
|
|
525
|
-
const footerLines: Line[] = [
|
|
526
|
-
...(latestError
|
|
527
|
-
? [buildStyledPanelLine(width, [
|
|
528
|
-
{ text: ' ✕ latest error ', fg: C.bad },
|
|
529
|
-
{ text: truncateDisplay(latestError.errorMessage ?? 'unknown error', Math.max(0, width - 18)), fg: C.dim },
|
|
530
|
-
])]
|
|
531
|
-
: []),
|
|
532
|
-
buildKeyboardHints(width, hints, C),
|
|
533
|
-
];
|
|
534
|
-
|
|
535
|
-
const callSection = resolvePrimaryScrollableSection(width, height, {
|
|
536
|
-
intro,
|
|
537
|
-
footerLines,
|
|
538
|
-
palette: C,
|
|
539
|
-
beforeSections: [summarySection, filterSection],
|
|
540
|
-
section: {
|
|
541
|
-
title: `API Call Log (${visible.length} of ${this._calls.length})`,
|
|
542
|
-
scrollableLines: rows,
|
|
543
|
-
selectedIndex: this.selectedIndex,
|
|
544
|
-
scrollOffset: this.scrollStart,
|
|
545
|
-
minRows: 6,
|
|
546
|
-
appendWindowSummary: { dimColor: C.dim },
|
|
547
|
-
},
|
|
548
|
-
afterSections: [detailSection],
|
|
549
|
-
});
|
|
550
|
-
this.scrollStart = callSection.scrollOffset;
|
|
551
|
-
|
|
552
|
-
this.needsRender = false;
|
|
553
|
-
const lines = buildPanelWorkspace(width, height, {
|
|
554
|
-
title: ' API Debug',
|
|
555
|
-
intro,
|
|
556
|
-
sections: [summarySection, filterSection, callSection.section, detailSection],
|
|
557
|
-
footerLines,
|
|
558
|
-
palette: C,
|
|
559
|
-
});
|
|
560
|
-
while (lines.length < height) lines.push(createEmptyLine(width));
|
|
561
|
-
return lines.slice(0, height);
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
// -------------------------------------------------------------------------
|
|
565
|
-
// Section renderers
|
|
566
|
-
// -------------------------------------------------------------------------
|
|
567
|
-
|
|
568
|
-
private _renderSummary(width: number): Line[] {
|
|
569
|
-
const errCount = this._totalErrors;
|
|
570
|
-
const okCount = this._totalCalls - this._totalErrors;
|
|
571
|
-
const last = this._calls[this._calls.length - 1];
|
|
572
|
-
const recent = this._calls.slice(-10);
|
|
573
|
-
const avgLat = recent.length > 0
|
|
574
|
-
? Math.round(recent.reduce((s, c) => s + c.latencyMs, 0) / recent.length)
|
|
575
|
-
: 0;
|
|
576
|
-
const sessionTokens = this._calls.reduce((s, c) => s + c.inputTokens + c.outputTokens, 0);
|
|
577
|
-
const sessionCost = this._calls.reduce((s, c) => s + callCost(c), 0);
|
|
578
|
-
// Only flag the aggregate as unpriced when every call is — a partial mix
|
|
579
|
-
// still sums the priced calls honestly; only the fully-unpriced case
|
|
580
|
-
// would otherwise render as a misleading $0.
|
|
581
|
-
const sessionAllUnpriced = this._calls.length > 0 && this._calls.every((c) => !isModelPriced(c.model));
|
|
582
|
-
|
|
583
|
-
const lines: Line[] = [
|
|
584
|
-
buildStyledPanelLine(width, [
|
|
585
|
-
{ text: ' Calls ', fg: C.label },
|
|
586
|
-
{ text: String(this._totalCalls), fg: C.value },
|
|
587
|
-
{ text: ' OK ', fg: C.label },
|
|
588
|
-
{ text: String(okCount), fg: C.good },
|
|
589
|
-
{ text: ' Errors ', fg: C.label },
|
|
590
|
-
{ text: String(errCount), fg: errCount > 0 ? C.bad : C.dim },
|
|
591
|
-
{ text: ' Avg latency ', fg: C.label },
|
|
592
|
-
{ text: fmtMs(avgLat), fg: avgLat > 0 ? latColor(avgLat) : C.dim },
|
|
593
|
-
{ text: ' Tokens ', fg: C.label },
|
|
594
|
-
{ text: fmtTok(sessionTokens), fg: C.value },
|
|
595
|
-
{ text: ' Cost ', fg: C.label },
|
|
596
|
-
{ text: fmtUsd(sessionCost, sessionAllUnpriced), fg: C.value },
|
|
597
|
-
]),
|
|
598
|
-
];
|
|
599
|
-
// Live status: most recent call (latency / age) or wiring hint.
|
|
600
|
-
if (last) {
|
|
601
|
-
lines.push(buildStyledPanelLine(width, [
|
|
602
|
-
{ text: ' Last ', fg: C.label },
|
|
603
|
-
{ text: last.status === 'ok' ? '✓ ' : '✕ ', fg: last.status === 'ok' ? C.good : C.bad },
|
|
604
|
-
{ text: fitDisplay(last.model, 22), fg: C.value },
|
|
605
|
-
{ text: ' ' + fmtMs(last.latencyMs), fg: latColor(last.latencyMs) },
|
|
606
|
-
{ text: ' ' + fmtAgo(last.ts), fg: C.dim },
|
|
607
|
-
]));
|
|
608
|
-
} else if (!this._orchestrator) {
|
|
609
|
-
lines.push(buildStyledPanelLine(width, [
|
|
610
|
-
{ text: ' Per-call token deltas need the orchestrator wired (wireOrchestrator).', fg: C.dim },
|
|
611
|
-
]));
|
|
612
|
-
}
|
|
613
|
-
return lines;
|
|
614
|
-
}
|
|
615
|
-
}
|