@pellux/goodvibes-tui 1.0.0 → 1.7.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 +130 -127
- package/README.md +32 -14
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +2 -2
- package/src/cli/ensure-goodvibes-gitignore.ts +32 -0
- package/src/cli/entrypoint.ts +2 -0
- 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/conversation-rendering.ts +19 -0
- package/src/core/conversation.ts +58 -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 +104 -2
- package/src/core/stream-stall-watchdog.ts +41 -17
- package/src/core/system-message-router.ts +37 -51
- package/src/core/turn-cancellation.ts +20 -0
- package/src/core/turn-event-wiring.ts +64 -3
- package/src/export/cost-utils.ts +126 -8
- package/src/input/command-registry.ts +54 -1
- package/src/input/commands/checkpoint-runtime.ts +280 -0
- package/src/input/commands/codebase-runtime.ts +192 -0
- package/src/input/commands/diff-runtime.ts +61 -30
- package/src/input/commands/eval.ts +1 -1
- package/src/input/commands/health-runtime.ts +1 -1
- 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 +6 -1
- package/src/input/commands/operator-panel-runtime.ts +40 -24
- package/src/input/commands/planning-runtime.ts +26 -3
- 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 +3 -3
- package/src/input/commands/recall-review.ts +35 -0
- 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 +12 -0
- package/src/input/commands/session-workflow.ts +19 -1
- package/src/input/commands/settings-sync-runtime.ts +1 -1
- package/src/input/commands/share-runtime.ts +9 -2
- package/src/input/commands/shell-core.ts +15 -7
- 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 +338 -0
- package/src/input/commands.ts +12 -0
- package/src/input/config-modal-types.ts +161 -0
- package/src/input/config-modal.ts +377 -0
- package/src/input/feed-context-factory.ts +7 -8
- package/src/input/handler-command-route.ts +27 -17
- package/src/input/handler-content-actions.ts +22 -8
- package/src/input/handler-feed-routes.ts +107 -7
- package/src/input/handler-feed.ts +49 -13
- package/src/input/handler-interactions.ts +3 -3
- package/src/input/handler-modal-routes.ts +65 -0
- package/src/input/handler-modal-stack.ts +3 -5
- package/src/input/handler-modal-token-routes.ts +16 -49
- package/src/input/handler-picker-routes.ts +11 -94
- package/src/input/handler-shortcuts.ts +24 -14
- package/src/input/handler-types.ts +3 -6
- package/src/input/handler-ui-state.ts +10 -22
- package/src/input/handler.ts +10 -28
- package/src/input/keybindings.ts +22 -8
- package/src/input/model-picker-types.ts +24 -6
- package/src/input/model-picker.ts +58 -0
- package/src/input/panel-integration-actions.ts +9 -170
- package/src/input/settings-modal-data.ts +95 -0
- package/src/input/settings-modal-mutations.ts +6 -4
- package/src/main.ts +41 -44
- package/src/panels/agent-inspector-shared.ts +35 -11
- package/src/panels/base-panel.ts +22 -1
- package/src/panels/builtin/agent.ts +21 -107
- package/src/panels/builtin/development.ts +16 -62
- 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 +9 -43
- package/src/panels/builtin-modals.ts +218 -0
- package/src/panels/builtin-panels.ts +5 -0
- package/src/panels/cost-tracker-panel.ts +63 -14
- package/src/panels/diff-panel.ts +123 -60
- package/src/panels/eval-registry.ts +60 -0
- package/src/panels/fleet-panel.ts +800 -0
- package/src/panels/fleet-read-model.ts +477 -0
- package/src/panels/fleet-steer.ts +92 -0
- package/src/panels/fleet-stop.ts +125 -0
- package/src/panels/fleet-tabs.ts +230 -0
- package/src/panels/fleet-transcript.ts +356 -0
- package/src/panels/git-panel.ts +9 -9
- package/src/panels/index.ts +7 -31
- package/src/panels/local-auth-panel.ts +10 -0
- package/src/panels/modals/hooks-modal.ts +187 -0
- package/src/panels/modals/keybindings-modal.ts +151 -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 +36 -0
- package/src/panels/modals/pairing-modal.ts +144 -0
- package/src/panels/modals/planning-modal.ts +282 -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 +149 -0
- package/src/panels/panel-confirm-overlay.ts +72 -0
- package/src/panels/panel-manager.ts +108 -5
- package/src/panels/project-planning-answer-actions.ts +4 -2
- package/src/panels/scrollable-list-panel.ts +9 -0
- package/src/panels/skills-panel.ts +7 -51
- package/src/panels/token-budget-panel.ts +5 -3
- package/src/panels/types.ts +26 -0
- package/src/permissions/hunk-selection.ts +179 -0
- package/src/permissions/prompt.ts +60 -4
- 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/config-modal.ts +81 -0
- package/src/renderer/conversation-overlays.ts +10 -17
- package/src/renderer/fleet-tab-strip.ts +56 -0
- package/src/renderer/footer-tips.ts +10 -2
- package/src/renderer/git-status.ts +40 -0
- package/src/renderer/help-overlay.ts +2 -2
- package/src/renderer/model-workspace.ts +44 -1
- package/src/renderer/panel-workspace-bar.ts +8 -2
- package/src/renderer/shell-surface.ts +8 -1
- package/src/renderer/turn-injection.ts +114 -0
- package/src/renderer/ui-factory.ts +91 -7
- package/src/runtime/bootstrap-command-context.ts +24 -1
- package/src/runtime/bootstrap-command-parts.ts +36 -7
- package/src/runtime/bootstrap-core.ts +19 -4
- package/src/runtime/bootstrap-hook-bridge.ts +5 -0
- package/src/runtime/bootstrap-shell.ts +41 -17
- package/src/runtime/bootstrap.ts +11 -17
- package/src/runtime/code-index-services.ts +112 -0
- package/src/runtime/orchestrator-core-services.ts +49 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- package/src/runtime/services.ts +91 -43
- package/src/runtime/ui-services.ts +8 -0
- package/src/runtime/workstream-services.ts +195 -0
- package/src/shell/blocking-input.ts +22 -1
- package/src/shell/ui-openers.ts +129 -17
- package/src/utils/format-duration.ts +8 -18
- package/src/utils/splash-lines.ts +1 -1
- 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 -233
- package/src/panels/communication-panel.ts +0 -256
- package/src/panels/control-plane-panel.ts +0 -470
- package/src/panels/debug-panel.ts +0 -609
- package/src/panels/docs-panel.ts +0 -375
- package/src/panels/eval-panel.ts +0 -627
- package/src/panels/file-explorer-panel.ts +0 -664
- 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 -506
- 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 -557
- 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 -721
- package/src/panels/provider-health-panel.ts +0 -678
- package/src/panels/provider-health-tracker.ts +0 -306
- package/src/panels/provider-health-views.ts +0 -560
- 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 -487
- 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 -530
- package/src/panels/worktree-panel.ts +0 -360
- package/src/panels/wrfc-panel.ts +0 -790
- package/src/renderer/agent-detail-modal.ts +0 -465
- package/src/renderer/live-tail-modal.ts +0 -156
- package/src/renderer/process-modal.ts +0 -656
- package/src/renderer/qr-renderer.ts +0 -120
|
@@ -1,64 +1,16 @@
|
|
|
1
1
|
import type { CommandContext } from './command-registry.ts';
|
|
2
|
-
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
3
2
|
import type { Panel } from '../panels/types.ts';
|
|
4
3
|
import type { PanelManager } from '../panels/panel-manager.ts';
|
|
5
|
-
import { FileExplorerPanel } from '../panels/file-explorer-panel.ts';
|
|
6
|
-
import { FilePreviewPanel } from '../panels/file-preview-panel.ts';
|
|
7
|
-
import { SymbolOutlinePanel } from '../panels/symbol-outline-panel.ts';
|
|
8
|
-
import { ApprovalPanel } from '../panels/approval-panel.ts';
|
|
9
|
-
import { TasksPanel } from '../panels/tasks-panel.ts';
|
|
10
|
-
import { OrchestrationPanel } from '../panels/orchestration-panel.ts';
|
|
11
|
-
import { AgentInspectorPanel } from '../panels/agent-inspector-panel.ts';
|
|
12
|
-
import { DiffPanel } from '../panels/diff-panel.ts';
|
|
13
|
-
|
|
14
|
-
function ensurePreviewPanel(panelManager: PanelManager): FilePreviewPanel | null {
|
|
15
|
-
const existing = panelManager.getPanel('preview');
|
|
16
|
-
if (existing instanceof FilePreviewPanel) {
|
|
17
|
-
const pane = panelManager.getPaneOf('preview');
|
|
18
|
-
panelManager.activateById('preview');
|
|
19
|
-
if (pane) panelManager.focusPane(pane);
|
|
20
|
-
return existing;
|
|
21
|
-
}
|
|
22
|
-
const targetPane: 'top' | 'bottom' = panelManager.isBottomPaneVisible()
|
|
23
|
-
? (panelManager.getFocusedPane() === 'top' ? 'bottom' : 'top')
|
|
24
|
-
: 'bottom';
|
|
25
|
-
const opened = panelManager.open('preview', targetPane);
|
|
26
|
-
panelManager.show();
|
|
27
|
-
panelManager.focusPane(targetPane);
|
|
28
|
-
return opened instanceof FilePreviewPanel ? opened : null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// WO-133: shared by explorer's and preview's 'd' (diff) key — same
|
|
32
|
-
// open/focus bridge as ensurePreviewPanel above.
|
|
33
|
-
function ensureDiffPanel(panelManager: PanelManager): DiffPanel | null {
|
|
34
|
-
const existing = panelManager.getPanel('diff');
|
|
35
|
-
if (existing instanceof DiffPanel) {
|
|
36
|
-
const pane = panelManager.getPaneOf('diff');
|
|
37
|
-
panelManager.activateById('diff');
|
|
38
|
-
if (pane) panelManager.focusPane(pane);
|
|
39
|
-
return existing;
|
|
40
|
-
}
|
|
41
|
-
const targetPane: 'top' | 'bottom' = panelManager.isBottomPaneVisible()
|
|
42
|
-
? (panelManager.getFocusedPane() === 'top' ? 'bottom' : 'top')
|
|
43
|
-
: 'bottom';
|
|
44
|
-
const opened = panelManager.open('diff', targetPane);
|
|
45
|
-
panelManager.show();
|
|
46
|
-
panelManager.focusPane(targetPane);
|
|
47
|
-
return opened instanceof DiffPanel ? opened : null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Exported so a future preview-reload action (e.g. an explicit "r" reload
|
|
51
|
-
// key) can re-sync the outline against the same file without duplicating
|
|
52
|
-
// this lookup — the panel-integration wiring for that key lands separately.
|
|
53
|
-
export function syncSymbolOutlineFromPreview(panelManager: PanelManager, previewPanel: FilePreviewPanel): void {
|
|
54
|
-
const symbols = panelManager.getPanel('symbols');
|
|
55
|
-
const filePath = previewPanel.getCurrentFilePath();
|
|
56
|
-
const source = previewPanel.getSource();
|
|
57
|
-
if (symbols instanceof SymbolOutlinePanel && filePath && source !== null) {
|
|
58
|
-
symbols.loadFile(filePath, source);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
4
|
|
|
5
|
+
// W6.1 (the purge): every instanceof-routed branch this file used to carry
|
|
6
|
+
// (FileExplorerPanel/FilePreviewPanel/SymbolOutlinePanel — DELETE;
|
|
7
|
+
// ApprovalPanel/TasksPanel/OrchestrationPanel/AgentInspectorPanel —
|
|
8
|
+
// RETIRE-INTO-FLEET) targeted a panel class that no longer exists. Panels
|
|
9
|
+
// migrated onto the formal `Panel.handlePanelIntegrationAction` hook (the
|
|
10
|
+
// preferred seam — see the comment below) are unaffected; this function now
|
|
11
|
+
// only provides that passthrough. See
|
|
12
|
+
// .goodvibes/audit/2026-07-04-wave6-briefs.json (W6.1) for the disposition
|
|
13
|
+
// map.
|
|
62
14
|
export function handlePanelIntegrationAction(
|
|
63
15
|
panelManager: PanelManager,
|
|
64
16
|
activePanel: Panel | null,
|
|
@@ -77,118 +29,5 @@ export function handlePanelIntegrationAction(
|
|
|
77
29
|
if (consumed) return true;
|
|
78
30
|
}
|
|
79
31
|
|
|
80
|
-
if ((key === 'enter' || key === 'return' || key === 'right') && activePanel instanceof FileExplorerPanel) {
|
|
81
|
-
const filePath = activePanel.getFocusedFilePath();
|
|
82
|
-
if (!filePath) return false;
|
|
83
|
-
const previewPanel = ensurePreviewPanel(panelManager);
|
|
84
|
-
if (!previewPanel) return false;
|
|
85
|
-
previewPanel.openFile(filePath);
|
|
86
|
-
syncSymbolOutlineFromPreview(panelManager, previewPanel);
|
|
87
|
-
return true;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// WO-133: 'd' on the explorer diffs the currently focused file, and 'd' on
|
|
91
|
-
// the preview diffs whatever file is currently open — both against HEAD,
|
|
92
|
-
// both via the same DiffPanel.showFileDiffs entry point.
|
|
93
|
-
if (key === 'd' && activePanel instanceof FileExplorerPanel) {
|
|
94
|
-
const filePath = activePanel.getFocusedFilePath();
|
|
95
|
-
if (!filePath) return false;
|
|
96
|
-
const diffPanel = ensureDiffPanel(panelManager);
|
|
97
|
-
if (!diffPanel) return false;
|
|
98
|
-
void diffPanel.showFileDiffs([filePath], 'HEAD').catch((err) => {
|
|
99
|
-
logger.debug('explorer diff dispatch failed', { err });
|
|
100
|
-
});
|
|
101
|
-
return true;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (key === 'd' && activePanel instanceof FilePreviewPanel) {
|
|
105
|
-
const filePath = activePanel.getCurrentFilePath();
|
|
106
|
-
if (!filePath) return false;
|
|
107
|
-
const diffPanel = ensureDiffPanel(panelManager);
|
|
108
|
-
if (!diffPanel) return false;
|
|
109
|
-
void diffPanel.showFileDiffs([filePath], 'HEAD').catch((err) => {
|
|
110
|
-
logger.debug('preview diff dispatch failed', { err });
|
|
111
|
-
});
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// WO-133: 'r' on the preview reloads the open file from disk, then
|
|
116
|
-
// re-syncs the symbol outline against the refreshed content (WO-126's
|
|
117
|
-
// async tree-sitter loadFile via syncSymbolOutlineFromPreview above) once
|
|
118
|
-
// the reload actually settles.
|
|
119
|
-
if (key === 'r' && activePanel instanceof FilePreviewPanel) {
|
|
120
|
-
const reloaded = activePanel.consumePendingReload();
|
|
121
|
-
if (!reloaded) return false;
|
|
122
|
-
void reloaded.then(() => syncSymbolOutlineFromPreview(panelManager, activePanel)).catch((err) => {
|
|
123
|
-
logger.debug('preview reload dispatch failed', { err });
|
|
124
|
-
});
|
|
125
|
-
return true;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if ((key === 'enter' || key === 'return') && activePanel instanceof SymbolOutlinePanel) {
|
|
129
|
-
const location = activePanel.getSelectedLocation();
|
|
130
|
-
if (!location) return false;
|
|
131
|
-
const previewPanel = ensurePreviewPanel(panelManager);
|
|
132
|
-
if (!previewPanel) return false;
|
|
133
|
-
if (previewPanel.getCurrentFilePath() !== location.path) {
|
|
134
|
-
previewPanel.openFile(location.path);
|
|
135
|
-
syncSymbolOutlineFromPreview(panelManager, previewPanel);
|
|
136
|
-
}
|
|
137
|
-
previewPanel.goToLine(location.line);
|
|
138
|
-
return true;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if ((key === 'enter' || key === 'return') && activePanel instanceof ApprovalPanel) {
|
|
142
|
-
const command = activePanel.getSelectedCommand();
|
|
143
|
-
if (!command || !commandContext?.executeCommand) return false;
|
|
144
|
-
const parts = command.replace(/^\//, '').split(/\s+/).filter(Boolean);
|
|
145
|
-
const [name, ...args] = parts;
|
|
146
|
-
if (!name) return false;
|
|
147
|
-
void commandContext.executeCommand(name, args).catch((err) => { logger.debug('approval panel command dispatch failed', { err }); });
|
|
148
|
-
return true;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// WO-131: Enter on a Tasks row — agent-kind tasks jump straight to the Agent
|
|
152
|
-
// Inspector (which owns the deep per-agent timeline); everything else's
|
|
153
|
-
// advertised worktree follow-up is dispatched for real via ctx.executeCommand
|
|
154
|
-
// instead of being printed as a static "/worktree task <task-id>" signpost.
|
|
155
|
-
// w dispatches the task-family posture review the header line advertises.
|
|
156
|
-
if ((key === 'enter' || key === 'return' || key === 'w') && activePanel instanceof TasksPanel) {
|
|
157
|
-
const followUp = activePanel.consumePendingFollowUp();
|
|
158
|
-
if (!followUp) return false;
|
|
159
|
-
if (followUp.kind === 'agent-jump') {
|
|
160
|
-
const inspector = panelManager.open('inspector');
|
|
161
|
-
if (!(inspector instanceof AgentInspectorPanel)) return false;
|
|
162
|
-
inspector.inspectAgent(followUp.agentId);
|
|
163
|
-
return true;
|
|
164
|
-
}
|
|
165
|
-
if (!commandContext?.executeCommand) return false;
|
|
166
|
-
if (followUp.kind === 'teamwork-review') {
|
|
167
|
-
void commandContext.executeCommand('teamwork', ['review']).catch((err) => {
|
|
168
|
-
logger.debug('tasks panel teamwork review dispatch failed', { err });
|
|
169
|
-
});
|
|
170
|
-
return true;
|
|
171
|
-
}
|
|
172
|
-
void commandContext.executeCommand('worktree', ['task', followUp.taskId]).catch((err) => {
|
|
173
|
-
logger.debug('tasks panel worktree review dispatch failed', { err });
|
|
174
|
-
});
|
|
175
|
-
return true;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// WO-131: Enter on a node-focused Orchestration row jumps to the Agent
|
|
179
|
-
// Inspector (agent-backed nodes) or the Tasks panel (task-backed nodes).
|
|
180
|
-
if ((key === 'enter' || key === 'return') && activePanel instanceof OrchestrationPanel) {
|
|
181
|
-
const jump = activePanel.consumePendingNodeJump();
|
|
182
|
-
if (!jump) return false;
|
|
183
|
-
if (jump.kind === 'agent-jump') {
|
|
184
|
-
const inspector = panelManager.open('inspector');
|
|
185
|
-
if (!(inspector instanceof AgentInspectorPanel)) return false;
|
|
186
|
-
inspector.inspectAgent(jump.id);
|
|
187
|
-
return true;
|
|
188
|
-
}
|
|
189
|
-
panelManager.open('tasks');
|
|
190
|
-
return true;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
32
|
return false;
|
|
194
33
|
}
|
|
@@ -14,6 +14,7 @@ import type { McpRegistry } from '@pellux/goodvibes-sdk/platform/mcp';
|
|
|
14
14
|
import { buildSubscriptionEntries } from './settings-modal-subscriptions.ts';
|
|
15
15
|
import type { SubscriptionManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
16
16
|
import type { ServiceInspectionQuery } from '../runtime/ui-service-queries.ts';
|
|
17
|
+
import { CODE_INDEX_ENABLED_CONFIG_KEY } from '../runtime/code-index-services.ts';
|
|
17
18
|
import {
|
|
18
19
|
SETTINGS_CATEGORIES,
|
|
19
20
|
type FlagEntry,
|
|
@@ -130,6 +131,25 @@ export function buildSettingGroups(
|
|
|
130
131
|
behaviorEntries.push(buildNotifyAfterSecondsSyntheticEntry(configManager));
|
|
131
132
|
}
|
|
132
133
|
|
|
134
|
+
// Inject the W2.3 alert-class toggles + master focus gate. TUI-local
|
|
135
|
+
// synthetic settings, same rationale as notifyAfterSeconds above.
|
|
136
|
+
if (behaviorEntries) {
|
|
137
|
+
for (const entry of buildNotifyAlertSyntheticEntries(configManager)) {
|
|
138
|
+
if (!behaviorEntries.some((e) => e.setting.key === entry.setting.key)) {
|
|
139
|
+
behaviorEntries.push(entry);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Wave 5 (wo804): inject the storage.codeIndexEnabled toggle into the
|
|
145
|
+
// storage category. TUI-local synthetic setting (not in the SDK ConfigKey
|
|
146
|
+
// union — see code-index-services.ts), same rationale as
|
|
147
|
+
// notifyAfterSeconds above: opt-in, default off, states its own bounds.
|
|
148
|
+
const storageEntries = groups.get('storage');
|
|
149
|
+
if (storageEntries && !storageEntries.some((e) => e.setting.key === (CODE_INDEX_ENABLED_CONFIG_KEY as ConfigKey))) {
|
|
150
|
+
storageEntries.push(buildCodeIndexEnabledSyntheticEntry(configManager));
|
|
151
|
+
}
|
|
152
|
+
|
|
133
153
|
return groups;
|
|
134
154
|
}
|
|
135
155
|
|
|
@@ -224,6 +244,81 @@ export function buildNotifyAfterSecondsSyntheticEntry(configManager: Pick<Config
|
|
|
224
244
|
};
|
|
225
245
|
}
|
|
226
246
|
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
// W2.3 alert-class synthetic settings — behavior.notifyOn* + notifyOnlyWhenUnfocused
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* The five W2.3 alert-gating booleans, all TUI-local (not yet in the SDK
|
|
253
|
+
* ConfigKey union), all defaulting to on. Read/written generically by
|
|
254
|
+
* core/alert-gating.ts (readBooleanConfig) and the per-alert-class modules
|
|
255
|
+
* (budget-breach-notifier.ts, approval-alert.ts, turn-event-wiring.ts,
|
|
256
|
+
* long-task-notifier.ts) — this is only the settings-modal-visible surface.
|
|
257
|
+
*/
|
|
258
|
+
const NOTIFY_ALERT_SYNTHETIC_SETTINGS: ReadonlyArray<{ readonly key: string; readonly description: string }> = [
|
|
259
|
+
{
|
|
260
|
+
key: 'behavior.notifyOnBudgetBreach',
|
|
261
|
+
description: 'Alert when session cost crosses the configured budget (set via the Cost panel\'s "b" key).',
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
key: 'behavior.notifyOnAgentFailure',
|
|
265
|
+
description: 'Alert when a delegated or background agent fails.',
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
key: 'behavior.notifyOnChainFailure',
|
|
269
|
+
description: 'Alert when a WRFC review chain fails.',
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
key: 'behavior.notifyOnApprovalPending',
|
|
273
|
+
description: 'Alert when a tool call is waiting on your approval.',
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
key: 'behavior.notifyOnlyWhenUnfocused',
|
|
277
|
+
description: 'Master gate for the four alerts above: fire only when the terminal window is unfocused, or when focus state was never observed (terminal does not report focus). Turn off to always fire regardless of focus.',
|
|
278
|
+
},
|
|
279
|
+
];
|
|
280
|
+
|
|
281
|
+
function buildBooleanSyntheticEntry(
|
|
282
|
+
configManager: Pick<ConfigManager, 'get'>,
|
|
283
|
+
key: string,
|
|
284
|
+
description: string,
|
|
285
|
+
defaultValue: boolean,
|
|
286
|
+
): SettingEntry {
|
|
287
|
+
const raw = configManager.get(key as ConfigKey);
|
|
288
|
+
const currentValue = typeof raw === 'boolean' ? raw : defaultValue;
|
|
289
|
+
return {
|
|
290
|
+
setting: { key: key as ConfigKey, type: 'boolean', default: defaultValue, description },
|
|
291
|
+
currentValue,
|
|
292
|
+
isDefault: currentValue === defaultValue,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Build the five synthetic SettingEntry rows for the behavior category. */
|
|
297
|
+
export function buildNotifyAlertSyntheticEntries(configManager: Pick<ConfigManager, 'get'>): SettingEntry[] {
|
|
298
|
+
return NOTIFY_ALERT_SYNTHETIC_SETTINGS.map((spec) => buildBooleanSyntheticEntry(configManager, spec.key, spec.description, true));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ---------------------------------------------------------------------------
|
|
302
|
+
// Wave 5 (wo804) — storage.codeIndexEnabled synthetic setting
|
|
303
|
+
// ---------------------------------------------------------------------------
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* The repo source-tree code index's auto-build-on-startup toggle
|
|
307
|
+
* (code-index-services.ts). TUI-local, default OFF: /codebase build is the
|
|
308
|
+
* explicit trigger unless a user opts in here. Honest bounds stated inline
|
|
309
|
+
* so enabling this isn't a surprise — see code-index-services.ts's
|
|
310
|
+
* CODE_INDEX_MAX_FILES/CODE_INDEX_MAX_FILE_BYTES for the numbers this
|
|
311
|
+
* description would otherwise duplicate as magic numbers.
|
|
312
|
+
*/
|
|
313
|
+
export function buildCodeIndexEnabledSyntheticEntry(configManager: Pick<ConfigManager, 'get'>): SettingEntry {
|
|
314
|
+
return buildBooleanSyntheticEntry(
|
|
315
|
+
configManager,
|
|
316
|
+
CODE_INDEX_ENABLED_CONFIG_KEY,
|
|
317
|
+
'Auto-build the repo source-tree code index on startup (bounded file/size scan; see /codebase status for bounds). Off by default — /codebase build indexes on demand.',
|
|
318
|
+
false,
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
227
322
|
// ---------------------------------------------------------------------------
|
|
228
323
|
// buildFlagEntries — snapshot of current feature flag states
|
|
229
324
|
// ---------------------------------------------------------------------------
|
|
@@ -113,13 +113,15 @@ export function persistFlagState(
|
|
|
113
113
|
if (newState === 'killed') return; // never persist killed state
|
|
114
114
|
|
|
115
115
|
try {
|
|
116
|
-
const current = (configManager.getCategory('featureFlags') as Record<string, PersistedFlagState>) ?? {};
|
|
117
116
|
if (newState === defaultState) {
|
|
118
|
-
|
|
117
|
+
// Back at the default: the override must be REMOVED, not merged.
|
|
118
|
+
// getCategory clones and mergeCategory only sets keys, so the old
|
|
119
|
+
// delete-then-merge approach left the stale override on disk and the
|
|
120
|
+
// flag silently reloaded in the overridden state on the next start.
|
|
121
|
+
configManager.removeCategoryKey('featureFlags', flagId);
|
|
119
122
|
} else {
|
|
120
|
-
|
|
123
|
+
configManager.mergeCategory('featureFlags', { [flagId]: newState } as Record<string, PersistedFlagState>);
|
|
121
124
|
}
|
|
122
|
-
configManager.mergeCategory('featureFlags', current);
|
|
123
125
|
} catch (e) {
|
|
124
126
|
logger.error('SettingsModal: failed to persist flag state', { flagId, error: summarizeError(e) });
|
|
125
127
|
}
|
package/src/main.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { registerAllTools } from '@pellux/goodvibes-sdk/platform/tools';
|
|
|
12
12
|
import { FileUndoManager } from '@pellux/goodvibes-sdk/platform/state';
|
|
13
13
|
import { PermissionManager } from '@pellux/goodvibes-sdk/platform/permissions';
|
|
14
14
|
import { AcpManager } from '@pellux/goodvibes-sdk/platform/acp';
|
|
15
|
-
import { PermissionPromptUI } from './permissions/prompt.ts';
|
|
15
|
+
import { PermissionPromptUI, buildPendingPermissionExtras } from './permissions/prompt.ts';
|
|
16
16
|
import { CommandRegistry } from './input/command-registry.ts';
|
|
17
17
|
import type { CommandContext } from './input/command-registry.ts';
|
|
18
18
|
import { renderProcessIndicator } from './renderer/process-indicator.ts';
|
|
@@ -32,6 +32,7 @@ import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
|
32
32
|
import { registerBuiltinPanels } from './panels/builtin-panels.ts';
|
|
33
33
|
import { bootstrapRuntime } from './runtime/bootstrap.ts';
|
|
34
34
|
import type { BootstrapContext } from './runtime/bootstrap.ts';
|
|
35
|
+
import { buildSharedOrchestratorCoreServices } from './runtime/orchestrator-core-services.ts';
|
|
35
36
|
import type { HITLMode } from '@pellux/goodvibes-sdk/platform/state';
|
|
36
37
|
import {
|
|
37
38
|
checkRecoveryFile,
|
|
@@ -62,12 +63,16 @@ import { wireStreamEventMetrics, type StreamMetrics, type WireStreamEventMetrics
|
|
|
62
63
|
import { wireTurnEventHandlers } from './core/turn-event-wiring.ts';
|
|
63
64
|
import { buildContextStatusHint } from './renderer/context-status-hint.ts';
|
|
64
65
|
import { evaluateSessionMaintenance } from '@/runtime/index.ts';
|
|
66
|
+
import { createCancelGeneration } from './core/turn-cancellation.ts';
|
|
67
|
+
import { wrapRequestPermissionWithAlert } from './core/approval-alert.ts';
|
|
68
|
+
import { setPanelFrameRequester } from './panels/base-panel.ts';
|
|
65
69
|
|
|
66
70
|
const ALT_SCREEN_ENTER = '\x1b[?1049h'; const ALT_SCREEN_EXIT = '\x1b[?1049l';
|
|
67
71
|
const MOUSE_ENABLE = '\x1b[?1000h\x1b[?1002h\x1b[?1006h'; const MOUSE_DISABLE = '\x1b[?1006l\x1b[?1002l\x1b[?1000l';
|
|
68
72
|
const CURSOR_HIDE = '\x1b[?25l'; const CURSOR_SHOW = '\x1b[?25h'; const CLEAR_SCREEN = '\x1b[2J\x1b[3J\x1b[H';
|
|
69
73
|
const KEYBOARD_EXT_ENABLE = '\x1b[>4;2m' + '\x1b[?1u'; const KEYBOARD_EXT_DISABLE = '\x1b[>4;0m' + '\x1b[?1l';
|
|
70
74
|
const PASTE_ENABLE = '\x1b[?2004h'; const PASTE_DISABLE = '\x1b[?2004l';
|
|
75
|
+
const FOCUS_ENABLE = '\x1b[?1004h'; const FOCUS_DISABLE = '\x1b[?1004l';
|
|
71
76
|
|
|
72
77
|
async function main() {
|
|
73
78
|
const stdout = process.stdout;
|
|
@@ -103,22 +108,17 @@ async function main() {
|
|
|
103
108
|
permissionPromptRef,
|
|
104
109
|
_writeLastSessionPointer: writeLastSessionPointer,
|
|
105
110
|
systemMessageRouter,
|
|
106
|
-
setOpenAgentDetail,
|
|
107
111
|
} = ctx;
|
|
108
112
|
const workingDir = ctx.services.workingDirectory;
|
|
109
113
|
const homeDirectory = ctx.services.homeDirectory;
|
|
110
114
|
const { approvalBroker, agentManager, modeManager, processManager, providerRegistry, secretsManager, subscriptionManager } = ctx.services;
|
|
111
115
|
conversation.setSessionMemoryStore(ctx.services.sessionMemoryStore);
|
|
112
116
|
conversation.setSessionLineageTracker(ctx.services.sessionLineageTracker);
|
|
117
|
+
// Shared payload (single source of truth, includes wo805's memoryRegistry —
|
|
118
|
+
// see orchestrator-core-services.ts) plus this site's favoritesStore.
|
|
113
119
|
orchestrator.setCoreServices({
|
|
114
|
-
configManager,
|
|
115
|
-
providerRegistry,
|
|
120
|
+
...buildSharedOrchestratorCoreServices({ services: ctx.services, configManager, providerRegistry }),
|
|
116
121
|
favoritesStore: ctx.services.favoritesStore,
|
|
117
|
-
planManager: ctx.services.planManager,
|
|
118
|
-
adaptivePlanner: ctx.services.adaptivePlanner,
|
|
119
|
-
sessionMemoryStore: ctx.services.sessionMemoryStore,
|
|
120
|
-
sessionLineageTracker: ctx.services.sessionLineageTracker,
|
|
121
|
-
idempotencyStore: ctx.services.idempotencyStore,
|
|
122
122
|
});
|
|
123
123
|
ctx.services.wrfcController.setPlanManager(ctx.services.planManager);
|
|
124
124
|
let activeConversationWidth = stdout.columns || 80;
|
|
@@ -130,13 +130,9 @@ async function main() {
|
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
// TUI default: show token speed ON
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
// user has explicitly set this key to false in EITHER their global or project
|
|
137
|
-
// persisted config, their value is respected and the default is NOT applied.
|
|
138
|
-
// Only when the key is absent from both files (e.g. a new install) does the
|
|
139
|
-
// TUI default of true take effect in-memory — no disk write occurs either way.
|
|
133
|
+
// TUI default: show token speed ON (SDK schema default is false). applyRuntimeConfigDefault
|
|
134
|
+
// reads the global + project settings files and only applies the default in-memory when the
|
|
135
|
+
// key is absent from both (e.g. a new install); an explicit user value is respected. No disk write.
|
|
140
136
|
applyRuntimeConfigDefault(configManager, 'display.showTokenSpeed', true);
|
|
141
137
|
|
|
142
138
|
const panelManager = ctx.services.panelManager;
|
|
@@ -180,6 +176,8 @@ async function main() {
|
|
|
180
176
|
ttftRecorded: false,
|
|
181
177
|
activeToolStartedAtMs: undefined,
|
|
182
178
|
activeToolName: undefined,
|
|
179
|
+
lastDeltaAtMs: undefined, stallEpisode: 0,
|
|
180
|
+
reconnectAttempt: undefined, reconnectMaxAttempts: undefined,
|
|
183
181
|
};
|
|
184
182
|
|
|
185
183
|
const getPromptContentWidth = () => computePromptContentWidth(stdout.columns);
|
|
@@ -190,10 +188,8 @@ async function main() {
|
|
|
190
188
|
const currentModel = providerRegistry.getCurrentModel();
|
|
191
189
|
const contextWindow = providerRegistry.getContextWindowForModel(currentModel);
|
|
192
190
|
const rows = stdout.rows || 24;
|
|
193
|
-
// Compact threshold must match buildShellFooter's `compact: height < 30`
|
|
194
|
-
//
|
|
195
|
-
// estimateShellFooterHeight can answer a compact-vs-non-compact query
|
|
196
|
-
// with the wrong cached mode and throw the viewport math off by several rows.
|
|
191
|
+
// Compact threshold must match buildShellFooter's `compact: height < 30` posture below,
|
|
192
|
+
// else estimateShellFooterHeight's cached-height fast path answers with the wrong mode.
|
|
197
193
|
return rows - 2 - estimateShellFooterHeight(promptLines, contextWindow, rows < 30);
|
|
198
194
|
};
|
|
199
195
|
|
|
@@ -223,7 +219,7 @@ async function main() {
|
|
|
223
219
|
stdout,
|
|
224
220
|
ctx,
|
|
225
221
|
noAltScreen: cli.flags.noAltScreen,
|
|
226
|
-
ansi: { CLEAR_SCREEN, ALT_SCREEN_EXIT, PASTE_DISABLE, KEYBOARD_EXT_DISABLE, MOUSE_DISABLE, CURSOR_SHOW },
|
|
222
|
+
ansi: { CLEAR_SCREEN, ALT_SCREEN_EXIT, PASTE_DISABLE, KEYBOARD_EXT_DISABLE, MOUSE_DISABLE, CURSOR_SHOW, FOCUS_DISABLE },
|
|
227
223
|
getInput: () => input,
|
|
228
224
|
render: () => renderScheduler.flushNow(), // resize: synchronous immediate path
|
|
229
225
|
getPromptContentWidth,
|
|
@@ -307,12 +303,7 @@ async function main() {
|
|
|
307
303
|
}
|
|
308
304
|
};
|
|
309
305
|
|
|
310
|
-
const cancelGeneration = ()
|
|
311
|
-
spokenTurns.stop('Spoken output stopped.');
|
|
312
|
-
if (orchestrator.isThinking) {
|
|
313
|
-
orchestrator.abort();
|
|
314
|
-
}
|
|
315
|
-
};
|
|
306
|
+
const cancelGeneration = createCancelGeneration(orchestrator, spokenTurns);
|
|
316
307
|
|
|
317
308
|
const jumpToBookmark = (key: string) => {
|
|
318
309
|
conversation.getDisplayBlocks();
|
|
@@ -341,6 +332,7 @@ async function main() {
|
|
|
341
332
|
commandContext.pasteFromClipboard = () => input.handlePaste();
|
|
342
333
|
commandContext.executeCommand = (name, args) => commandRegistry.execute(name, args, commandContext);
|
|
343
334
|
commandContext.cancelGeneration = cancelGeneration;
|
|
335
|
+
commandContext.isGenerating = () => orchestrator.isThinking;
|
|
344
336
|
commandContext.jumpToBookmark = jumpToBookmark;
|
|
345
337
|
commandContext.scrollToLine = scrollToLine;
|
|
346
338
|
commandContext.clearScreen = () => {
|
|
@@ -348,14 +340,15 @@ async function main() {
|
|
|
348
340
|
allowTerminalWrite(() => stdout.write(CLEAR_SCREEN));
|
|
349
341
|
render();
|
|
350
342
|
};
|
|
351
|
-
|
|
343
|
+
commandContext.requestFullRepaint = () => { compositor.resetDiff(); render(); };
|
|
344
|
+
permissionPromptRef.requestPermission = wrapRequestPermissionWithAlert((request) =>
|
|
352
345
|
new Promise((resolve) => {
|
|
353
346
|
pendingPermission = {
|
|
354
347
|
...request,
|
|
355
|
-
|
|
348
|
+
...buildPendingPermissionExtras(request, resolve),
|
|
356
349
|
};
|
|
357
350
|
render();
|
|
358
|
-
});
|
|
351
|
+
}), { focusTracker: ctx.services.focusTracker, configGet: (k: string) => configManager.get(k as Parameters<typeof configManager.get>[0]), webhookNotifier: ctx.services.webhookNotifier });
|
|
359
352
|
|
|
360
353
|
const input: InputHandler = new InputHandler(
|
|
361
354
|
() => render(),
|
|
@@ -388,7 +381,7 @@ async function main() {
|
|
|
388
381
|
replayEngine: ctx.services.replayEngine,
|
|
389
382
|
webhookNotifier: ctx.services.webhookNotifier,
|
|
390
383
|
policyRuntimeState: ctx.services.policyRuntimeState,
|
|
391
|
-
externalServices: uiServices.platform.externalServices,
|
|
384
|
+
externalServices: uiServices.platform.externalServices, focusTracker: ctx.services.focusTracker,
|
|
392
385
|
},
|
|
393
386
|
shell: {
|
|
394
387
|
bookmarkManager: ctx.services.bookmarkManager,
|
|
@@ -418,9 +411,8 @@ async function main() {
|
|
|
418
411
|
input.setConversationManager(conversation);
|
|
419
412
|
input.setContentWidth(getPromptContentWidth());
|
|
420
413
|
input.filePicker.setOnUpdate(() => render());
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
setOpenAgentDetail((id) => input.agentDetailModal.open(id));
|
|
414
|
+
// W6.1 retirement: agentDetailModal/processModal setOnRefresh wiring removed —
|
|
415
|
+
// those modals were deleted (Fleet subsumes the live process tree via F2).
|
|
424
416
|
|
|
425
417
|
// Model picker callback is handled in bootstrap.ts — do not duplicate here.
|
|
426
418
|
input.setHistory(inputHistory);
|
|
@@ -514,6 +506,8 @@ async function main() {
|
|
|
514
506
|
hitlMode: modeManager.getHITLMode(),
|
|
515
507
|
runningAgentCount,
|
|
516
508
|
runningProcessCount,
|
|
509
|
+
// Composer must not read as focused while the panel/process indicator owns keyboard focus.
|
|
510
|
+
promptFocused: !input.panelFocused && !input.indicatorFocused,
|
|
517
511
|
indicatorFocused: input.indicatorFocused,
|
|
518
512
|
runningAgentProgress: runningAgentSummary.progress,
|
|
519
513
|
composerMode: composerState.modeLabel,
|
|
@@ -558,7 +552,7 @@ async function main() {
|
|
|
558
552
|
// Calculate how many rows are consumed by overlays (thinking, permissions, queue, file picker)
|
|
559
553
|
let overlayRows = 0;
|
|
560
554
|
if (orchestrator.isThinking) overlayRows += 2; // spinner + blank
|
|
561
|
-
if (pendingPermission) overlayRows += PermissionPromptUI.getPromptHeight(pendingPermission);
|
|
555
|
+
if (pendingPermission) overlayRows += PermissionPromptUI.getPromptHeight(pendingPermission, pendingPermission.hunkState);
|
|
562
556
|
overlayRows += orchestrator.messageQueue.length * 3; // queued messages
|
|
563
557
|
// File picker and model picker overlay rows computed from actual rendered line count below
|
|
564
558
|
// Selection modal overlay rows are computed from actual rendered line count below
|
|
@@ -584,6 +578,8 @@ async function main() {
|
|
|
584
578
|
const partialToolPreview = showPreview ? sessionSnapshot.streamToolPreview : undefined;
|
|
585
579
|
// Elapsed from turn start (stream or tool execution), used for the thinking indicator timer.
|
|
586
580
|
const turnElapsedMs = streamMetrics.startTime > 0 ? Date.now() - streamMetrics.startTime : undefined;
|
|
581
|
+
// Suppressed while a tool executes — its ticking timer is the honest indicator then.
|
|
582
|
+
const stallInfo = UIFactory.computeRenderStallInfo(streamMetrics, Date.now());
|
|
587
583
|
const thinking = UIFactory.createThinkingFragment(
|
|
588
584
|
conversationWidth,
|
|
589
585
|
orchestrator.getSpinner(),
|
|
@@ -594,6 +590,7 @@ async function main() {
|
|
|
594
590
|
orchestrator.streamingOutputTokens > 0 ? orchestrator.streamingOutputTokens : undefined,
|
|
595
591
|
turnElapsedMs,
|
|
596
592
|
streamMetrics.ttftMs,
|
|
593
|
+
stallInfo,
|
|
597
594
|
);
|
|
598
595
|
viewport.push(...thinking);
|
|
599
596
|
// Live tool timer: render the currently executing tool row with ticking elapsed.
|
|
@@ -604,7 +601,7 @@ async function main() {
|
|
|
604
601
|
}
|
|
605
602
|
|
|
606
603
|
if (pendingPermission) {
|
|
607
|
-
viewport.push(...PermissionPromptUI.createPromptLines(conversationWidth, pendingPermission));
|
|
604
|
+
viewport.push(...PermissionPromptUI.createPromptLines(conversationWidth, pendingPermission, pendingPermission.hunkState));
|
|
608
605
|
}
|
|
609
606
|
|
|
610
607
|
orchestrator.messageQueue.forEach(msg => {
|
|
@@ -655,6 +652,7 @@ async function main() {
|
|
|
655
652
|
const terminalOutputGuard = installTuiTerminalOutputGuard({ stdout, stderr: process.stderr, notify: (message) => { systemMessageRouter.low(message); render(); } });
|
|
656
653
|
|
|
657
654
|
setRenderRequest(renderNow); // bootstrap's 16ms coalescer calls the composite directly
|
|
655
|
+
setPanelFrameRequester(render); // live panels repaint when idle (Wave-6 replay: fleet sat stale until keypress)
|
|
658
656
|
orchestratorRefs.requestRender = render;
|
|
659
657
|
commandContext.renderRequest = render;
|
|
660
658
|
wireShellUiOpeners({
|
|
@@ -670,6 +668,7 @@ async function main() {
|
|
|
670
668
|
subscriptionManager,
|
|
671
669
|
secretsManager,
|
|
672
670
|
serviceRegistry: ctx.services.serviceRegistry,
|
|
671
|
+
memoryEmbeddingRegistry: ctx.services.memoryEmbeddingRegistry,
|
|
673
672
|
workingDirectory: workingDir,
|
|
674
673
|
homeDirectory,
|
|
675
674
|
getConfiguredProviderIds: ctx._getConfiguredProviderIds,
|
|
@@ -692,15 +691,14 @@ async function main() {
|
|
|
692
691
|
gitStatusProvider,
|
|
693
692
|
lastGitInfoRef,
|
|
694
693
|
buildSessionContinuityHints,
|
|
695
|
-
render, webhookNotifier: ctx.services.webhookNotifier,
|
|
694
|
+
render, webhookNotifier: ctx.services.webhookNotifier, focusTracker: ctx.services.focusTracker,
|
|
696
695
|
});
|
|
697
696
|
unsubs.push(...turnUnsubs);
|
|
698
697
|
|
|
699
698
|
// Stable turn context for failover retry — set in submitInput, read by retryTurn.
|
|
700
699
|
let retryCtx: { count: number; text: string; content?: ContentPart[]; opts?: Parameters<typeof orchestrator.handleUserInput>[2] } | null = null;
|
|
701
|
-
// One-key retry affordance
|
|
702
|
-
//
|
|
703
|
-
// picker. Any other character clears the affordance and routes normally.
|
|
700
|
+
// One-key retry affordance, active right after a user-visible TURN_ERROR: 'r' re-submits on the
|
|
701
|
+
// current provider, 'm' opens the model picker, any other character clears it and routes normally.
|
|
704
702
|
let errorAffordanceActive = false;
|
|
705
703
|
const retryTurn = (): void => {
|
|
706
704
|
if (!retryCtx) return;
|
|
@@ -724,9 +722,8 @@ async function main() {
|
|
|
724
722
|
}
|
|
725
723
|
});
|
|
726
724
|
|
|
727
|
-
// Register terminal-restoring crash/termination handlers BEFORE entering raw mode so a
|
|
728
|
-
//
|
|
729
|
-
// 'exit' listener is the final safety net for any process.exit path.
|
|
725
|
+
// Register terminal-restoring crash/termination handlers BEFORE entering raw mode so a throw
|
|
726
|
+
// during setup or the initial render still restores the terminal; 'exit' is the final safety net.
|
|
730
727
|
process.on('uncaughtException', uncaughtExceptionHandler);
|
|
731
728
|
process.on('SIGTERM', terminationSignalHandler);
|
|
732
729
|
process.on('SIGHUP', terminationSignalHandler);
|
|
@@ -736,7 +733,7 @@ async function main() {
|
|
|
736
733
|
stdin.setRawMode(true);
|
|
737
734
|
stdin.resume();
|
|
738
735
|
stdin.setEncoding('utf8');
|
|
739
|
-
allowTerminalWrite(() => stdout.write((cli.flags.noAltScreen ? '' : ALT_SCREEN_ENTER) + CLEAR_SCREEN + CURSOR_HIDE + MOUSE_ENABLE + KEYBOARD_EXT_ENABLE + PASTE_ENABLE));
|
|
736
|
+
allowTerminalWrite(() => stdout.write((cli.flags.noAltScreen ? '' : ALT_SCREEN_ENTER) + CLEAR_SCREEN + CURSOR_HIDE + MOUSE_ENABLE + KEYBOARD_EXT_ENABLE + PASTE_ENABLE + FOCUS_ENABLE));
|
|
740
737
|
|
|
741
738
|
applyInitialTuiCliState({ cli, input, commandRegistry, commandContext, shellPaths: ctx.services.shellPaths, render });
|
|
742
739
|
|