@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,466 +0,0 @@
|
|
|
1
|
-
import { readFile } from 'fs/promises';
|
|
2
|
-
import { type Line } from '../types/grid.ts';
|
|
3
|
-
import { ModalFactory } from './modal-factory.ts';
|
|
4
|
-
import type { AgentManager } from '@pellux/goodvibes-sdk/platform/tools';
|
|
5
|
-
import type { AgentMessageBus } from '@pellux/goodvibes-sdk/platform/agents';
|
|
6
|
-
import type { WrfcController } from '@pellux/goodvibes-sdk/platform/agents';
|
|
7
|
-
import { formatElapsed } from '../utils/format-elapsed.ts';
|
|
8
|
-
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
9
|
-
import { getOverlaySurfaceMetrics, getStableOverlayContentRows } from './overlay-viewport.ts';
|
|
10
|
-
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
11
|
-
import { handleConfirmInput, type ConfirmState } from '../panels/confirm-state.ts';
|
|
12
|
-
import { AGENT_TERMINAL_STATUSES as MODAL_TERMINAL_STATUSES, AGENT_STALL_THRESHOLD_MS as MODAL_STALL_THRESHOLD_MS, hasReportedUsage } from '../panels/agent-inspector-shared.ts';
|
|
13
|
-
import { UI_TONES } from './ui-primitives.ts';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Agent-detail-modal neon accent palette — WRFC/streaming panel-native hues
|
|
17
|
-
* with no UI_TONES.state/accent role. Preserved byte-exact as named
|
|
18
|
-
* accents per WO-207b (2026-07-02-renderer-plan.md decisions: "Agent-detail
|
|
19
|
-
* neon palette: preserved byte-exact as named accents, zero visual change").
|
|
20
|
-
*/
|
|
21
|
-
const AGENT_DETAIL_NEON = {
|
|
22
|
-
/** Addendum note + streaming content text. */
|
|
23
|
-
mint: '#aaffee',
|
|
24
|
-
/** Unsatisfied WRFC findings + agent error text. */
|
|
25
|
-
bad: '#ff6666',
|
|
26
|
-
/** Satisfied WRFC findings. */
|
|
27
|
-
good: '#44ff88',
|
|
28
|
-
/** Progress line + streaming label. */
|
|
29
|
-
accent: '#00ffcc',
|
|
30
|
-
} as const;
|
|
31
|
-
|
|
32
|
-
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
33
|
-
|
|
34
|
-
const MAX_LOG_ENTRIES = 10;
|
|
35
|
-
const AGENT_ID_DISPLAY_LENGTH = 16;
|
|
36
|
-
|
|
37
|
-
// MODAL_TERMINAL_STATUSES and MODAL_STALL_THRESHOLD_MS are re-exported aliases
|
|
38
|
-
// from agent-inspector-shared.ts (imported above alongside ConfirmState).
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* formatStalledLabel — the "[STALLED — N+ min no activity]" suffix appended
|
|
42
|
-
* to the Status line for a stalled agent. The minute count is derived from
|
|
43
|
-
* the stall threshold (in ms) rather than hardcoded, so the label can never
|
|
44
|
-
* drift out of sync with the actual threshold that decided isStalled.
|
|
45
|
-
*/
|
|
46
|
-
export function formatStalledLabel(thresholdMs: number): string {
|
|
47
|
-
const minutes = Math.floor(thresholdMs / 60000);
|
|
48
|
-
return ` [STALLED — ${minutes}+ min no activity]`;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface AgentDetailModalDeps {
|
|
52
|
-
readonly agentManager: Pick<AgentManager, 'getStatus' | 'list'>;
|
|
53
|
-
readonly agentMessageBus: Pick<AgentMessageBus, 'getMessages'>;
|
|
54
|
-
readonly sessionLogPathResolver: (agentId: string) => string;
|
|
55
|
-
/** Optional — when supplied, constraint data from the agent's WRFC chain is shown (SDK 0.23.0). */
|
|
56
|
-
readonly wrfcController?: Pick<WrfcController, 'getChain'>;
|
|
57
|
-
/** Cancel the agent by id using the same orphan-free path as WRFC. Returns true if cancelled. */
|
|
58
|
-
readonly cancelAgent: (agentId: string) => boolean;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ─── AgentDetailModal ─────────────────────────────────────────────────────────
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* AgentDetailModal — deep-view modal for a single running/completed agent.
|
|
65
|
-
*
|
|
66
|
-
* Displays task description, template, model, status, duration, tool-call
|
|
67
|
-
* count, estimated token usage, recent messages from AgentMessageBus, and
|
|
68
|
-
* the agent's current progress note.
|
|
69
|
-
*/
|
|
70
|
-
export class AgentDetailModal {
|
|
71
|
-
public active = false;
|
|
72
|
-
public agentId: string | null = null;
|
|
73
|
-
|
|
74
|
-
/** Cached JSONL log entries, loaded on open(). */
|
|
75
|
-
public logEntries: Record<string, unknown>[] = [];
|
|
76
|
-
public logTotal = 0;
|
|
77
|
-
|
|
78
|
-
/** Pending cancel confirmation. Subject is the agent id to cancel. */
|
|
79
|
-
public confirmCancel: ConfirmState<string> | null = null;
|
|
80
|
-
|
|
81
|
-
private refreshTimer: ReturnType<typeof setInterval> | null = null;
|
|
82
|
-
private onRefresh: (() => void) | null = null;
|
|
83
|
-
|
|
84
|
-
constructor(readonly deps: AgentDetailModalDeps) {}
|
|
85
|
-
|
|
86
|
-
/** Set a callback to trigger re-render when log data updates. */
|
|
87
|
-
setOnRefresh(fn: () => void): void {
|
|
88
|
-
this.onRefresh = fn;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
open(agentId: string): void {
|
|
92
|
-
this.agentId = agentId;
|
|
93
|
-
this.active = true;
|
|
94
|
-
this.logEntries = [];
|
|
95
|
-
this.logTotal = 0;
|
|
96
|
-
this.loadLog().catch((err) => { logger.debug('agent detail log load failed', { err }); });
|
|
97
|
-
// Auto-refresh log every 500ms while modal is open
|
|
98
|
-
if (this.refreshTimer) clearInterval(this.refreshTimer);
|
|
99
|
-
this.refreshTimer = setInterval(() => {
|
|
100
|
-
this.loadLog().then(() => this.onRefresh?.()).catch((err) => { logger.debug('agent detail log refresh tick failed', { err }); });
|
|
101
|
-
}, 500);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
close(): void {
|
|
105
|
-
this.active = false;
|
|
106
|
-
this.agentId = null;
|
|
107
|
-
this.logEntries = [];
|
|
108
|
-
this.logTotal = 0;
|
|
109
|
-
this.confirmCancel = null;
|
|
110
|
-
if (this.refreshTimer) {
|
|
111
|
-
clearInterval(this.refreshTimer);
|
|
112
|
-
this.refreshTimer = null;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Handle a key press while the modal is active.
|
|
118
|
-
* Must be called BEFORE the Esc handler closes the modal.
|
|
119
|
-
*
|
|
120
|
-
* Routes:
|
|
121
|
-
* - 'c' initiates cancel confirm (if agent is non-terminal)
|
|
122
|
-
* - confirm keys (Enter/y/n/Esc) are forwarded to handleConfirmInput
|
|
123
|
-
*
|
|
124
|
-
* Returns true when the key was consumed (caller should NOT propagate).
|
|
125
|
-
*/
|
|
126
|
-
handleKey(key: string): boolean {
|
|
127
|
-
if (!this.active) return false;
|
|
128
|
-
|
|
129
|
-
if (this.confirmCancel) {
|
|
130
|
-
const result = handleConfirmInput(this.confirmCancel, key);
|
|
131
|
-
if (result === 'confirmed') {
|
|
132
|
-
if (this.agentId) {
|
|
133
|
-
const rec = this.deps.agentManager.getStatus(this.agentId);
|
|
134
|
-
if (rec && !MODAL_TERMINAL_STATUSES.has(rec.status)) {
|
|
135
|
-
this.deps.cancelAgent(rec.id);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
this.confirmCancel = null;
|
|
139
|
-
this.onRefresh?.();
|
|
140
|
-
return true;
|
|
141
|
-
}
|
|
142
|
-
if (result === 'cancelled') {
|
|
143
|
-
this.confirmCancel = null;
|
|
144
|
-
this.onRefresh?.();
|
|
145
|
-
return true;
|
|
146
|
-
}
|
|
147
|
-
// absorbed — key swallowed while confirm is pending
|
|
148
|
-
return true;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (key === 'c') {
|
|
152
|
-
if (this.agentId) {
|
|
153
|
-
const rec = this.deps.agentManager.getStatus(this.agentId);
|
|
154
|
-
if (rec && !MODAL_TERMINAL_STATUSES.has(rec.status)) {
|
|
155
|
-
const label = rec.task.split('\n')[0]?.slice(0, 40) ?? rec.id.slice(-8);
|
|
156
|
-
this.confirmCancel = { subject: rec.id, label };
|
|
157
|
-
this.onRefresh?.();
|
|
158
|
-
return true;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
// Non-cancellable — absorb key silently
|
|
162
|
-
return true;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return false;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Returns whether the current agent is considered stalled.
|
|
170
|
-
* Non-terminal agent with elapsed time exceeding MODAL_STALL_THRESHOLD_MS.
|
|
171
|
-
*/
|
|
172
|
-
isCurrentAgentStalled(): boolean {
|
|
173
|
-
if (!this.agentId) return false;
|
|
174
|
-
const rec = this.deps.agentManager.getStatus(this.agentId);
|
|
175
|
-
if (!rec || MODAL_TERMINAL_STATUSES.has(rec.status)) return false;
|
|
176
|
-
return (Date.now() - rec.startedAt) >= MODAL_STALL_THRESHOLD_MS;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Count of all stalled agents across the agentManager list.
|
|
181
|
-
* Non-terminal agents with elapsed time >= MODAL_STALL_THRESHOLD_MS.
|
|
182
|
-
*/
|
|
183
|
-
getStalledAgentCount(): number {
|
|
184
|
-
const now = Date.now();
|
|
185
|
-
return this.deps.agentManager.list().filter(rec => {
|
|
186
|
-
if (MODAL_TERMINAL_STATUSES.has(rec.status)) return false;
|
|
187
|
-
return (now - rec.startedAt) >= MODAL_STALL_THRESHOLD_MS;
|
|
188
|
-
}).length;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
async loadLog(): Promise<void> {
|
|
192
|
-
if (!this.agentId) { this.logEntries = []; this.logTotal = 0; return; }
|
|
193
|
-
try {
|
|
194
|
-
const sessionFile = this.deps.sessionLogPathResolver(this.agentId);
|
|
195
|
-
const logContent = await readFile(sessionFile, 'utf-8');
|
|
196
|
-
const logLines = logContent.trim().split('\n');
|
|
197
|
-
this.logTotal = logLines.length;
|
|
198
|
-
const parsed = logLines.slice(-MAX_LOG_ENTRIES).map(line => {
|
|
199
|
-
try { return JSON.parse(line) as Record<string, unknown>; } catch { return null; }
|
|
200
|
-
});
|
|
201
|
-
const failedCount = parsed.filter(e => e === null).length;
|
|
202
|
-
if (failedCount > 0) {
|
|
203
|
-
logger.debug('AgentDetailModal: skipped malformed JSONL lines', { count: failedCount });
|
|
204
|
-
}
|
|
205
|
-
this.logEntries = parsed.filter((e): e is Record<string, unknown> => e !== null);
|
|
206
|
-
} catch (err) {
|
|
207
|
-
const code = (err as NodeJS.ErrnoException).code;
|
|
208
|
-
if (code !== 'ENOENT') {
|
|
209
|
-
logger.debug('AgentDetailModal: failed to load session log', { error: summarizeError(err) });
|
|
210
|
-
}
|
|
211
|
-
this.logEntries = [];
|
|
212
|
-
this.logTotal = 0;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// ─── renderAgentDetailModal ───────────────────────────────────────────────────
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Render the agent detail modal as Line[] for overlay in the viewport.
|
|
221
|
-
*
|
|
222
|
-
* Shows a deep view of the selected agent: task, template, model, status,
|
|
223
|
-
* duration, tool call count, token estimate, recent bus messages, and
|
|
224
|
-
* progress text.
|
|
225
|
-
*
|
|
226
|
-
* @param modal AgentDetailModal state
|
|
227
|
-
* @param width Terminal width
|
|
228
|
-
*/
|
|
229
|
-
export function renderAgentDetailModal(
|
|
230
|
-
modal: AgentDetailModal,
|
|
231
|
-
width: number,
|
|
232
|
-
viewportHeight = 24,
|
|
233
|
-
): Line[] {
|
|
234
|
-
if (!modal.agentId) return [];
|
|
235
|
-
const metrics = getOverlaySurfaceMetrics(width, viewportHeight, {
|
|
236
|
-
margin: 2,
|
|
237
|
-
maxWidth: width - 4,
|
|
238
|
-
chromeRows: 6,
|
|
239
|
-
minContentRows: 10,
|
|
240
|
-
maxContentRows: 22,
|
|
241
|
-
});
|
|
242
|
-
const targetContentRows = Math.max(18, Math.min(22, getStableOverlayContentRows(metrics.contentRows, 12) + 8));
|
|
243
|
-
|
|
244
|
-
const rec = modal.deps.agentManager.getStatus(modal.agentId);
|
|
245
|
-
if (!rec) {
|
|
246
|
-
return ModalFactory.createModal({
|
|
247
|
-
title: 'Agent Detail',
|
|
248
|
-
width: metrics.boxWidth,
|
|
249
|
-
margin: metrics.margin,
|
|
250
|
-
targetContentRows,
|
|
251
|
-
sections: [
|
|
252
|
-
{ type: 'text', content: '(agent not found)' },
|
|
253
|
-
],
|
|
254
|
-
hints: ['[Esc] Close'],
|
|
255
|
-
}, width);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const now = Date.now();
|
|
259
|
-
const elapsedMs = (rec.completedAt ?? now) - rec.startedAt;
|
|
260
|
-
|
|
261
|
-
// ── Build sections ────────────────────────────────────────────────────────
|
|
262
|
-
|
|
263
|
-
const sections: import('./modal-factory.ts').ModalSection[] = [];
|
|
264
|
-
|
|
265
|
-
// Task — show first line only, capped at 120 chars
|
|
266
|
-
const taskFirstLine = rec.task.split('\n')[0].replace(/^(WRFC\s+(Fix|Review)\s+Request\s*)/i, '').trim();
|
|
267
|
-
const taskDisplay = taskFirstLine.length > 120 ? taskFirstLine.slice(0, 117) + '\u2026' : taskFirstLine;
|
|
268
|
-
sections.push({
|
|
269
|
-
type: 'text',
|
|
270
|
-
content: `Task: ${taskDisplay}`,
|
|
271
|
-
style: { bold: true },
|
|
272
|
-
});
|
|
273
|
-
sections.push({ type: 'separator' });
|
|
274
|
-
|
|
275
|
-
// Metadata grid
|
|
276
|
-
const modelStr = rec.model ? `${rec.provider ?? ''}/${rec.model}` : (rec.provider ?? '(default)');
|
|
277
|
-
sections.push({ type: 'text', content: `Template : ${rec.template}` });
|
|
278
|
-
sections.push({ type: 'text', content: `Model : ${modelStr}` });
|
|
279
|
-
const isStalled = !MODAL_TERMINAL_STATUSES.has(rec.status) && (now - rec.startedAt) >= MODAL_STALL_THRESHOLD_MS;
|
|
280
|
-
sections.push({ type: 'text', content: `Status : ${rec.status}${isStalled ? formatStalledLabel(MODAL_STALL_THRESHOLD_MS) : ''}` });
|
|
281
|
-
sections.push({ type: 'text', content: `Duration : ${formatElapsed(elapsedMs)}` });
|
|
282
|
-
sections.push({ type: 'separator' });
|
|
283
|
-
|
|
284
|
-
// Metrics
|
|
285
|
-
sections.push({ type: 'text', content: `Tool calls : ${rec.toolCallCount}` });
|
|
286
|
-
if (hasReportedUsage(rec.usage)) {
|
|
287
|
-
const usage = rec.usage;
|
|
288
|
-
const totalIn = usage.inputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheWriteTokens ?? 0);
|
|
289
|
-
sections.push({ type: 'text', content: `Tokens in : ${totalIn.toLocaleString()}` });
|
|
290
|
-
sections.push({ type: 'text', content: `Tokens out : ${usage.outputTokens.toLocaleString()}` });
|
|
291
|
-
} else {
|
|
292
|
-
sections.push({
|
|
293
|
-
type: 'text',
|
|
294
|
-
content: 'Tokens : n/a (agent running)',
|
|
295
|
-
style: { dim: true },
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// SDK 0.23.0: systemPromptAddendum indicator — confirms WRFC constraint addendum was injected
|
|
300
|
-
if (rec.systemPromptAddendum) {
|
|
301
|
-
sections.push({
|
|
302
|
-
type: 'text',
|
|
303
|
-
content: 'Addendum : yes (WRFC constraint layer injected)',
|
|
304
|
-
style: { fg: AGENT_DETAIL_NEON.mint },
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// SDK 0.23.0: constraint data from WRFC chain (engineer constraints + reviewer findings)
|
|
309
|
-
if (rec.wrfcId && modal.deps.wrfcController) {
|
|
310
|
-
try {
|
|
311
|
-
const chain = modal.deps.wrfcController.getChain(rec.wrfcId);
|
|
312
|
-
if (chain && chain.constraints.length > 0) {
|
|
313
|
-
sections.push({ type: 'separator' });
|
|
314
|
-
sections.push({
|
|
315
|
-
type: 'text',
|
|
316
|
-
content: `Constraints (${chain.constraints.length}):`,
|
|
317
|
-
style: { dim: true },
|
|
318
|
-
});
|
|
319
|
-
for (const c of chain.constraints) {
|
|
320
|
-
const text = c.text.length > 80 ? c.text.slice(0, 77) + '…' : c.text;
|
|
321
|
-
sections.push({
|
|
322
|
-
type: 'text',
|
|
323
|
-
content: ` [${c.id}] ${text}`,
|
|
324
|
-
style: { fg: '246' },
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
// Reviewer constraint findings (if review has completed)
|
|
328
|
-
const findings = chain.reviewerReport?.constraintFindings;
|
|
329
|
-
if (findings && findings.length > 0) {
|
|
330
|
-
const unsatisfied = findings.filter((f) => !f.satisfied);
|
|
331
|
-
sections.push({
|
|
332
|
-
type: 'text',
|
|
333
|
-
content: `Findings : ${findings.length} checked, ${unsatisfied.length} unsatisfied`,
|
|
334
|
-
style: { fg: unsatisfied.length > 0 ? AGENT_DETAIL_NEON.bad : AGENT_DETAIL_NEON.good },
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
} catch {
|
|
339
|
-
// wrfcController.getChain throws when chain not found — normal during teardown
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Progress
|
|
344
|
-
if (rec.progress) {
|
|
345
|
-
sections.push({ type: 'separator' });
|
|
346
|
-
sections.push({
|
|
347
|
-
type: 'text',
|
|
348
|
-
content: `Progress: ${rec.progress}`,
|
|
349
|
-
style: { fg: AGENT_DETAIL_NEON.accent },
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// Error
|
|
354
|
-
if (rec.error) {
|
|
355
|
-
sections.push({ type: 'separator' });
|
|
356
|
-
sections.push({
|
|
357
|
-
type: 'text',
|
|
358
|
-
content: `Error: ${rec.error}`,
|
|
359
|
-
style: { fg: AGENT_DETAIL_NEON.bad },
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// Recent messages from AgentMessageBus
|
|
364
|
-
const recentMessages = modal.deps.agentMessageBus
|
|
365
|
-
.getMessages(modal.agentId)
|
|
366
|
-
.slice(-4); // last 4 messages
|
|
367
|
-
|
|
368
|
-
if (recentMessages.length > 0) {
|
|
369
|
-
sections.push({ type: 'separator' });
|
|
370
|
-
sections.push({
|
|
371
|
-
type: 'text',
|
|
372
|
-
content: `Recent messages (${recentMessages.length}):`,
|
|
373
|
-
style: { dim: true },
|
|
374
|
-
});
|
|
375
|
-
for (const msg of recentMessages) {
|
|
376
|
-
const fromLabel = msg.from === '*' ? 'broadcast' : msg.from.slice(0, 12);
|
|
377
|
-
const preview = msg.content.length > 50
|
|
378
|
-
? msg.content.slice(0, 47) + '\u2026'
|
|
379
|
-
: msg.content;
|
|
380
|
-
sections.push({
|
|
381
|
-
type: 'text',
|
|
382
|
-
content: ` [${fromLabel}] ${preview}`,
|
|
383
|
-
style: { fg: '246' },
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// Execution history from cached JSONL session log (loaded on open())
|
|
389
|
-
if (modal.logEntries.length > 0) {
|
|
390
|
-
sections.push({ type: 'separator' });
|
|
391
|
-
sections.push({
|
|
392
|
-
type: 'text',
|
|
393
|
-
content: `Execution Log (${modal.logTotal} events, showing last ${modal.logEntries.length}):`,
|
|
394
|
-
style: { dim: true },
|
|
395
|
-
});
|
|
396
|
-
for (const entry of modal.logEntries) {
|
|
397
|
-
const entryType = String(entry.type ?? 'unknown');
|
|
398
|
-
const rawTs = entry.timestamp;
|
|
399
|
-
const ts = typeof rawTs === 'string' && rawTs.length >= 19
|
|
400
|
-
? rawTs.slice(11, 19)
|
|
401
|
-
: '';
|
|
402
|
-
let detail = '';
|
|
403
|
-
if (entryType === 'tool_execution') detail = ` ${entry.toolName}`;
|
|
404
|
-
if (entryType === 'llm_response') detail = ` (${entry.toolCallCount} tools, ${entry.contentLength} chars)`;
|
|
405
|
-
if (entryType === 'session_end') detail = ` [${entry.status}]`;
|
|
406
|
-
sections.push({
|
|
407
|
-
type: 'text',
|
|
408
|
-
content: ` ${ts} ${entryType}${detail}`,
|
|
409
|
-
style: { fg: '246' },
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// Streaming content — show live output when agent is actively streaming
|
|
415
|
-
const STREAMING_MAX_CHARS = 500;
|
|
416
|
-
if (rec.status === 'running' && rec.streamingContent) {
|
|
417
|
-
const content = rec.streamingContent;
|
|
418
|
-
const truncated = content.length > STREAMING_MAX_CHARS;
|
|
419
|
-
const display = truncated ? content.slice(-STREAMING_MAX_CHARS) : content;
|
|
420
|
-
sections.push({ type: 'separator' });
|
|
421
|
-
sections.push({
|
|
422
|
-
type: 'text',
|
|
423
|
-
content: truncated
|
|
424
|
-
? `Streaming (last ${STREAMING_MAX_CHARS} of ${content.length} chars \u2191 scroll for more):`
|
|
425
|
-
: 'Streaming:',
|
|
426
|
-
style: { fg: AGENT_DETAIL_NEON.accent, dim: true },
|
|
427
|
-
});
|
|
428
|
-
// Split into display lines, capped at width for readability
|
|
429
|
-
const maxLineWidth = Math.max(width - 10, 40);
|
|
430
|
-
const streamLines = display.split('\n');
|
|
431
|
-
for (const line of streamLines) {
|
|
432
|
-
const trimmed = line.length > maxLineWidth ? line.slice(0, maxLineWidth - 1) + '\u2026' : line;
|
|
433
|
-
sections.push({
|
|
434
|
-
type: 'text',
|
|
435
|
-
content: ` ${trimmed}`,
|
|
436
|
-
style: { fg: AGENT_DETAIL_NEON.mint },
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// Cancel confirm overlay (when pending)
|
|
442
|
-
const cancellable = !MODAL_TERMINAL_STATUSES.has(rec.status);
|
|
443
|
-
if (modal.confirmCancel) {
|
|
444
|
-
sections.push({ type: 'separator' });
|
|
445
|
-
sections.push({
|
|
446
|
-
type: 'text',
|
|
447
|
-
content: `Cancel agent "${modal.confirmCancel.label}"?`,
|
|
448
|
-
style: { fg: UI_TONES.state.warn },
|
|
449
|
-
});
|
|
450
|
-
sections.push({
|
|
451
|
-
type: 'text',
|
|
452
|
-
content: 'y / Enter confirm n / Esc cancel',
|
|
453
|
-
style: { dim: true },
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
const cancelHint = cancellable ? '[c] Cancel ' : '';
|
|
458
|
-
return ModalFactory.createModal({
|
|
459
|
-
title: `Agent: ${rec.id.slice(0, AGENT_ID_DISPLAY_LENGTH)}`,
|
|
460
|
-
width: metrics.boxWidth,
|
|
461
|
-
margin: metrics.margin,
|
|
462
|
-
targetContentRows,
|
|
463
|
-
sections,
|
|
464
|
-
hints: [cancelHint + '[Esc] Close'],
|
|
465
|
-
}, width);
|
|
466
|
-
}
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
import { type Line } from '../types/grid.ts';
|
|
2
|
-
import { ModalFactory } from './modal-factory.ts';
|
|
3
|
-
import type { ProcessManager } from '@pellux/goodvibes-sdk/platform/tools';
|
|
4
|
-
import type { AgentManager } from '@pellux/goodvibes-sdk/platform/tools';
|
|
5
|
-
import type { ProcessEntry } from './process-modal.ts';
|
|
6
|
-
import { getOverlaySurfaceMetrics, getStableOverlayContentRows } from './overlay-viewport.ts';
|
|
7
|
-
|
|
8
|
-
export interface LiveTailModalDeps {
|
|
9
|
-
readonly agentManager: Pick<AgentManager, 'cancel' | 'getStatus'>;
|
|
10
|
-
readonly processManager: Pick<ProcessManager, 'stop' | 'getOutput'>;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// ─── LiveTailModal ────────────────────────────────────────────────────────────
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* LiveTailModal — manages state for the live output peek modal.
|
|
17
|
-
*
|
|
18
|
-
* Shows streaming stdout/stderr from a selected background process or agent
|
|
19
|
-
* progress notes. Auto-scrolls to the bottom unless the user scrolled up.
|
|
20
|
-
*/
|
|
21
|
-
export class LiveTailModal {
|
|
22
|
-
public active = false;
|
|
23
|
-
public entry: ProcessEntry | null = null;
|
|
24
|
-
|
|
25
|
-
/** Number of lines scrolled up from the bottom (0 = at bottom). */
|
|
26
|
-
public scrollOffset = 0;
|
|
27
|
-
|
|
28
|
-
constructor(private readonly deps: LiveTailModalDeps) {}
|
|
29
|
-
|
|
30
|
-
open(entry: ProcessEntry): void {
|
|
31
|
-
this.entry = entry;
|
|
32
|
-
this.scrollOffset = 0;
|
|
33
|
-
this.active = true;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
close(): void {
|
|
37
|
-
this.active = false;
|
|
38
|
-
this.entry = null;
|
|
39
|
-
this.scrollOffset = 0;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
scrollUp(): void {
|
|
43
|
-
this.scrollOffset++;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
scrollDown(): void {
|
|
47
|
-
this.scrollOffset = Math.max(0, this.scrollOffset - 1);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Kill the current process.
|
|
52
|
-
* Returns true if the process was found and stopped.
|
|
53
|
-
*/
|
|
54
|
-
killProcess(): boolean {
|
|
55
|
-
if (!this.entry) return false;
|
|
56
|
-
|
|
57
|
-
if (this.entry.type === 'exec') {
|
|
58
|
-
return this.deps.processManager.stop(this.entry.id);
|
|
59
|
-
} else {
|
|
60
|
-
return this.deps.agentManager.cancel(this.entry.id);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** Retrieve the current output text for the watched process. */
|
|
65
|
-
getOutput(): string {
|
|
66
|
-
if (!this.entry) return '';
|
|
67
|
-
|
|
68
|
-
if (this.entry.type === 'exec') {
|
|
69
|
-
const out = this.deps.processManager.getOutput(this.entry.id);
|
|
70
|
-
if (!out) return '';
|
|
71
|
-
const combined = [out.stdout, out.stderr].filter(Boolean).join('\n').trim();
|
|
72
|
-
return combined || '(no output yet)';
|
|
73
|
-
} else {
|
|
74
|
-
// For agents, show progress note and status
|
|
75
|
-
const rec = this.deps.agentManager.getStatus(this.entry.id);
|
|
76
|
-
if (!rec) return '(process not found)';
|
|
77
|
-
const parts: string[] = [
|
|
78
|
-
`Task: ${rec.task}`,
|
|
79
|
-
`Status: ${rec.status}`,
|
|
80
|
-
`Tool calls: ${rec.toolCallCount}`,
|
|
81
|
-
];
|
|
82
|
-
if (rec.progress) parts.push(`Progress: ${rec.progress}`);
|
|
83
|
-
if (rec.error) parts.push(`Error: ${rec.error}`);
|
|
84
|
-
return parts.join('\n');
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// ─── renderLiveTailModal ──────────────────────────────────────────────────────
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Render the live-tail peek modal as Line[] for overlay in the viewport.
|
|
93
|
-
*
|
|
94
|
-
* Shows a scrollable view of the process output. scrollOffset=0 means the
|
|
95
|
-
* bottom of the output is visible (auto-scroll behaviour).
|
|
96
|
-
*
|
|
97
|
-
* @param modal LiveTailModal state
|
|
98
|
-
* @param width Terminal width
|
|
99
|
-
* @param maxOutputLines Maximum lines to show inside the box (default: 16)
|
|
100
|
-
*/
|
|
101
|
-
export function renderLiveTailModal(
|
|
102
|
-
modal: LiveTailModal,
|
|
103
|
-
width: number,
|
|
104
|
-
viewportHeight = 24,
|
|
105
|
-
): Line[] {
|
|
106
|
-
const entry = modal.entry;
|
|
107
|
-
if (!entry) return [];
|
|
108
|
-
const metrics = getOverlaySurfaceMetrics(width, viewportHeight, {
|
|
109
|
-
chromeRows: 4,
|
|
110
|
-
minContentRows: 6,
|
|
111
|
-
maxContentRows: 10,
|
|
112
|
-
});
|
|
113
|
-
const maxOutputLines = metrics.contentRows;
|
|
114
|
-
const targetContentRows = getStableOverlayContentRows(metrics.contentRows, 8);
|
|
115
|
-
|
|
116
|
-
const output = modal.getOutput();
|
|
117
|
-
const allLines = output.split('\n');
|
|
118
|
-
|
|
119
|
-
// Apply scroll: scrollOffset=0 → show tail; larger → scroll toward head
|
|
120
|
-
const totalLines = allLines.length;
|
|
121
|
-
// Clamp scrollOffset so we never scroll past the top of the content
|
|
122
|
-
const maxScroll = Math.max(0, totalLines - maxOutputLines);
|
|
123
|
-
const clampedOffset = Math.min(modal.scrollOffset, maxScroll);
|
|
124
|
-
const endIdx = Math.max(maxOutputLines, totalLines - clampedOffset);
|
|
125
|
-
const startIdx = Math.max(0, endIdx - maxOutputLines);
|
|
126
|
-
const visibleLines = allLines.slice(startIdx, endIdx);
|
|
127
|
-
|
|
128
|
-
const typeTag = entry.type === 'agent' ? '[agent]' : '[exec]';
|
|
129
|
-
const maxLabelW = Math.max(20, width - 30);
|
|
130
|
-
const title = `${typeTag} ${entry.label.slice(0, maxLabelW)}`;
|
|
131
|
-
|
|
132
|
-
// Build scroll indicator for text section header
|
|
133
|
-
const scrollInfo = totalLines > maxOutputLines
|
|
134
|
-
? ` Lines ${startIdx + 1}-${Math.min(endIdx, totalLines)} of ${totalLines} [Up/Down] Scroll`
|
|
135
|
-
: '';
|
|
136
|
-
|
|
137
|
-
const contentText = visibleLines.join('\n') || '(no output yet)';
|
|
138
|
-
|
|
139
|
-
const sections: import('./modal-factory.ts').ModalSection[] = [];
|
|
140
|
-
|
|
141
|
-
if (scrollInfo) {
|
|
142
|
-
sections.push({ type: 'text', content: scrollInfo });
|
|
143
|
-
sections.push({ type: 'separator' });
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
sections.push({ type: 'text', content: contentText });
|
|
147
|
-
|
|
148
|
-
return ModalFactory.createModal({
|
|
149
|
-
title,
|
|
150
|
-
width: metrics.boxWidth,
|
|
151
|
-
margin: 2,
|
|
152
|
-
targetContentRows,
|
|
153
|
-
sections,
|
|
154
|
-
hints: ['[Up/Down] Scroll', '[k] Kill', '[Esc] Back'],
|
|
155
|
-
}, width);
|
|
156
|
-
}
|