@pellux/goodvibes-tui 1.1.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 +85 -21
- package/README.md +20 -11
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +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/conversation-rendering.ts +19 -0
- 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/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 +36 -0
- package/src/input/command-registry.ts +38 -1
- package/src/input/commands/checkpoint-runtime.ts +280 -0
- package/src/input/commands/codebase-runtime.ts +192 -0
- 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-workflow.ts +16 -1
- package/src/input/commands/settings-sync-runtime.ts +1 -1
- 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-feed-routes.ts +61 -23
- package/src/input/handler-feed.ts +47 -23
- package/src/input/handler-interactions.ts +1 -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 +2 -6
- package/src/input/handler-ui-state.ts +10 -22
- package/src/input/handler.ts +6 -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 +24 -27
- 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 +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 +36 -10
- package/src/panels/diff-panel.ts +12 -43
- 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/index.ts +7 -31
- 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/skills-panel.ts +7 -51
- package/src/panels/types.ts +13 -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/turn-injection.ts +114 -0
- package/src/runtime/bootstrap-command-context.ts +21 -1
- package/src/runtime/bootstrap-command-parts.ts +34 -7
- package/src/runtime/bootstrap-core.ts +14 -4
- package/src/runtime/bootstrap-shell.ts +29 -16
- 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 -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,360 +0,0 @@
|
|
|
1
|
-
import type { Line } from '../types/grid.ts';
|
|
2
|
-
import { createEmptyLine } from '../types/grid.ts';
|
|
3
|
-
import { basename } from 'node:path';
|
|
4
|
-
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
5
|
-
import { buildAlignedRow, buildKeyValueLine, buildKeyboardHints, buildPanelLine, buildPanelWorkspace, DEFAULT_PANEL_PALETTE, resolvePrimaryScrollableSection, type ColumnSpec, type PanelWorkspaceSection } from './polish.ts';
|
|
6
|
-
import { type ConfirmState, handleConfirmInput, renderConfirmLines } from './confirm-state.ts';
|
|
7
|
-
import { summarizeWorktreeOwnership, type WorktreeStatusRecord } from '@/runtime/index.ts';
|
|
8
|
-
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
9
|
-
import type { PanelIntegrationContext } from './types.ts';
|
|
10
|
-
|
|
11
|
-
// Base chrome only — state colors and text tokens come straight from
|
|
12
|
-
// DEFAULT_PANEL_PALETTE (WO-002).
|
|
13
|
-
const C = DEFAULT_PANEL_PALETTE;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Structural shape the panel actually needs from a worktree registry. The
|
|
17
|
-
* real `WorktreeRegistry` (from the SDK) satisfies this directly; it has no
|
|
18
|
-
* `subscribe()` method today, so the panel falls back to polling when one
|
|
19
|
-
* isn't provided (see constructor). Kept narrow and `Like`-suffixed per the
|
|
20
|
-
* project's duck-typing convention (see `agent-inspector-shared.ts`).
|
|
21
|
-
*/
|
|
22
|
-
export interface WorktreeRegistryLike {
|
|
23
|
-
list(): Promise<WorktreeStatusRecord[]>;
|
|
24
|
-
attach(path: string, target: { sessionId?: string; taskId?: string }): void;
|
|
25
|
-
setState(path: string, state: WorktreeStatusRecord['state']): void;
|
|
26
|
-
cleanup(path: string): Promise<void>;
|
|
27
|
-
/** Optional live-update hook; when present the panel prefers it over polling. */
|
|
28
|
-
subscribe?(listener: () => void): () => void;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** What a pending destructive confirm will do once the user confirms. */
|
|
32
|
-
interface WorktreeConfirmSubject {
|
|
33
|
-
readonly path: string;
|
|
34
|
-
readonly action: 'discard' | 'cleanup';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** Poll cadence used only when the registry has no `subscribe()` method. */
|
|
38
|
-
const POLL_INTERVAL_MS = 5_000;
|
|
39
|
-
|
|
40
|
-
function stateColor(state: WorktreeStatusRecord['state']): string {
|
|
41
|
-
switch (state) {
|
|
42
|
-
case 'active': return C.good;
|
|
43
|
-
case 'paused':
|
|
44
|
-
case 'kept': return C.warn;
|
|
45
|
-
default: return C.dim;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** Glyph that reads at a glance: ● active, ◌ paused/kept, ⊘ discard/cleanup. */
|
|
50
|
-
function stateGlyph(state: WorktreeStatusRecord['state']): string {
|
|
51
|
-
switch (state) {
|
|
52
|
-
case 'active': return '●';
|
|
53
|
-
case 'paused':
|
|
54
|
-
case 'kept': return '◌';
|
|
55
|
-
default: return '⊘';
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* One worktree row, aligned with display-width-aware columns so a wide-char or
|
|
61
|
-
* long branch name never shoves the path column out of alignment. The active
|
|
62
|
-
* worktree is flagged with a ● glyph and selection uses the shared marker.
|
|
63
|
-
*/
|
|
64
|
-
function buildWorktreeRow(width: number, row: WorktreeStatusRecord, selected: boolean): Line {
|
|
65
|
-
const pathW = Math.max(8, width - (2 + 10 + 13 + 20) - 4 - 2);
|
|
66
|
-
const columns: ColumnSpec[] = [
|
|
67
|
-
{ width: 2 },
|
|
68
|
-
{ width: 10 },
|
|
69
|
-
{ width: 13 },
|
|
70
|
-
{ width: 20 },
|
|
71
|
-
{ width: pathW },
|
|
72
|
-
];
|
|
73
|
-
return buildAlignedRow(
|
|
74
|
-
width,
|
|
75
|
-
[
|
|
76
|
-
{ text: stateGlyph(row.state), fg: stateColor(row.state) },
|
|
77
|
-
{ text: row.kind, fg: C.info },
|
|
78
|
-
{ text: row.state, fg: stateColor(row.state) },
|
|
79
|
-
{ text: row.branch, fg: C.value },
|
|
80
|
-
{ text: basename(row.path) || row.path, fg: C.dim },
|
|
81
|
-
],
|
|
82
|
-
columns,
|
|
83
|
-
{ selected, selectedBg: C.headerBg, marker: '▸' },
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const WORKTREE_HINTS = [
|
|
88
|
-
{ keys: '↑/↓', label: 'select' },
|
|
89
|
-
{ keys: 'p/u/k', label: 'pause/resume/keep' },
|
|
90
|
-
{ keys: 'd/c', label: 'discard/cleanup' },
|
|
91
|
-
{ keys: 'enter', label: 'jump to session/task' },
|
|
92
|
-
{ keys: 'r', label: 'refresh' },
|
|
93
|
-
] as const;
|
|
94
|
-
|
|
95
|
-
export class WorktreePanel extends ScrollableListPanel<WorktreeStatusRecord> {
|
|
96
|
-
private rows: WorktreeStatusRecord[] = [];
|
|
97
|
-
private loading = false;
|
|
98
|
-
private confirm: ConfirmState<WorktreeConfirmSubject> | null = null;
|
|
99
|
-
private pendingJump: { targetPanel: 'sessions' | 'tasks' } | null = null;
|
|
100
|
-
private readonly worktreeRegistry: WorktreeRegistryLike;
|
|
101
|
-
private readonly requestRender: () => void;
|
|
102
|
-
private unsubscribe: (() => void) | null = null;
|
|
103
|
-
|
|
104
|
-
public constructor(worktreeRegistry: WorktreeRegistryLike, requestRender: () => void = () => {}) {
|
|
105
|
-
super('worktrees', 'Worktrees', '▯', 'automation-control');
|
|
106
|
-
this.showSelectionGutter = true; // I5: non-color selection affordance
|
|
107
|
-
this.worktreeRegistry = worktreeRegistry;
|
|
108
|
-
this.requestRender = requestRender;
|
|
109
|
-
void this.refresh();
|
|
110
|
-
// Live state: prefer a registry subscription when the caller provides one
|
|
111
|
-
// (matches the mock shape used by contract tests); otherwise poll, so the
|
|
112
|
-
// real SDK WorktreeRegistry (which has no subscribe() today) still stays live.
|
|
113
|
-
if (typeof this.worktreeRegistry.subscribe === 'function') {
|
|
114
|
-
this.unsubscribe = this.worktreeRegistry.subscribe(() => { void this.refresh(); });
|
|
115
|
-
} else {
|
|
116
|
-
this.registerTimer(setInterval(() => { void this.refresh(); }, POLL_INTERVAL_MS));
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
public override onActivate(): void {
|
|
121
|
-
super.onActivate();
|
|
122
|
-
if (!this.loading) void this.refresh();
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
public override onDestroy(): void {
|
|
126
|
-
super.onDestroy();
|
|
127
|
-
if (this.unsubscribe) {
|
|
128
|
-
this.unsubscribe();
|
|
129
|
-
this.unsubscribe = null;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
public override handleInput(key: string): boolean {
|
|
134
|
-
if (this.lastError !== null) this.clearError();
|
|
135
|
-
|
|
136
|
-
const confirmResult = handleConfirmInput(this.confirm, key);
|
|
137
|
-
if (confirmResult === 'confirmed') {
|
|
138
|
-
const subject = this.confirm!.subject;
|
|
139
|
-
this.confirm = null;
|
|
140
|
-
if (subject.action === 'discard') {
|
|
141
|
-
this.worktreeRegistry.setState(subject.path, 'discard');
|
|
142
|
-
void this.refresh();
|
|
143
|
-
} else {
|
|
144
|
-
void this._cleanup(subject.path);
|
|
145
|
-
}
|
|
146
|
-
this.markDirty();
|
|
147
|
-
return true;
|
|
148
|
-
}
|
|
149
|
-
if (confirmResult === 'cancelled') {
|
|
150
|
-
this.confirm = null;
|
|
151
|
-
this.markDirty();
|
|
152
|
-
return true;
|
|
153
|
-
}
|
|
154
|
-
if (confirmResult === 'absorbed') return true;
|
|
155
|
-
|
|
156
|
-
if (key === 'r') {
|
|
157
|
-
void this.refresh();
|
|
158
|
-
return true;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const selected = this.getSelectedItem();
|
|
162
|
-
|
|
163
|
-
if (key === 'p' && selected && selected.state !== 'paused') {
|
|
164
|
-
this.worktreeRegistry.setState(selected.path, 'paused');
|
|
165
|
-
void this.refresh();
|
|
166
|
-
return true;
|
|
167
|
-
}
|
|
168
|
-
if (key === 'u' && selected && selected.state !== 'active') {
|
|
169
|
-
this.worktreeRegistry.setState(selected.path, 'active');
|
|
170
|
-
void this.refresh();
|
|
171
|
-
return true;
|
|
172
|
-
}
|
|
173
|
-
if (key === 'k' && selected && selected.state !== 'kept') {
|
|
174
|
-
this.worktreeRegistry.setState(selected.path, 'kept');
|
|
175
|
-
void this.refresh();
|
|
176
|
-
return true;
|
|
177
|
-
}
|
|
178
|
-
if (key === 'd' && selected && selected.state !== 'discard') {
|
|
179
|
-
this.confirm = {
|
|
180
|
-
subject: { path: selected.path, action: 'discard' },
|
|
181
|
-
label: `worktree ${basename(selected.path)} (${selected.branch})`,
|
|
182
|
-
verb: 'Discard',
|
|
183
|
-
};
|
|
184
|
-
this.markDirty();
|
|
185
|
-
return true;
|
|
186
|
-
}
|
|
187
|
-
if (key === 'c' && selected) {
|
|
188
|
-
this.confirm = {
|
|
189
|
-
subject: { path: selected.path, action: 'cleanup' },
|
|
190
|
-
label: `worktree ${basename(selected.path)} (${selected.branch})`,
|
|
191
|
-
verb: 'Clean up',
|
|
192
|
-
};
|
|
193
|
-
this.markDirty();
|
|
194
|
-
return true;
|
|
195
|
-
}
|
|
196
|
-
if ((key === 'enter' || key === 'return') && selected && (selected.sessionId || selected.taskId)) {
|
|
197
|
-
this.pendingJump = { targetPanel: selected.sessionId ? 'sessions' : 'tasks' };
|
|
198
|
-
return true;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return super.handleInput(key);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Enter on an attached worktree row jumps to the owning session/task panel.
|
|
206
|
-
* The actual `PanelManager.open()` call requires the integration context,
|
|
207
|
-
* which is only available here (invoked right after `handleInput` returns
|
|
208
|
-
* `true` for the same key) — same staged-pending-action pattern as
|
|
209
|
-
* `incident-review-panel.ts`.
|
|
210
|
-
*/
|
|
211
|
-
public handlePanelIntegrationAction(_key: string, ctx: PanelIntegrationContext): boolean {
|
|
212
|
-
if (this.pendingJump) {
|
|
213
|
-
const jump = this.pendingJump;
|
|
214
|
-
this.pendingJump = null;
|
|
215
|
-
ctx.panelManager.open(jump.targetPanel);
|
|
216
|
-
return true;
|
|
217
|
-
}
|
|
218
|
-
return false;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
protected getItems(): readonly WorktreeStatusRecord[] {
|
|
222
|
-
return this.rows;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
protected renderItem(row: WorktreeStatusRecord, index: number, _selected: boolean, width: number): Line {
|
|
226
|
-
return buildWorktreeRow(width, row, index === this.selectedIndex);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
private async refresh(): Promise<void> {
|
|
230
|
-
this.loading = true;
|
|
231
|
-
this.markDirty();
|
|
232
|
-
try {
|
|
233
|
-
this.rows = await this.worktreeRegistry.list();
|
|
234
|
-
this.clampSelection();
|
|
235
|
-
} finally {
|
|
236
|
-
this.loading = false;
|
|
237
|
-
this.markDirty();
|
|
238
|
-
this.requestRender();
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
private async _cleanup(path: string): Promise<void> {
|
|
243
|
-
try {
|
|
244
|
-
await this.worktreeRegistry.cleanup(path);
|
|
245
|
-
} catch (err) {
|
|
246
|
-
this.setError(summarizeError(err));
|
|
247
|
-
} finally {
|
|
248
|
-
await this.refresh();
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
public render(width: number, height: number): Line[] {
|
|
253
|
-
this.needsRender = false;
|
|
254
|
-
|
|
255
|
-
if (this.confirm) {
|
|
256
|
-
const lines = buildPanelWorkspace(width, height, {
|
|
257
|
-
title: 'Worktree Control Room',
|
|
258
|
-
sections: [{ title: 'Confirmation', lines: renderConfirmLines(width, this.confirm) }],
|
|
259
|
-
palette: C,
|
|
260
|
-
});
|
|
261
|
-
while (lines.length < height) lines.push(createEmptyLine(width));
|
|
262
|
-
return lines.slice(0, height);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const sections: PanelWorkspaceSection[] = [];
|
|
266
|
-
|
|
267
|
-
if (this.loading && this.rows.length === 0) {
|
|
268
|
-
sections.push({ title: 'Worktrees', lines: [buildPanelLine(width, [[' Loading worktree state...', C.info]])] });
|
|
269
|
-
} else if (this.rows.length === 0) {
|
|
270
|
-
sections.push({
|
|
271
|
-
title: 'Worktrees',
|
|
272
|
-
lines: [
|
|
273
|
-
buildPanelLine(width, [[' No git worktrees discovered for this project yet.', C.dim]]),
|
|
274
|
-
buildPanelLine(width, [[' /worktree attach <path>', C.info], [' register a worktree for orchestrator-managed lifecycle', C.dim]]),
|
|
275
|
-
],
|
|
276
|
-
});
|
|
277
|
-
} else {
|
|
278
|
-
const summary = summarizeWorktreeOwnership(this.rows);
|
|
279
|
-
sections.push({
|
|
280
|
-
title: 'Worktree posture',
|
|
281
|
-
lines: [
|
|
282
|
-
buildKeyValueLine(width, [
|
|
283
|
-
{ label: 'total', value: String(summary.total), valueColor: C.value },
|
|
284
|
-
{ label: 'active', value: String(summary.active), valueColor: C.good },
|
|
285
|
-
{ label: 'paused', value: String(summary.paused), valueColor: summary.paused > 0 ? C.warn : C.dim },
|
|
286
|
-
{ label: 'cleanup', value: String(summary.pendingCleanup), valueColor: summary.pendingCleanup > 0 ? C.warn : C.dim },
|
|
287
|
-
], C),
|
|
288
|
-
buildKeyValueLine(width, [
|
|
289
|
-
{ label: 'session attached', value: String(summary.sessionAttached), valueColor: summary.sessionAttached > 0 ? C.info : C.dim },
|
|
290
|
-
{ label: 'task attached', value: String(summary.taskAttached), valueColor: summary.taskAttached > 0 ? C.info : C.dim },
|
|
291
|
-
{ label: 'agent owned', value: String(summary.agentOwned), valueColor: summary.agentOwned > 0 ? C.value : C.dim },
|
|
292
|
-
{ label: 'orchestrator', value: String(summary.orchestratorOwned), valueColor: summary.orchestratorOwned > 0 ? C.value : C.dim },
|
|
293
|
-
], C),
|
|
294
|
-
],
|
|
295
|
-
});
|
|
296
|
-
const selected = this.getSelectedItem()!;
|
|
297
|
-
const detailSection: PanelWorkspaceSection = {
|
|
298
|
-
title: 'Details',
|
|
299
|
-
lines: [
|
|
300
|
-
buildPanelLine(width, [[' path ', C.label], [selected.path, C.dim]]),
|
|
301
|
-
buildPanelLine(width, [[' branch ', C.label], [selected.branch, C.value], [' head ', C.label], [selected.head.slice(0, 12), C.info], [' state ', C.label], [selected.state, stateColor(selected.state)]]),
|
|
302
|
-
buildPanelLine(width, [[' kind ', C.label], [selected.kind, C.info], [' owner ', C.label], [selected.ownerId ?? 'n/a', C.dim], [' session ', C.label], [selected.sessionId ?? 'n/a', C.dim]]),
|
|
303
|
-
buildPanelLine(width, [[' task ', C.label], [selected.taskId ?? 'n/a', C.dim], [' updated ', C.label], [new Date(selected.updatedAt).toLocaleString(), C.dim]]),
|
|
304
|
-
buildPanelLine(width, [[
|
|
305
|
-
selected.sessionId || selected.taskId
|
|
306
|
-
? ' Attached worktree can be resumed from session/task flows and should be merged or cleaned up by the orchestrator.'
|
|
307
|
-
: ' Unattached worktree detected. Review whether it should be attached, kept, discarded, or cleaned up.',
|
|
308
|
-
selected.sessionId || selected.taskId ? C.info : C.warn,
|
|
309
|
-
]]),
|
|
310
|
-
],
|
|
311
|
-
};
|
|
312
|
-
const resolvedWorktreesSection = resolvePrimaryScrollableSection(width, height, {
|
|
313
|
-
intro: 'Orchestrator-owned worktree lifecycle, attachments, pause/resume posture, and cleanup state.',
|
|
314
|
-
footerLines: [buildKeyboardHints(width, WORKTREE_HINTS, C)],
|
|
315
|
-
palette: C,
|
|
316
|
-
beforeSections: sections,
|
|
317
|
-
section: {
|
|
318
|
-
title: 'Worktrees',
|
|
319
|
-
fixedLines: [
|
|
320
|
-
buildAlignedRow(width, [
|
|
321
|
-
{ text: '', fg: C.dim },
|
|
322
|
-
{ text: 'KIND', fg: C.label, bold: true },
|
|
323
|
-
{ text: 'STATE', fg: C.label, bold: true },
|
|
324
|
-
{ text: 'BRANCH', fg: C.label, bold: true },
|
|
325
|
-
{ text: 'WORKTREE', fg: C.label, bold: true },
|
|
326
|
-
], [
|
|
327
|
-
{ width: 2 }, { width: 10 }, { width: 13 }, { width: 20 },
|
|
328
|
-
{ width: Math.max(8, width - (2 + 10 + 13 + 20) - 4 - 2) },
|
|
329
|
-
], { marker: ' ' }),
|
|
330
|
-
],
|
|
331
|
-
scrollableLines: this.rows.map((row, absolute) =>
|
|
332
|
-
buildWorktreeRow(width, row, absolute === this.selectedIndex),
|
|
333
|
-
),
|
|
334
|
-
selectedIndex: this.selectedIndex,
|
|
335
|
-
scrollOffset: this.scrollStart,
|
|
336
|
-
guardRows: 1,
|
|
337
|
-
minRows: 4,
|
|
338
|
-
appendWindowSummary: { dimColor: C.dim },
|
|
339
|
-
},
|
|
340
|
-
afterSections: [detailSection],
|
|
341
|
-
});
|
|
342
|
-
this.scrollStart = resolvedWorktreesSection.scrollOffset;
|
|
343
|
-
sections.push(resolvedWorktreesSection.section);
|
|
344
|
-
sections.push(detailSection);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
const errorLine = this.renderErrorLine(width);
|
|
348
|
-
if (errorLine) sections.push({ title: 'Error', lines: [errorLine] });
|
|
349
|
-
|
|
350
|
-
const lines = buildPanelWorkspace(width, height, {
|
|
351
|
-
title: 'Worktree Control Room',
|
|
352
|
-
intro: 'Orchestrator-owned worktree lifecycle, attachments, pause/resume posture, and cleanup state.',
|
|
353
|
-
sections,
|
|
354
|
-
footerLines: [buildKeyboardHints(width, WORKTREE_HINTS, C)],
|
|
355
|
-
palette: C,
|
|
356
|
-
});
|
|
357
|
-
while (lines.length < height) lines.push(createEmptyLine(width));
|
|
358
|
-
return lines.slice(0, height);
|
|
359
|
-
}
|
|
360
|
-
}
|