@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,496 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// SessionBrowserPanel — browse, search, and resume old sessions.
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
|
|
5
|
-
import type { Line } from '../types/grid.ts';
|
|
6
|
-
import { BasePanel } from './base-panel.ts';
|
|
7
|
-
import type { PanelIntegrationContext } from './types.ts';
|
|
8
|
-
import type { SessionInfo } from '@pellux/goodvibes-sdk/platform/sessions';
|
|
9
|
-
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
10
|
-
import type { SessionBrowserQuery } from '../runtime/ui-service-queries.ts';
|
|
11
|
-
import {
|
|
12
|
-
buildEmptyState,
|
|
13
|
-
buildKeyboardHints,
|
|
14
|
-
buildPanelLine,
|
|
15
|
-
buildSearchInputLine,
|
|
16
|
-
buildStyledPanelLine,
|
|
17
|
-
buildPanelWorkspace,
|
|
18
|
-
resolveScrollablePanelSection,
|
|
19
|
-
DEFAULT_PANEL_PALETTE,
|
|
20
|
-
extendPalette,
|
|
21
|
-
type PanelWorkspaceSection,
|
|
22
|
-
} from './polish.ts';
|
|
23
|
-
import { fitDisplay, truncateDisplay } from '../utils/terminal-width.ts';
|
|
24
|
-
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
25
|
-
import {
|
|
26
|
-
getPanelSearchFocusTransition,
|
|
27
|
-
isPanelSearchBackspace,
|
|
28
|
-
isPanelSearchCancel,
|
|
29
|
-
isPanelSearchCommit,
|
|
30
|
-
isPanelSearchPrintable,
|
|
31
|
-
} from './search-focus.ts';
|
|
32
|
-
import { type ConfirmState, handleConfirmInput } from './confirm-state.ts';
|
|
33
|
-
|
|
34
|
-
// Panel-specific extras only; shared tones come from DEFAULT_PANEL_PALETTE so
|
|
35
|
-
// theme changes propagate. `selected` is a deliberate bright-cyan cursor accent
|
|
36
|
-
// and the date/model/count column tints have no clean shared equivalent.
|
|
37
|
-
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
38
|
-
selected: '#00ffff',
|
|
39
|
-
dateFg: '#6699aa',
|
|
40
|
-
modelFg: '#99aacc',
|
|
41
|
-
countFg: '#88bbcc',
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
// Splits a computed '/command arg1 arg2' string into the { name, args } shape
|
|
45
|
-
// ctx.executeCommand expects — same leading-slash-stripping approach used by
|
|
46
|
-
// remote-panel.ts for its own dispatched command.
|
|
47
|
-
function parseCommand(command: string): { name: string; args: string[] } | null {
|
|
48
|
-
const parts = command.replace(/^\//, '').split(/\s+/).filter(Boolean);
|
|
49
|
-
const [name, ...args] = parts;
|
|
50
|
-
return name ? { name, args } : null;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function shortDate(ts: number): string {
|
|
54
|
-
const d = new Date(ts);
|
|
55
|
-
const Y = d.getFullYear();
|
|
56
|
-
const M = String(d.getMonth() + 1).padStart(2, '0');
|
|
57
|
-
const D = String(d.getDate()).padStart(2, '0');
|
|
58
|
-
const h = String(d.getHours()).padStart(2, '0');
|
|
59
|
-
const m = String(d.getMinutes()).padStart(2, '0');
|
|
60
|
-
return `${Y}-${M}-${D} ${h}:${m}`;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function formatReturnContextLines(returnContext: SessionInfo['returnContext']): string[] {
|
|
64
|
-
if (!returnContext) return [];
|
|
65
|
-
const lines: string[] = [];
|
|
66
|
-
if (returnContext.activityLabel) lines.push(`activity: ${returnContext.activityLabel}`);
|
|
67
|
-
if (returnContext.statusLabel) lines.push(`status: ${returnContext.statusLabel}`);
|
|
68
|
-
if (returnContext.activeTasks || returnContext.blockedTasks || returnContext.pendingApprovals) {
|
|
69
|
-
lines.push(`tasks: active=${returnContext.activeTasks ?? 0} blocked=${returnContext.blockedTasks ?? 0} approvals=${returnContext.pendingApprovals ?? 0}`);
|
|
70
|
-
}
|
|
71
|
-
if (returnContext.remoteRunners?.length) {
|
|
72
|
-
lines.push(`remote runners: ${returnContext.remoteRunners.join(', ')}`);
|
|
73
|
-
}
|
|
74
|
-
if (returnContext.worktreePaths?.length) {
|
|
75
|
-
lines.push(`worktrees: ${returnContext.worktreePaths.join(', ')}`);
|
|
76
|
-
}
|
|
77
|
-
if (returnContext.openPanels?.length) {
|
|
78
|
-
lines.push(`open panels: ${returnContext.openPanels.join(', ')}`);
|
|
79
|
-
}
|
|
80
|
-
return lines;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// ---------------------------------------------------------------------------
|
|
84
|
-
// Confirmation state for deletion
|
|
85
|
-
// ---------------------------------------------------------------------------
|
|
86
|
-
// ConfirmState<string> — subject holds the session name to delete
|
|
87
|
-
|
|
88
|
-
export class SessionBrowserPanel extends BasePanel {
|
|
89
|
-
private sessions: SessionInfo[] = [];
|
|
90
|
-
private filtered: SessionInfo[] = [];
|
|
91
|
-
private searchQuery = '';
|
|
92
|
-
private searching = false; // true when user is actively typing a search
|
|
93
|
-
private cursorIndex = 0;
|
|
94
|
-
private scrollOffset = 0;
|
|
95
|
-
private confirm: ConfirmState<string> | null = null;
|
|
96
|
-
private deleteError = '';
|
|
97
|
-
private loadError = '';
|
|
98
|
-
private hasLoaded = false;
|
|
99
|
-
private refreshTimerId: ReturnType<typeof setInterval> | null = null;
|
|
100
|
-
// x = dispatch the computed next-step command ('/remote recover <runner>'
|
|
101
|
-
// or '/session resume <name>') via the same handleInput -> handlePanelIntegrationAction
|
|
102
|
-
// bridge remote-panel.ts uses — handleInput has no ctx.executeCommand.
|
|
103
|
-
private pendingCommand: { name: string; args: string[] } | null = null;
|
|
104
|
-
// On resume, restore any panels the session had open when it was saved.
|
|
105
|
-
private pendingOpenPanels: string[] | null = null;
|
|
106
|
-
|
|
107
|
-
constructor(
|
|
108
|
-
private readonly sessionManager: SessionBrowserQuery,
|
|
109
|
-
private resumeSession?: (sessionId: string) => void,
|
|
110
|
-
) {
|
|
111
|
-
super('sessions', 'Sessions', 'H', 'session');
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
override onActivate(): void {
|
|
115
|
-
super.onActivate();
|
|
116
|
-
this._load();
|
|
117
|
-
this.refreshTimerId = this.registerTimer(setInterval(() => { this._load(); }, 5000));
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Clears any active search/filter and moves the cursor to the session
|
|
122
|
-
* matching `sessionId` (SessionInfo.name) so a sibling panel (e.g.
|
|
123
|
-
* RoutesPanel's Enter-on-binding jump) can land directly on it. No-ops
|
|
124
|
-
* (beyond clearing the filter) if the session isn't found.
|
|
125
|
-
*/
|
|
126
|
-
focusSession(sessionId: string): void {
|
|
127
|
-
this.searching = false;
|
|
128
|
-
this.searchQuery = '';
|
|
129
|
-
this._filter();
|
|
130
|
-
const index = this.filtered.findIndex((session) => session.name === sessionId);
|
|
131
|
-
if (index >= 0) this.cursorIndex = index;
|
|
132
|
-
this.markDirty();
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
override onDeactivate(): void {
|
|
136
|
-
if (this.refreshTimerId !== null) { this.clearTimer(this.refreshTimerId); this.refreshTimerId = null; }
|
|
137
|
-
this.searching = false;
|
|
138
|
-
this.confirm = null;
|
|
139
|
-
super.onDeactivate();
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* The `/`-to-search buffer wants every character of a burst (paste, or a
|
|
144
|
-
* fast-typed query landing in one input.feed() call), same as it always
|
|
145
|
-
* has — see the interface doc on `Panel.isCapturingTextBurst`.
|
|
146
|
-
*/
|
|
147
|
-
isCapturingTextBurst(): boolean {
|
|
148
|
-
return this.searching;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
handleInput(key: string): boolean {
|
|
152
|
-
// Confirmation dialog — use shared handleConfirmInput for y/n/Esc UX
|
|
153
|
-
const confirmResult = handleConfirmInput(this.confirm, key);
|
|
154
|
-
if (confirmResult === 'confirmed') {
|
|
155
|
-
this._deleteConfirmed();
|
|
156
|
-
return true;
|
|
157
|
-
}
|
|
158
|
-
if (confirmResult === 'cancelled') {
|
|
159
|
-
this.confirm = null;
|
|
160
|
-
this.markDirty();
|
|
161
|
-
return true;
|
|
162
|
-
}
|
|
163
|
-
if (confirmResult === 'absorbed') return true;
|
|
164
|
-
|
|
165
|
-
// Search mode
|
|
166
|
-
if (this.searching) {
|
|
167
|
-
const transition = getPanelSearchFocusTransition(key, { selectedIndex: this.cursorIndex, itemCount: this.filtered.length });
|
|
168
|
-
if (transition === 'focus-list') {
|
|
169
|
-
this.searching = false;
|
|
170
|
-
this.cursorIndex = 0;
|
|
171
|
-
this.markDirty();
|
|
172
|
-
return true;
|
|
173
|
-
}
|
|
174
|
-
if (isPanelSearchCancel(key) || isPanelSearchCommit(key)) {
|
|
175
|
-
this.searching = false;
|
|
176
|
-
this.markDirty();
|
|
177
|
-
return true;
|
|
178
|
-
}
|
|
179
|
-
if (isPanelSearchBackspace(key)) {
|
|
180
|
-
this.searchQuery = this.searchQuery.slice(0, -1);
|
|
181
|
-
this._filter();
|
|
182
|
-
return true;
|
|
183
|
-
}
|
|
184
|
-
if (isPanelSearchPrintable(key)) {
|
|
185
|
-
this.searchQuery += key;
|
|
186
|
-
this._filter();
|
|
187
|
-
return true;
|
|
188
|
-
}
|
|
189
|
-
return false;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const transition = getPanelSearchFocusTransition(key, { selectedIndex: this.cursorIndex, itemCount: this.filtered.length });
|
|
193
|
-
if (transition === 'focus-search') {
|
|
194
|
-
this._startSearch();
|
|
195
|
-
return true;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
switch (key) {
|
|
199
|
-
case 'up': this._move(-1); return true;
|
|
200
|
-
case 'down': this._move(1); return true;
|
|
201
|
-
case 'pageup': this._move(-10); return true;
|
|
202
|
-
case 'pagedown': this._move(10); return true;
|
|
203
|
-
case 'return': this._resume(); return true;
|
|
204
|
-
case 'd': this._promptDelete(); return true;
|
|
205
|
-
case 'r': this._load(); return true;
|
|
206
|
-
case 'x': this._dispatchNextStep(); return true;
|
|
207
|
-
default: return false;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Drains `pendingCommand` (set by the 'x' key) and `pendingOpenPanels`
|
|
213
|
-
* (set by `_resume()`) via the ctx.executeCommand / PanelManager bridge.
|
|
214
|
-
* Called by the input router immediately after handleInput() consumes the
|
|
215
|
-
* same key (panel-integration-actions.ts's onPanelInputConsumed).
|
|
216
|
-
*/
|
|
217
|
-
handlePanelIntegrationAction(_key: string, ctx: PanelIntegrationContext): boolean {
|
|
218
|
-
let consumed = false;
|
|
219
|
-
if (this.pendingCommand) {
|
|
220
|
-
const { name, args } = this.pendingCommand;
|
|
221
|
-
this.pendingCommand = null;
|
|
222
|
-
void ctx.executeCommand?.(name, args);
|
|
223
|
-
consumed = true;
|
|
224
|
-
}
|
|
225
|
-
if (this.pendingOpenPanels) {
|
|
226
|
-
const openPanels = this.pendingOpenPanels;
|
|
227
|
-
this.pendingOpenPanels = null;
|
|
228
|
-
for (const panelId of openPanels) ctx.panelManager.open(panelId);
|
|
229
|
-
consumed = true;
|
|
230
|
-
}
|
|
231
|
-
return consumed;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
render(width: number, height: number): Line[] {
|
|
235
|
-
if (height <= 0 || width <= 0) return [];
|
|
236
|
-
const intro = 'Browse, search, resume, and prune saved conversations.';
|
|
237
|
-
|
|
238
|
-
// Lazily load on first render so the panel works even when rendered without
|
|
239
|
-
// onActivate(). The session list is a synchronous source, so this resolves
|
|
240
|
-
// immediately rather than flashing a loading screen.
|
|
241
|
-
if (!this.hasLoaded) this._load();
|
|
242
|
-
|
|
243
|
-
const count = this.filtered.length;
|
|
244
|
-
const total = this.sessions.length;
|
|
245
|
-
const hasSelection = this.filtered.length > 0;
|
|
246
|
-
// Search/status line — surfaces the active query or any error.
|
|
247
|
-
const searchLine = this.searching
|
|
248
|
-
? ` Search: ${this.searchQuery}_`
|
|
249
|
-
: this.loadError
|
|
250
|
-
? ` Error: ${this.loadError}`
|
|
251
|
-
: this.deleteError
|
|
252
|
-
? ` Error: ${this.deleteError}`
|
|
253
|
-
: this.searchQuery
|
|
254
|
-
? ` Filter: ${this.searchQuery}`
|
|
255
|
-
: ` (no filter)`;
|
|
256
|
-
const statusFg = this.loadError || this.deleteError ? DEFAULT_PANEL_PALETTE.bad : this.searching ? DEFAULT_PANEL_PALETTE.info : DEFAULT_PANEL_PALETTE.dim;
|
|
257
|
-
// Context-aware hints: only advertise keys that work in the current state.
|
|
258
|
-
const hints: Array<{ keys: string; label: string }> = [];
|
|
259
|
-
if (this.searching) {
|
|
260
|
-
hints.push({ keys: 'type', label: 'filter' }, { keys: 'Esc/Enter', label: 'apply' });
|
|
261
|
-
} else {
|
|
262
|
-
hints.push({ keys: '/', label: this.searchQuery ? 'edit search' : 'search' });
|
|
263
|
-
if (hasSelection) hints.push({ keys: 'Enter', label: 'resume' }, { keys: 'x', label: 'run next step' }, { keys: 'd', label: 'delete' });
|
|
264
|
-
hints.push({ keys: 'r', label: 'refresh' });
|
|
265
|
-
}
|
|
266
|
-
const footerLines = [
|
|
267
|
-
buildSearchInputLine(width, '', searchLine.trimStart(), DEFAULT_PANEL_PALETTE, { active: this.searching, valueColor: statusFg }),
|
|
268
|
-
buildKeyboardHints(width, hints, DEFAULT_PANEL_PALETTE),
|
|
269
|
-
];
|
|
270
|
-
|
|
271
|
-
if (this.confirm) {
|
|
272
|
-
return buildPanelWorkspace(width, height, {
|
|
273
|
-
title: ` Sessions [${count}/${total}]`,
|
|
274
|
-
intro,
|
|
275
|
-
sections: [
|
|
276
|
-
{
|
|
277
|
-
title: 'Confirmation',
|
|
278
|
-
lines: [
|
|
279
|
-
buildPanelLine(width, [[` Delete "${this.confirm.subject}"?`, DEFAULT_PANEL_PALETTE.warn]]),
|
|
280
|
-
buildPanelLine(width, [[' y', DEFAULT_PANEL_PALETTE.info], [' confirm delete', DEFAULT_PANEL_PALETTE.dim], [' n / Esc', DEFAULT_PANEL_PALETTE.info], [' cancel', DEFAULT_PANEL_PALETTE.dim]]),
|
|
281
|
-
],
|
|
282
|
-
},
|
|
283
|
-
],
|
|
284
|
-
footerLines,
|
|
285
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
if (this.filtered.length === 0) {
|
|
290
|
-
const emptyTitle = this.searchQuery ? ` No sessions match "${this.searchQuery}"` : ' No sessions found';
|
|
291
|
-
const emptyBody = this.searchQuery
|
|
292
|
-
? 'Clear or change the current filter to surface saved conversations again.'
|
|
293
|
-
: 'Conversations are saved automatically. Once you have saved sessions, they appear here for review and resume.';
|
|
294
|
-
const emptyActions = this.searchQuery
|
|
295
|
-
? [{ command: 'Esc', summary: 'clear the filter and show all sessions' }]
|
|
296
|
-
: [{ command: '/session save', summary: 'name and persist the current conversation' }];
|
|
297
|
-
return buildPanelWorkspace(width, height, {
|
|
298
|
-
title: ` Sessions [${count}/${total}]`,
|
|
299
|
-
intro,
|
|
300
|
-
sections: [
|
|
301
|
-
{
|
|
302
|
-
lines: buildEmptyState(width, emptyTitle, emptyBody, emptyActions, DEFAULT_PANEL_PALETTE),
|
|
303
|
-
},
|
|
304
|
-
],
|
|
305
|
-
footerLines,
|
|
306
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
this.cursorIndex = Math.max(0, Math.min(this.cursorIndex, this.filtered.length - 1));
|
|
311
|
-
const summary: PanelWorkspaceSection = {
|
|
312
|
-
title: 'Summary',
|
|
313
|
-
lines: [
|
|
314
|
-
buildPanelLine(width, [
|
|
315
|
-
[' Sessions ', DEFAULT_PANEL_PALETTE.label],
|
|
316
|
-
[String(total), DEFAULT_PANEL_PALETTE.value],
|
|
317
|
-
[' Visible ', DEFAULT_PANEL_PALETTE.label],
|
|
318
|
-
[String(count), DEFAULT_PANEL_PALETTE.info],
|
|
319
|
-
[' Search ', DEFAULT_PANEL_PALETTE.label],
|
|
320
|
-
[this.searchQuery || 'none', this.searchQuery ? DEFAULT_PANEL_PALETTE.warn : DEFAULT_PANEL_PALETTE.dim],
|
|
321
|
-
]),
|
|
322
|
-
],
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
const selected = this.filtered[this.cursorIndex];
|
|
326
|
-
const selectedSection: PanelWorkspaceSection = selected
|
|
327
|
-
? {
|
|
328
|
-
title: 'Selected',
|
|
329
|
-
lines: [
|
|
330
|
-
buildPanelLine(width, [[' Title ', DEFAULT_PANEL_PALETTE.label], [selected.title || selected.name || '(untitled)', DEFAULT_PANEL_PALETTE.value]]),
|
|
331
|
-
buildPanelLine(width, [[' Model ', DEFAULT_PANEL_PALETTE.label], [selected.model || 'unknown', DEFAULT_PANEL_PALETTE.info], [' Title ', DEFAULT_PANEL_PALETTE.label], [selected.titleSource === 'user' ? 'user-set' : 'system', selected.titleSource === 'user' ? DEFAULT_PANEL_PALETTE.good : DEFAULT_PANEL_PALETTE.dim]]),
|
|
332
|
-
buildPanelLine(width, [[' Date ', DEFAULT_PANEL_PALETTE.label], [shortDate(selected.timestamp), DEFAULT_PANEL_PALETTE.value], [' Messages ', DEFAULT_PANEL_PALETTE.label], [String(selected.messageCount), DEFAULT_PANEL_PALETTE.value]]),
|
|
333
|
-
buildPanelLine(width, [
|
|
334
|
-
[' Tasks ', DEFAULT_PANEL_PALETTE.label],
|
|
335
|
-
[String(selected.returnContext?.activeTasks ?? 0), (selected.returnContext?.activeTasks ?? 0) > 0 ? DEFAULT_PANEL_PALETTE.info : DEFAULT_PANEL_PALETTE.dim],
|
|
336
|
-
[' Blocked ', DEFAULT_PANEL_PALETTE.label],
|
|
337
|
-
[String(selected.returnContext?.blockedTasks ?? 0), (selected.returnContext?.blockedTasks ?? 0) > 0 ? DEFAULT_PANEL_PALETTE.warn : DEFAULT_PANEL_PALETTE.dim],
|
|
338
|
-
[' Approvals ', DEFAULT_PANEL_PALETTE.label],
|
|
339
|
-
[String(selected.returnContext?.pendingApprovals ?? 0), (selected.returnContext?.pendingApprovals ?? 0) > 0 ? DEFAULT_PANEL_PALETTE.warn : DEFAULT_PANEL_PALETTE.dim],
|
|
340
|
-
]),
|
|
341
|
-
buildPanelLine(width, [
|
|
342
|
-
[' Remote ', DEFAULT_PANEL_PALETTE.label],
|
|
343
|
-
[String(selected.returnContext?.remoteRunners?.length ?? 0), (selected.returnContext?.remoteRunners?.length ?? 0) > 0 ? DEFAULT_PANEL_PALETTE.info : DEFAULT_PANEL_PALETTE.dim],
|
|
344
|
-
[' Worktrees ', DEFAULT_PANEL_PALETTE.label],
|
|
345
|
-
[String(selected.returnContext?.worktreePaths?.length ?? 0), (selected.returnContext?.worktreePaths?.length ?? 0) > 0 ? DEFAULT_PANEL_PALETTE.info : DEFAULT_PANEL_PALETTE.dim],
|
|
346
|
-
[' Panels ', DEFAULT_PANEL_PALETTE.label],
|
|
347
|
-
[String(selected.returnContext?.openPanels?.length ?? 0), (selected.returnContext?.openPanels?.length ?? 0) > 0 ? DEFAULT_PANEL_PALETTE.good : DEFAULT_PANEL_PALETTE.dim],
|
|
348
|
-
]),
|
|
349
|
-
...formatReturnContextLines(selected.returnContext).map((line) =>
|
|
350
|
-
buildPanelLine(width, [[' ', DEFAULT_PANEL_PALETTE.dim], [truncateDisplay(line, Math.max(0, width - 2)), DEFAULT_PANEL_PALETTE.dim]])
|
|
351
|
-
),
|
|
352
|
-
buildPanelLine(width, [[' Next ', DEFAULT_PANEL_PALETTE.label], [this._computeNextStepCommand(selected), DEFAULT_PANEL_PALETTE.dim], [' (x to run)', DEFAULT_PANEL_PALETTE.dim]]),
|
|
353
|
-
],
|
|
354
|
-
}
|
|
355
|
-
: { title: 'Selected', lines: [] };
|
|
356
|
-
|
|
357
|
-
const sessionsSection = resolveScrollablePanelSection(width, height, {
|
|
358
|
-
intro,
|
|
359
|
-
footerLines,
|
|
360
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
361
|
-
beforeSections: [summary],
|
|
362
|
-
section: {
|
|
363
|
-
title: 'Sessions',
|
|
364
|
-
scrollableLines: this.filtered.map((sess, index) =>
|
|
365
|
-
this._renderSession(width, sess, index === this.cursorIndex),
|
|
366
|
-
),
|
|
367
|
-
selectedIndex: this.cursorIndex,
|
|
368
|
-
scrollOffset: this.scrollOffset,
|
|
369
|
-
minRows: 6,
|
|
370
|
-
},
|
|
371
|
-
afterSections: [selectedSection],
|
|
372
|
-
});
|
|
373
|
-
this.scrollOffset = sessionsSection.scrollOffset;
|
|
374
|
-
|
|
375
|
-
return buildPanelWorkspace(width, height, {
|
|
376
|
-
title: ` Sessions [${count}/${total}]`,
|
|
377
|
-
intro,
|
|
378
|
-
sections: [
|
|
379
|
-
summary,
|
|
380
|
-
sessionsSection.section,
|
|
381
|
-
selectedSection,
|
|
382
|
-
],
|
|
383
|
-
footerLines,
|
|
384
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
private _renderSession(width: number, sess: SessionInfo, isCursor: boolean): Line {
|
|
389
|
-
const bg = isCursor ? C.selectBg : '';
|
|
390
|
-
const date = shortDate(sess.timestamp);
|
|
391
|
-
const cnt = String(sess.messageCount).padStart(3) + 'm ';
|
|
392
|
-
const model = fitDisplay(sess.model || 'unknown', 18) + ' ';
|
|
393
|
-
const prefixLength = 1 + 16 + 1 + 4 + 19;
|
|
394
|
-
const title = truncateDisplay(sess.title || sess.name || '(untitled)', Math.max(0, width - prefixLength));
|
|
395
|
-
return buildStyledPanelLine(width, [
|
|
396
|
-
{ text: isCursor ? '▸' : ' ', fg: C.selected, bg, bold: isCursor },
|
|
397
|
-
{ text: date, fg: C.dateFg, bg },
|
|
398
|
-
{ text: ' ', fg: C.value, bg },
|
|
399
|
-
{ text: cnt, fg: C.countFg, bg },
|
|
400
|
-
{ text: model, fg: C.modelFg, bg },
|
|
401
|
-
{ text: title, fg: isCursor ? C.selected : C.value, bg, bold: isCursor },
|
|
402
|
-
]);
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
private _load(): void {
|
|
406
|
-
try {
|
|
407
|
-
this.sessions = this.sessionManager.list();
|
|
408
|
-
this._filter();
|
|
409
|
-
this.loadError = '';
|
|
410
|
-
this.hasLoaded = true;
|
|
411
|
-
this.markDirty();
|
|
412
|
-
} catch (e) {
|
|
413
|
-
logger.debug('SessionBrowserPanel._load failed', { error: summarizeError(e) });
|
|
414
|
-
this.loadError = 'Failed to load sessions';
|
|
415
|
-
this.hasLoaded = true;
|
|
416
|
-
this.markDirty();
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
private _filter(): void {
|
|
421
|
-
if (!this.searchQuery.trim()) {
|
|
422
|
-
this.filtered = [...this.sessions];
|
|
423
|
-
} else {
|
|
424
|
-
const q = this.searchQuery.toLowerCase();
|
|
425
|
-
try {
|
|
426
|
-
const results = this.sessionManager.search(q);
|
|
427
|
-
const names = new Set(results.map(r => r.session.name));
|
|
428
|
-
this.filtered = this.sessions.filter(s => names.has(s.name));
|
|
429
|
-
} catch (e) {
|
|
430
|
-
logger.debug('SessionBrowserPanel._filter search failed, falling back', { error: summarizeError(e) });
|
|
431
|
-
this.filtered = this.sessions.filter(s =>
|
|
432
|
-
(s.title || '').toLowerCase().includes(q) ||
|
|
433
|
-
(s.model || '').toLowerCase().includes(q) ||
|
|
434
|
-
s.name.toLowerCase().includes(q)
|
|
435
|
-
);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
this.cursorIndex = Math.min(this.cursorIndex, Math.max(0, this.filtered.length - 1));
|
|
439
|
-
this.markDirty();
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
private _startSearch(): void {
|
|
443
|
-
this.searching = true;
|
|
444
|
-
this.markDirty();
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
private _move(delta: number): void {
|
|
448
|
-
if (this.filtered.length === 0) return;
|
|
449
|
-
this.cursorIndex = Math.max(0, Math.min(this.filtered.length - 1, this.cursorIndex + delta));
|
|
450
|
-
this.markDirty();
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
private _resume(): void {
|
|
454
|
-
const sess = this.filtered[this.cursorIndex];
|
|
455
|
-
if (!sess) return;
|
|
456
|
-
if (sess.returnContext?.openPanels?.length) {
|
|
457
|
-
this.pendingOpenPanels = [...sess.returnContext.openPanels];
|
|
458
|
-
}
|
|
459
|
-
this.resumeSession?.(sess.name);
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
/** The next-step command shown in the 'Selected' section and dispatched by 'x'. */
|
|
463
|
-
private _computeNextStepCommand(sess: SessionInfo): string {
|
|
464
|
-
const runner = sess.returnContext?.remoteRunners?.[0];
|
|
465
|
-
return runner ? `/remote recover ${runner}` : `/session resume ${sess.name}`;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
private _dispatchNextStep(): void {
|
|
469
|
-
const sess = this.filtered[this.cursorIndex];
|
|
470
|
-
if (!sess) return;
|
|
471
|
-
const parsed = parseCommand(this._computeNextStepCommand(sess));
|
|
472
|
-
if (parsed) this.pendingCommand = parsed;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
private _promptDelete(): void {
|
|
476
|
-
const sess = this.filtered[this.cursorIndex];
|
|
477
|
-
if (!sess) return;
|
|
478
|
-
this.confirm = { subject: sess.name, label: sess.name };
|
|
479
|
-
this.markDirty();
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
private _deleteConfirmed(): void {
|
|
483
|
-
if (!this.confirm) return;
|
|
484
|
-
const name = this.confirm.subject;
|
|
485
|
-
this.confirm = null;
|
|
486
|
-
try {
|
|
487
|
-
this.sessionManager.delete(name);
|
|
488
|
-
this.deleteError = '';
|
|
489
|
-
this._load();
|
|
490
|
-
} catch (e) {
|
|
491
|
-
logger.debug('SessionBrowserPanel._deleteConfirmed failed', { error: summarizeError(e) });
|
|
492
|
-
this.deleteError = `Delete failed: ${name}`;
|
|
493
|
-
}
|
|
494
|
-
this.markDirty();
|
|
495
|
-
}
|
|
496
|
-
}
|