@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,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
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// panel-mouse-geometry.ts
|
|
3
|
+
//
|
|
4
|
+
// Pure mouse-geometry helpers for the panel workspace, extracted from
|
|
5
|
+
// handler-feed-routes.ts so that file stays under the 800-line architecture
|
|
6
|
+
// cap once the UX-C focus verbs and the DEBT-5 paste-flood guard both live in
|
|
7
|
+
// it. These map a terminal (row, col) onto the panel/pane/workspace-tab under
|
|
8
|
+
// the cursor. No dependency on the feed-route state types, so no import cycle.
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
import type { PanelManager } from '../panels/panel-manager.ts';
|
|
12
|
+
import { renderPanelWorkspaceBar } from '../renderer/panel-workspace-bar.ts';
|
|
13
|
+
import type { TabHitRegion } from '../renderer/tab-strip.ts';
|
|
14
|
+
|
|
15
|
+
/** Screen rectangle the panel workspace occupies, plus its vertical split. */
|
|
16
|
+
export type PanelMouseLayout = {
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
hasBottomPane: boolean;
|
|
22
|
+
verticalSplitRatio: number;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function clampRatio(value: number): number {
|
|
26
|
+
return Math.max(0.2, Math.min(0.8, value));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getActivePanelInPane(panelManager: PanelManager, pane: 'top' | 'bottom') {
|
|
30
|
+
const target = pane === 'top' ? panelManager.getTopPane() : panelManager.getBottomPane();
|
|
31
|
+
return target.panels[target.activeIndex] ?? null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function getPanelUnderMouse(
|
|
35
|
+
panelManager: PanelManager,
|
|
36
|
+
layout: PanelMouseLayout | null,
|
|
37
|
+
row: number,
|
|
38
|
+
col: number,
|
|
39
|
+
) {
|
|
40
|
+
if (
|
|
41
|
+
layout === null
|
|
42
|
+
|| !panelManager.isVisible()
|
|
43
|
+
|| panelManager.getAllOpen().length === 0
|
|
44
|
+
|| col < layout.x
|
|
45
|
+
|| col >= layout.x + layout.width
|
|
46
|
+
|| row < layout.y
|
|
47
|
+
|| row >= layout.y + layout.height
|
|
48
|
+
) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const panelRow = row - layout.y;
|
|
53
|
+
if (!layout.hasBottomPane) {
|
|
54
|
+
return getActivePanelInPane(panelManager, 'top');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Single consolidated workspace bar (row 0) + h-separator; the rest splits
|
|
58
|
+
// between the two panes' content.
|
|
59
|
+
const panelAreaRows = Math.max(0, layout.height - 1);
|
|
60
|
+
const contentRows = Math.max(0, panelAreaRows - 1);
|
|
61
|
+
const topContentRows = contentRows <= 1
|
|
62
|
+
? contentRows
|
|
63
|
+
: Math.max(1, Math.floor(contentRows * clampRatio(layout.verticalSplitRatio)));
|
|
64
|
+
// panelRow 0 = workspace bar; rows 1..topContentRows = top pane; rest = bottom.
|
|
65
|
+
return panelRow <= topContentRows
|
|
66
|
+
? getActivePanelInPane(panelManager, 'top')
|
|
67
|
+
: getActivePanelInPane(panelManager, 'bottom');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* If the mouse is over the consolidated workspace tab bar (the first panel
|
|
72
|
+
* row), return the index of the tab under the cursor, else null. Recomputes the
|
|
73
|
+
* tab hit regions by rendering the bar with a layout callback — cheap and keeps
|
|
74
|
+
* the click geometry in lockstep with what was drawn.
|
|
75
|
+
*/
|
|
76
|
+
export function workspaceTabAtMouse(
|
|
77
|
+
panelManager: PanelManager,
|
|
78
|
+
layout: PanelMouseLayout | null,
|
|
79
|
+
row: number,
|
|
80
|
+
col: number,
|
|
81
|
+
): number | null {
|
|
82
|
+
if (
|
|
83
|
+
layout === null
|
|
84
|
+
|| !panelManager.isVisible()
|
|
85
|
+
|| panelManager.getAllOpen().length === 0
|
|
86
|
+
|| row !== layout.y // workspace bar is the first panel row
|
|
87
|
+
|| col < layout.x
|
|
88
|
+
|| col >= layout.x + layout.width
|
|
89
|
+
) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
let regions: readonly TabHitRegion[] = [];
|
|
93
|
+
renderPanelWorkspaceBar(panelManager.getWorkspaceTabs(), layout.width, true, (r) => { regions = r; });
|
|
94
|
+
const relCol = col - layout.x;
|
|
95
|
+
const hit = regions.find((rg) => relCol >= rg.startCol && relCol < rg.endCol);
|
|
96
|
+
return hit ? hit.index : null;
|
|
97
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// panel-paste-flood-guard.ts — DEBT-5 item 5.
|
|
3
|
+
//
|
|
4
|
+
// A terminal WITHOUT bracketed paste delivers a pasted block as a burst of
|
|
5
|
+
// discrete 1-char 'text' tokens (isPasteToken stays false for every one of
|
|
6
|
+
// them, since that flag only fires for a single token whose value.length > 1
|
|
7
|
+
// — see handler-feed-routes.ts's own Invariant B doc). Before this guard,
|
|
8
|
+
// each such character became a real panel hotkey on a focused non-capturing
|
|
9
|
+
// panel (K arms kill, etc.) once it reached handlePanelFocusToken's per-char
|
|
10
|
+
// dispatch loop.
|
|
11
|
+
//
|
|
12
|
+
// This is a RATE guard: more than PANEL_PASTE_FLOOD_THRESHOLD qualifying
|
|
13
|
+
// tokens within the trailing PANEL_PASTE_FLOOD_WINDOW_MS, evaluated with a
|
|
14
|
+
// real sliding window (old timestamps age out of `timestamps` every call).
|
|
15
|
+
// It is deliberately NOT the retired per-feed char-SUM burst heuristic this
|
|
16
|
+
// same input layer already tore out once (see panel-focus-burst.test.ts's
|
|
17
|
+
// header doc): that old guard summed one feed()'s character count with no
|
|
18
|
+
// timing signal at all, so two ordinary nav keystrokes landing in a single
|
|
19
|
+
// feed() — a real, common case, not an edge case — were misread as a burst
|
|
20
|
+
// and focus was silently flipped to the composer. This guard:
|
|
21
|
+
// - never touches focus (panelFocused is untouched by trackPanelPasteFloodGuard
|
|
22
|
+
// and by its caller);
|
|
23
|
+
// - is keyed on WALL-CLOCK TIMING, not a per-feed token count, so it
|
|
24
|
+
// doesn't care how many tokens land in one feed() call, only how fast
|
|
25
|
+
// they arrive relative to each other;
|
|
26
|
+
// - is sticky once tripped (only a quiet gap — no qualifying token for a
|
|
27
|
+
// full window — clears it) so it doesn't flap dispatch on/off as the
|
|
28
|
+
// count oscillates near the threshold mid-flood.
|
|
29
|
+
//
|
|
30
|
+
// ~8 keys/120ms is far beyond sustained human typing (a fast typist peaks
|
|
31
|
+
// well under that inter-key rate over any real span) but is exactly the
|
|
32
|
+
// shape an unbracketed paste replay takes. It also keeps the pre-existing
|
|
33
|
+
// Invariant B regression tests (2 keys in one feed(), no timing control at
|
|
34
|
+
// all) far under threshold while catching a real flood within its first ~9
|
|
35
|
+
// characters.
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
export const PANEL_PASTE_FLOOD_WINDOW_MS = 120;
|
|
39
|
+
export const PANEL_PASTE_FLOOD_THRESHOLD = 8;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Guard state — a single persistent instance lives on the caller's
|
|
43
|
+
* long-lived context (handler-feed.ts's InputFeedContext, mirroring how that
|
|
44
|
+
* object already owns `nextPasteId`/`mouseDownRow`/etc.) and is MUTATED IN
|
|
45
|
+
* PLACE by trackPanelPasteFloodGuard below, never replaced — so callers never
|
|
46
|
+
* need to thread a return value back into their own state (unlike
|
|
47
|
+
* `panelFocused`, which handlePanelFocusToken returns because it is NOT a
|
|
48
|
+
* mutable outparam).
|
|
49
|
+
*/
|
|
50
|
+
export interface PanelBurstGuardState {
|
|
51
|
+
timestamps: readonly number[];
|
|
52
|
+
suspended: boolean;
|
|
53
|
+
hintShown: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface PanelBurstGuardResult {
|
|
57
|
+
/** False while suspended — the caller must drop this token, not dispatch it. */
|
|
58
|
+
readonly dispatch: boolean;
|
|
59
|
+
/** True exactly once per burst: the call that just tripped suspension. */
|
|
60
|
+
readonly showHintNow: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Advance `guard` (mutated in place) by one qualifying token at time `now` (ms). */
|
|
64
|
+
export function trackPanelPasteFloodGuard(guard: PanelBurstGuardState, now: number): PanelBurstGuardResult {
|
|
65
|
+
const lastAt = guard.timestamps.length > 0 ? guard.timestamps[guard.timestamps.length - 1]! : -Infinity;
|
|
66
|
+
const isQuietGap = now - lastAt > PANEL_PASTE_FLOOD_WINDOW_MS;
|
|
67
|
+
if (isQuietGap && guard.suspended) {
|
|
68
|
+
// A silence at least as long as the window means whatever burst was
|
|
69
|
+
// happening has ended — un-suspend so a LATER burst gets its own fresh
|
|
70
|
+
// count and its own one-shot hint.
|
|
71
|
+
guard.suspended = false;
|
|
72
|
+
guard.hintShown = false;
|
|
73
|
+
}
|
|
74
|
+
guard.timestamps = isQuietGap
|
|
75
|
+
? [now]
|
|
76
|
+
: [...guard.timestamps.filter((t) => t > now - PANEL_PASTE_FLOOD_WINDOW_MS), now];
|
|
77
|
+
if (guard.timestamps.length > PANEL_PASTE_FLOOD_THRESHOLD) {
|
|
78
|
+
guard.suspended = true;
|
|
79
|
+
}
|
|
80
|
+
let showHintNow = false;
|
|
81
|
+
if (guard.suspended && !guard.hintShown) {
|
|
82
|
+
guard.hintShown = true;
|
|
83
|
+
showHintNow = true;
|
|
84
|
+
}
|
|
85
|
+
return { dispatch: !guard.suspended, showHintNow };
|
|
86
|
+
}
|
|
@@ -36,6 +36,14 @@ export class SelectionModal {
|
|
|
36
36
|
public selectedIndex = 0;
|
|
37
37
|
public allowSearch = true;
|
|
38
38
|
public customActions: Map<string, SelectionAction> = new Map();
|
|
39
|
+
/**
|
|
40
|
+
* UX-C vocab unification: overrides the default per-primaryAction Enter
|
|
41
|
+
* verb ("Select"/"Toggle"/"Edit"/"Delete") shown in the footer hint. Used by
|
|
42
|
+
* callers whose items are really commands (e.g. /help) so the hint reads
|
|
43
|
+
* "[Enter] Run", matching the slash-command palette's own verb, instead of
|
|
44
|
+
* the generic "Select".
|
|
45
|
+
*/
|
|
46
|
+
public primaryVerbLabel: string | undefined = undefined;
|
|
39
47
|
|
|
40
48
|
/** Open the modal with items and title */
|
|
41
49
|
open(
|
|
@@ -45,6 +53,7 @@ export class SelectionModal {
|
|
|
45
53
|
preSelectId?: string;
|
|
46
54
|
allowSearch?: boolean;
|
|
47
55
|
customActions?: Map<string, SelectionAction>;
|
|
56
|
+
primaryVerbLabel?: string;
|
|
48
57
|
}
|
|
49
58
|
): void {
|
|
50
59
|
this.title = title;
|
|
@@ -53,6 +62,7 @@ export class SelectionModal {
|
|
|
53
62
|
this.allowSearch = opts?.allowSearch ?? true;
|
|
54
63
|
this.searchFocused = false;
|
|
55
64
|
this.customActions = opts?.customActions ?? new Map();
|
|
65
|
+
this.primaryVerbLabel = opts?.primaryVerbLabel;
|
|
56
66
|
this.active = true;
|
|
57
67
|
this.filterItems();
|
|
58
68
|
|
|
@@ -74,6 +84,7 @@ export class SelectionModal {
|
|
|
74
84
|
this.filteredItems = [];
|
|
75
85
|
this.selectedIndex = 0;
|
|
76
86
|
this.customActions = new Map();
|
|
87
|
+
this.primaryVerbLabel = undefined;
|
|
77
88
|
}
|
|
78
89
|
|
|
79
90
|
moveUp(): void {
|
|
@@ -1,12 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SessionPickerModal — state management for the /sessions picker modal.
|
|
3
3
|
*
|
|
4
|
-
* Lists
|
|
5
|
-
*
|
|
4
|
+
* Lists LOCAL saved transcript files from SessionManager.list() (load/delete
|
|
5
|
+
* operate on these — a saved JSONL session is a different thing from a live
|
|
6
|
+
* control-plane session, see below), tracks selected index, and handles
|
|
7
|
+
* load/delete actions.
|
|
8
|
+
*
|
|
9
|
+
* W3-T2 (union-sessions surface): additionally surfaces the cross-surface
|
|
10
|
+
* session union from `sessionBroker` (a SessionReadFacade — normally
|
|
11
|
+
* `uiServices.sessions.sessionBroker`, the SessionUnionCache) so a user can
|
|
12
|
+
* SEE what sessions are live/closed across every surface sharing this
|
|
13
|
+
* daemon (TUI, webui, companion, automation), not just this process's own
|
|
14
|
+
* saved files. This is deliberately READ-ONLY: a SharedSessionRecord has no
|
|
15
|
+
* on-disk transcript this process can load()/delete() the way a local
|
|
16
|
+
* SessionInfo does, so cross-surface rows are visible + badged, not
|
|
17
|
+
* selectable for load/delete. `sessionBroker` is optional so every existing
|
|
18
|
+
* caller/test that only cares about local saved-session management keeps
|
|
19
|
+
* working unchanged.
|
|
6
20
|
*/
|
|
7
21
|
|
|
8
22
|
import { unlinkSync } from 'node:fs';
|
|
9
23
|
import type { SessionInfo, SessionManager } from '@pellux/goodvibes-sdk/platform/sessions';
|
|
24
|
+
import type { SharedSessionRecord } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
25
|
+
import type { CrossSurfaceView, SessionReadFacade } from '@pellux/goodvibes-sdk/platform/runtime/session-spine';
|
|
10
26
|
import type { ConversationManager } from '../core/conversation';
|
|
11
27
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
12
28
|
|
|
@@ -14,9 +30,22 @@ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
|
14
30
|
// SessionPickerModal
|
|
15
31
|
// ---------------------------------------------------------------------------
|
|
16
32
|
|
|
33
|
+
/** Honest default posture when no `sessionBroker` was wired: no cross-surface claim. */
|
|
34
|
+
const DORMANT_CROSS_SURFACE_VIEW: CrossSurfaceView = {
|
|
35
|
+
mode: 'local',
|
|
36
|
+
online: false,
|
|
37
|
+
stale: false,
|
|
38
|
+
lastSyncAt: null,
|
|
39
|
+
offlineNote: null,
|
|
40
|
+
};
|
|
41
|
+
|
|
17
42
|
export class SessionPickerModal {
|
|
18
43
|
public active = false;
|
|
19
44
|
public sessions: SessionInfo[] = [];
|
|
45
|
+
/** Cross-surface session union (view-only) — see class doc. Empty until open(). */
|
|
46
|
+
public crossSurfaceSessions: readonly SharedSessionRecord[] = [];
|
|
47
|
+
/** Honest posture for crossSurfaceSessions: mode/online/stale/offlineNote. */
|
|
48
|
+
public crossSurfaceView: CrossSurfaceView = DORMANT_CROSS_SURFACE_VIEW;
|
|
20
49
|
public selectedIndex = 0;
|
|
21
50
|
public scrollOffset = 0;
|
|
22
51
|
public visibleRows = 8;
|
|
@@ -25,13 +54,24 @@ export class SessionPickerModal {
|
|
|
25
54
|
/** Last status message to show in the modal (e.g. error or success). */
|
|
26
55
|
public statusMessage = '';
|
|
27
56
|
|
|
28
|
-
public constructor(
|
|
57
|
+
public constructor(
|
|
58
|
+
private readonly sessionManager: SessionManager,
|
|
59
|
+
private readonly sessionBroker?: SessionReadFacade,
|
|
60
|
+
) {}
|
|
29
61
|
|
|
30
62
|
/**
|
|
31
|
-
* Open the modal, loading sessions from SessionManager
|
|
63
|
+
* Open the modal, loading local saved sessions from SessionManager and (when
|
|
64
|
+
* a sessionBroker was wired) the cross-surface session union, honestly.
|
|
32
65
|
*/
|
|
33
66
|
open(): void {
|
|
34
67
|
this.sessions = this.sessionManager.list();
|
|
68
|
+
if (this.sessionBroker) {
|
|
69
|
+
this.crossSurfaceSessions = this.sessionBroker.listSessions();
|
|
70
|
+
this.crossSurfaceView = this.sessionBroker.crossSurfaceView;
|
|
71
|
+
} else {
|
|
72
|
+
this.crossSurfaceSessions = [];
|
|
73
|
+
this.crossSurfaceView = DORMANT_CROSS_SURFACE_VIEW;
|
|
74
|
+
}
|
|
35
75
|
this.selectedIndex = 0;
|
|
36
76
|
this.scrollOffset = 0;
|
|
37
77
|
this.statusMessage = '';
|