@pellux/goodvibes-agent 0.1.102 → 0.1.104
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 +10 -0
- package/docs/README.md +1 -1
- package/docs/getting-started.md +17 -3
- package/package.json +1 -1
- package/src/agent/memory-safety.ts +16 -0
- package/src/cli/help.ts +86 -0
- package/src/cli/local-library-command.ts +516 -0
- package/src/cli/management.ts +17 -0
- package/src/cli/memory-command.ts +630 -0
- package/src/cli/package-verification.ts +10 -0
- package/src/cli/parser.ts +8 -0
- package/src/cli/types.ts +3 -0
- package/src/input/agent-workspace-activation.ts +170 -0
- package/src/input/agent-workspace-categories.ts +8 -1
- package/src/input/agent-workspace-editors.ts +36 -0
- package/src/input/agent-workspace-memory-editor.ts +88 -0
- package/src/input/agent-workspace-setup.ts +7 -5
- package/src/input/agent-workspace-snapshot.ts +40 -4
- package/src/input/agent-workspace-token.ts +51 -0
- package/src/input/agent-workspace-types.ts +13 -3
- package/src/input/agent-workspace.ts +130 -185
- package/src/input/feed-context-factory.ts +1 -3
- package/src/input/handler-feed.ts +1 -4
- package/src/input/handler-interactions.ts +0 -1
- package/src/input/handler-modal-stack.ts +0 -1
- package/src/input/handler-modal-token-routes.ts +0 -11
- package/src/input/handler-picker-routes.ts +11 -20
- package/src/input/handler-ui-state.ts +0 -6
- package/src/input/handler.ts +1 -17
- package/src/main.ts +0 -6
- package/src/panels/builtin/agent.ts +0 -17
- package/src/panels/index.ts +0 -2
- package/src/renderer/agent-workspace.ts +8 -3
- package/src/renderer/conversation-overlays.ts +0 -6
- package/src/renderer/live-tail-modal.ts +10 -69
- package/src/renderer/process-modal.ts +28 -530
- package/src/runtime/bootstrap-core.ts +1 -1
- package/src/runtime/services.ts +3 -4
- package/src/tools/{wrfc-agent-guard.ts → agent-tool-policy-guard.ts} +0 -6
- package/src/version.ts +1 -1
- package/src/panels/agent-inspector-panel.ts +0 -521
- package/src/panels/agent-inspector-shared.ts +0 -94
- package/src/panels/agent-logs-panel.ts +0 -559
- package/src/panels/agent-logs-shared.ts +0 -129
- package/src/renderer/agent-detail-modal.ts +0 -331
- package/src/renderer/process-summary.ts +0 -67
package/src/input/handler.ts
CHANGED
|
@@ -21,14 +21,12 @@ import type { BlockMeta, ConversationManager } from '../core/conversation';
|
|
|
21
21
|
import { ProcessModal } from '../renderer/process-modal.ts';
|
|
22
22
|
import { LiveTailModal } from '../renderer/live-tail-modal.ts';
|
|
23
23
|
import { BlockActionsMenu } from '../renderer/block-actions.ts';
|
|
24
|
-
import { AgentDetailModal } from '../renderer/agent-detail-modal.ts';
|
|
25
24
|
import { ContextInspectorModal } from '../renderer/context-inspector.ts';
|
|
26
25
|
import { BookmarkModal } from './bookmark-modal.ts';
|
|
27
26
|
import { SettingsModal } from './settings-modal.ts';
|
|
28
27
|
import { McpWorkspace } from './mcp-workspace.ts';
|
|
29
28
|
import { AgentWorkspace } from './agent-workspace.ts';
|
|
30
29
|
import { SessionPickerModal } from './session-picker-modal.ts';
|
|
31
|
-
import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
|
|
32
30
|
import { ProfilePickerModal } from './profile-picker-modal.ts';
|
|
33
31
|
import { OnboardingWizardController, type OnboardingWizardAction, type OnboardingWizardMode } from './onboarding/onboarding-wizard.ts';
|
|
34
32
|
import {
|
|
@@ -155,7 +153,6 @@ export class InputHandler {
|
|
|
155
153
|
public searchManager = new SearchManager();
|
|
156
154
|
public processModal: ProcessModal;
|
|
157
155
|
public liveTailModal: LiveTailModal;
|
|
158
|
-
public agentDetailModal: AgentDetailModal;
|
|
159
156
|
public contextInspectorModal = new ContextInspectorModal();
|
|
160
157
|
public bookmarkModal: BookmarkModal;
|
|
161
158
|
public blockActionsMenu = new BlockActionsMenu();
|
|
@@ -220,8 +217,7 @@ export class InputHandler {
|
|
|
220
217
|
public scroll: (delta: number) => void,
|
|
221
218
|
public exitApp: () => void,
|
|
222
219
|
public readonly uiServices: Pick<UiRuntimeServices,
|
|
223
|
-
'
|
|
224
|
-
| 'environment'
|
|
220
|
+
'environment'
|
|
225
221
|
| 'platform'
|
|
226
222
|
| 'providers'
|
|
227
223
|
| 'sessions'
|
|
@@ -235,22 +231,11 @@ export class InputHandler {
|
|
|
235
231
|
uiServices.providers.providerRegistry,
|
|
236
232
|
);
|
|
237
233
|
this.processModal = new ProcessModal({
|
|
238
|
-
agentManager: uiServices.agents.agentManager,
|
|
239
234
|
processManager: uiServices.shell.processManager,
|
|
240
|
-
wrfcController: uiServices.agents.wrfcController,
|
|
241
|
-
agentEntries: 'hidden',
|
|
242
235
|
});
|
|
243
236
|
this.liveTailModal = new LiveTailModal({
|
|
244
|
-
agentManager: uiServices.agents.agentManager,
|
|
245
237
|
processManager: uiServices.shell.processManager,
|
|
246
238
|
});
|
|
247
|
-
this.agentDetailModal = new AgentDetailModal({
|
|
248
|
-
agentManager: uiServices.agents.agentManager,
|
|
249
|
-
agentMessageBus: uiServices.agents.agentMessageBus,
|
|
250
|
-
sessionLogPathResolver: (agentId) => uiServices.environment.shellPaths.resolveProjectPath(GOODVIBES_AGENT_SURFACE_ROOT, 'sessions', `${agentId}.jsonl`),
|
|
251
|
-
// SDK 0.23.0: supply wrfcController so the modal can show constraint data
|
|
252
|
-
wrfcController: uiServices.agents.wrfcController,
|
|
253
|
-
});
|
|
254
239
|
this.bookmarkModal = new BookmarkModal(uiServices.shell.bookmarkManager);
|
|
255
240
|
this.sessionPickerModal = new SessionPickerModal(uiServices.sessions.sessionManager);
|
|
256
241
|
this.profilePickerModal = new ProfilePickerModal(uiServices.shell.profileManager);
|
|
@@ -294,7 +279,6 @@ export class InputHandler {
|
|
|
294
279
|
onboardingWizard: this.onboardingWizard,
|
|
295
280
|
processModal: this.processModal,
|
|
296
281
|
liveTailModal: this.liveTailModal,
|
|
297
|
-
agentDetailModal: this.agentDetailModal,
|
|
298
282
|
contextInspectorModal: this.contextInspectorModal,
|
|
299
283
|
blockActionsMenu: this.blockActionsMenu,
|
|
300
284
|
searchManager: this.searchManager,
|
package/src/main.ts
CHANGED
|
@@ -398,11 +398,6 @@ async function main() {
|
|
|
398
398
|
scroll,
|
|
399
399
|
exitApp,
|
|
400
400
|
{
|
|
401
|
-
agents: {
|
|
402
|
-
agentManager,
|
|
403
|
-
agentMessageBus: ctx.services.agentMessageBus,
|
|
404
|
-
wrfcController: ctx.services.wrfcController,
|
|
405
|
-
},
|
|
406
401
|
providers: {
|
|
407
402
|
benchmarkStore: ctx.services.benchmarkStore,
|
|
408
403
|
favoritesStore: ctx.services.favoritesStore,
|
|
@@ -450,7 +445,6 @@ async function main() {
|
|
|
450
445
|
input.setConversationManager(conversation);
|
|
451
446
|
input.setContentWidth(getPromptContentWidth());
|
|
452
447
|
input.filePicker.setOnUpdate(() => render());
|
|
453
|
-
input.agentDetailModal.setOnRefresh(() => render());
|
|
454
448
|
input.processModal.setOnRefresh(() => render());
|
|
455
449
|
|
|
456
450
|
// Model picker callback is handled in bootstrap.ts — do not duplicate here.
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { PanelManager } from '../panel-manager.ts';
|
|
2
|
-
import { AgentLogsPanel } from '../agent-logs-panel.ts';
|
|
3
2
|
import { ContextVisualizerPanel } from '../context-visualizer-panel.ts';
|
|
4
3
|
import { ThinkingPanel } from '../thinking-panel.ts';
|
|
5
4
|
import { ToolInspectorPanel } from '../tool-inspector-panel.ts';
|
|
@@ -50,22 +49,6 @@ export function registerAgentPanels(manager: PanelManager, deps: ResolvedBuiltin
|
|
|
50
49
|
),
|
|
51
50
|
});
|
|
52
51
|
|
|
53
|
-
manager.registerType({
|
|
54
|
-
id: 'agent-logs',
|
|
55
|
-
name: 'Agents',
|
|
56
|
-
icon: 'A',
|
|
57
|
-
category: 'agent',
|
|
58
|
-
description: 'View-only stream for explicit delegated build/review session records',
|
|
59
|
-
preload: true,
|
|
60
|
-
factory: () => {
|
|
61
|
-
const ui = requireUiServices(deps);
|
|
62
|
-
return new AgentLogsPanel(ui.events.agents, {
|
|
63
|
-
agentManager: ui.agents.agentManager,
|
|
64
|
-
workingDirectory: ui.environment.workingDirectory,
|
|
65
|
-
});
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
|
|
69
52
|
manager.registerType({
|
|
70
53
|
id: 'work-plan',
|
|
71
54
|
name: 'Work Plan',
|
package/src/panels/index.ts
CHANGED
|
@@ -4,8 +4,6 @@ export { BasePanel } from './base-panel.ts';
|
|
|
4
4
|
export { PanelManager } from './panel-manager.ts';
|
|
5
5
|
export { TokenBudgetPanel } from './token-budget-panel.ts';
|
|
6
6
|
export { CostTrackerPanel } from './cost-tracker-panel.ts';
|
|
7
|
-
export { AgentInspectorPanel } from './agent-inspector-panel.ts';
|
|
8
|
-
export { AgentLogsPanel } from './agent-logs-panel.ts';
|
|
9
7
|
export { ProviderHealthPanel } from './provider-health-panel.ts';
|
|
10
8
|
export { ProviderHealthTracker } from './provider-health-tracker.ts';
|
|
11
9
|
export type { ProviderHealth, ProviderStatus } from './provider-health-tracker.ts';
|
|
@@ -123,6 +123,8 @@ function localLibraryLines(
|
|
|
123
123
|
selected ? 'selected' : '',
|
|
124
124
|
item.active ? 'active' : '',
|
|
125
125
|
item.enabled === true ? 'enabled' : item.enabled === false ? 'disabled' : '',
|
|
126
|
+
item.scope && item.cls ? `${item.scope}/${item.cls}` : '',
|
|
127
|
+
item.confidence !== undefined ? `${item.confidence}%` : '',
|
|
126
128
|
item.reviewState,
|
|
127
129
|
item.startCount !== undefined ? `starts ${item.startCount}` : '',
|
|
128
130
|
].filter(Boolean).join(' / ');
|
|
@@ -194,8 +196,8 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
|
|
|
194
196
|
);
|
|
195
197
|
} else if (category.id === 'setup') {
|
|
196
198
|
base.push(
|
|
197
|
-
{ text: `GoodVibes runtime: ${snapshot.
|
|
198
|
-
{ text: `Runtime owner: ${snapshot.
|
|
199
|
+
{ text: `GoodVibes runtime: ${snapshot.runtimeBaseUrl}`, fg: PALETTE.info },
|
|
200
|
+
{ text: `Runtime owner: ${snapshot.runtimeOwnership}; Agent connects but never starts or restarts it`, fg: PALETTE.good },
|
|
199
201
|
...setupChecklistLines(snapshot),
|
|
200
202
|
{ text: '' },
|
|
201
203
|
{ text: `Workspace: ${snapshot.workingDirectory}`, fg: PALETTE.muted },
|
|
@@ -216,7 +218,7 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
|
|
|
216
218
|
...snapshot.channels.filter((channel) => !channel.enabled),
|
|
217
219
|
].slice(0, 6);
|
|
218
220
|
base.push(
|
|
219
|
-
{ text: `GoodVibes runtime: ${snapshot.
|
|
221
|
+
{ text: `GoodVibes runtime: ${snapshot.runtimeBaseUrl}`, fg: PALETTE.info },
|
|
220
222
|
{ text: `Readiness: ${readyCount}/${snapshot.channels.length} ready; ${enabledCount} enabled; ${configuredDefaults} default target(s) configured.`, fg: PALETTE.info },
|
|
221
223
|
{ text: `Ready channels: ${readyChannels.join(', ') || 'none'}.`, fg: readyChannels.length > 0 ? PALETTE.good : PALETTE.warn },
|
|
222
224
|
{ text: `Needs default target: ${needsTarget.map((channel) => `${channel.label} -> ${channel.defaultTargetKeys.join('|')}`).join(', ') || 'none'}.`, fg: needsTarget.length > 0 ? PALETTE.warn : PALETTE.muted },
|
|
@@ -301,11 +303,14 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
|
|
|
301
303
|
} else if (category.id === 'memory') {
|
|
302
304
|
base.push(
|
|
303
305
|
{ text: `Session memories: ${snapshot.sessionMemoryCount}`, fg: PALETTE.info },
|
|
306
|
+
{ text: `Agent memory: ${snapshot.localMemoryCount}; review queue: ${snapshot.localMemoryReviewQueueCount}`, fg: PALETTE.info },
|
|
304
307
|
{ text: `Local routines: ${snapshot.localRoutineCount}; enabled: ${snapshot.enabledRoutineCount}`, fg: PALETTE.info },
|
|
305
308
|
{ text: `Local skills: ${snapshot.localSkillCount}; enabled: ${snapshot.enabledSkillCount}; bundles: ${snapshot.localSkillBundleCount}; active skills: ${snapshot.activeSkillCount}`, fg: PALETTE.info },
|
|
306
309
|
{ text: `Local personas: ${snapshot.localPersonaCount}; active: ${snapshot.activePersonaName}`, fg: PALETTE.info },
|
|
307
310
|
{ text: 'Durable memory, routines, skills, and personas remain Agent-local until shared registry contracts exist.', fg: PALETTE.good },
|
|
308
311
|
{ text: 'Secrets are rejected/redacted; store secret references instead of secret values.', fg: PALETTE.warn },
|
|
312
|
+
{ text: '' },
|
|
313
|
+
...localLibraryLines('Agent Memory', snapshot.localMemories, 'No Agent memory yet. Create one here with Create memory.', workspace.selectedLocalLibraryItem('memory')?.id ?? null),
|
|
309
314
|
);
|
|
310
315
|
} else if (category.id === 'personas') {
|
|
311
316
|
base.push(
|
|
@@ -9,7 +9,6 @@ import { renderSelectionModalOverlay } from './selection-modal-overlay.ts';
|
|
|
9
9
|
import { renderSearchOverlay } from './search-overlay.ts';
|
|
10
10
|
import { renderHistorySearchOverlay } from './history-search-overlay.ts';
|
|
11
11
|
import { renderProcessModal } from './process-modal.ts';
|
|
12
|
-
import { renderAgentDetailModal } from './agent-detail-modal.ts';
|
|
13
12
|
import { renderLiveTailModal } from './live-tail-modal.ts';
|
|
14
13
|
import { renderContextInspector } from './context-inspector.ts';
|
|
15
14
|
import { renderSettingsModal } from './settings-modal.ts';
|
|
@@ -74,11 +73,6 @@ export function applyConversationOverlays(
|
|
|
74
73
|
next = overlayViewportBottom(next, lines, conversationWidth, viewportHeight, bottomDockInset);
|
|
75
74
|
}
|
|
76
75
|
|
|
77
|
-
if (input.agentDetailModal.active) {
|
|
78
|
-
const lines = renderAgentDetailModal(input.agentDetailModal, conversationWidth);
|
|
79
|
-
next = overlayViewportBottom(next, lines, conversationWidth, viewportHeight, bottomDockInset);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
76
|
if (input.liveTailModal.active) {
|
|
83
77
|
const lines = renderLiveTailModal(input.liveTailModal, conversationWidth, viewportHeight);
|
|
84
78
|
next = overlayViewportBottom(next, lines, conversationWidth, viewportHeight, bottomDockInset);
|
|
@@ -1,28 +1,16 @@
|
|
|
1
1
|
import { type Line } from '../types/grid.ts';
|
|
2
2
|
import { ModalFactory } from './modal-factory.ts';
|
|
3
3
|
import type { ProcessManager } from '@pellux/goodvibes-sdk/platform/tools';
|
|
4
|
-
import type { AgentManager } from '@pellux/goodvibes-sdk/platform/tools';
|
|
5
4
|
import type { ProcessEntry } from './process-modal.ts';
|
|
6
5
|
import { getOverlaySurfaceMetrics, getStableOverlayContentRows } from './overlay-viewport.ts';
|
|
7
6
|
|
|
8
7
|
export interface LiveTailModalDeps {
|
|
9
|
-
readonly agentManager: Pick<AgentManager, 'getStatus'>;
|
|
10
8
|
readonly processManager: Pick<ProcessManager, 'stop' | 'getOutput'>;
|
|
11
9
|
}
|
|
12
10
|
|
|
13
|
-
// ─── LiveTailModal ────────────────────────────────────────────────────────────
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* LiveTailModal — manages state for the live output peek modal.
|
|
17
|
-
*
|
|
18
|
-
* Shows streaming stdout/stderr from a selected shell process or agent
|
|
19
|
-
* progress notes. Auto-scrolls to the bottom unless the user scrolled up.
|
|
20
|
-
*/
|
|
21
11
|
export class LiveTailModal {
|
|
22
12
|
public active = false;
|
|
23
13
|
public entry: ProcessEntry | null = null;
|
|
24
|
-
|
|
25
|
-
/** Number of lines scrolled up from the bottom (0 = at bottom). */
|
|
26
14
|
public scrollOffset = 0;
|
|
27
15
|
|
|
28
16
|
constructor(private readonly deps: LiveTailModalDeps) {}
|
|
@@ -40,64 +28,27 @@ export class LiveTailModal {
|
|
|
40
28
|
}
|
|
41
29
|
|
|
42
30
|
scrollUp(): void {
|
|
43
|
-
this.scrollOffset
|
|
31
|
+
this.scrollOffset += 1;
|
|
44
32
|
}
|
|
45
33
|
|
|
46
34
|
scrollDown(): void {
|
|
47
35
|
this.scrollOffset = Math.max(0, this.scrollOffset - 1);
|
|
48
36
|
}
|
|
49
37
|
|
|
50
|
-
|
|
51
|
-
* Stop the current shell process when it is an exec entry.
|
|
52
|
-
* Agent entries are read-only in GoodVibes Agent; build execution and
|
|
53
|
-
* cancellation belong to GoodVibes TUI/shared-session owners.
|
|
54
|
-
*/
|
|
55
|
-
killProcess(): boolean {
|
|
38
|
+
stopProcess(): boolean {
|
|
56
39
|
if (!this.entry) return false;
|
|
57
|
-
|
|
58
|
-
if (this.entry.type === 'exec') {
|
|
59
|
-
return this.deps.processManager.stop(this.entry.id);
|
|
60
|
-
}
|
|
61
|
-
return false;
|
|
40
|
+
return this.deps.processManager.stop(this.entry.id);
|
|
62
41
|
}
|
|
63
42
|
|
|
64
|
-
/** Retrieve the current output text for the watched process. */
|
|
65
43
|
getOutput(): string {
|
|
66
44
|
if (!this.entry) return '';
|
|
67
|
-
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
}
|
|
45
|
+
const output = this.deps.processManager.getOutput(this.entry.id);
|
|
46
|
+
if (!output) return '';
|
|
47
|
+
const combined = [output.stdout, output.stderr].filter(Boolean).join('\n').trim();
|
|
48
|
+
return combined || '(no output yet)';
|
|
86
49
|
}
|
|
87
50
|
}
|
|
88
51
|
|
|
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
52
|
export function renderLiveTailModal(
|
|
102
53
|
modal: LiveTailModal,
|
|
103
54
|
width: number,
|
|
@@ -115,35 +66,25 @@ export function renderLiveTailModal(
|
|
|
115
66
|
|
|
116
67
|
const output = modal.getOutput();
|
|
117
68
|
const allLines = output.split('\n');
|
|
118
|
-
|
|
119
|
-
// Apply scroll: scrollOffset=0 → show tail; larger → scroll toward head
|
|
120
69
|
const totalLines = allLines.length;
|
|
121
|
-
// Clamp scrollOffset so we never scroll past the top of the content
|
|
122
70
|
const maxScroll = Math.max(0, totalLines - maxOutputLines);
|
|
123
71
|
const clampedOffset = Math.min(modal.scrollOffset, maxScroll);
|
|
124
72
|
const endIdx = Math.max(maxOutputLines, totalLines - clampedOffset);
|
|
125
73
|
const startIdx = Math.max(0, endIdx - maxOutputLines);
|
|
126
74
|
const visibleLines = allLines.slice(startIdx, endIdx);
|
|
127
75
|
|
|
128
|
-
const typeTag = entry.type === 'agent' ? '[agent]' : '[exec]';
|
|
129
76
|
const maxLabelW = Math.max(20, width - 30);
|
|
130
|
-
const title =
|
|
131
|
-
|
|
132
|
-
// Build scroll indicator for text section header
|
|
77
|
+
const title = `[exec] ${entry.label.slice(0, maxLabelW)}`;
|
|
133
78
|
const scrollInfo = totalLines > maxOutputLines
|
|
134
79
|
? ` Lines ${startIdx + 1}-${Math.min(endIdx, totalLines)} of ${totalLines} [Up/Down] Scroll`
|
|
135
80
|
: '';
|
|
136
81
|
|
|
137
|
-
const contentText = visibleLines.join('\n') || '(no output yet)';
|
|
138
|
-
|
|
139
82
|
const sections: import('./modal-factory.ts').ModalSection[] = [];
|
|
140
|
-
|
|
141
83
|
if (scrollInfo) {
|
|
142
84
|
sections.push({ type: 'text', content: scrollInfo });
|
|
143
85
|
sections.push({ type: 'separator' });
|
|
144
86
|
}
|
|
145
|
-
|
|
146
|
-
sections.push({ type: 'text', content: contentText });
|
|
87
|
+
sections.push({ type: 'text', content: visibleLines.join('\n') || '(no output yet)' });
|
|
147
88
|
|
|
148
89
|
return ModalFactory.createModal({
|
|
149
90
|
title,
|
|
@@ -151,6 +92,6 @@ export function renderLiveTailModal(
|
|
|
151
92
|
margin: 2,
|
|
152
93
|
targetContentRows,
|
|
153
94
|
sections,
|
|
154
|
-
hints: ['[Up/Down] Scroll', '[k] Stop
|
|
95
|
+
hints: ['[Up/Down] Scroll', '[k] Stop process', '[Esc] Back'],
|
|
155
96
|
}, width);
|
|
156
97
|
}
|