@pellux/goodvibes-tui 0.28.0 → 1.0.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 +133 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/core/conversation-line-cache.ts +432 -0
- package/src/core/conversation-rendering.ts +11 -3
- package/src/core/conversation.ts +50 -4
- package/src/input/commands/cost-runtime.ts +49 -0
- package/src/input/commands/operator-runtime.ts +5 -1
- package/src/input/commands.ts +2 -0
- package/src/input/handler-content-actions.ts +8 -3
- package/src/input/handler-feed-routes.ts +12 -35
- package/src/input/handler-feed.ts +4 -8
- package/src/input/handler-shortcuts.ts +51 -0
- package/src/input/handler.ts +43 -6
- package/src/input/input-history.ts +17 -4
- package/src/input/keybindings.ts +48 -8
- package/src/input/panel-integration-actions.ts +107 -1
- package/src/main.ts +19 -11
- package/src/panels/agent-inspector-panel.ts +125 -36
- package/src/panels/agent-inspector-shared.ts +144 -0
- package/src/panels/approval-panel.ts +67 -16
- package/src/panels/automation-control-panel.ts +368 -124
- package/src/panels/base-panel.ts +1 -1
- package/src/panels/builtin/agent.ts +28 -37
- package/src/panels/builtin/development.ts +40 -32
- package/src/panels/builtin/knowledge.ts +15 -6
- package/src/panels/builtin/operations.ts +178 -117
- package/src/panels/builtin/session.ts +35 -9
- package/src/panels/builtin/shared.ts +98 -6
- package/src/panels/cockpit-panel.ts +232 -73
- package/src/panels/communication-panel.ts +58 -20
- package/src/panels/confirm-state.ts +8 -1
- package/src/panels/control-plane-panel.ts +351 -118
- package/src/panels/cost-tracker-panel.ts +165 -7
- package/src/panels/debug-panel.ts +312 -159
- package/src/panels/diff-panel.ts +209 -57
- package/src/panels/docs-panel.ts +152 -66
- package/src/panels/eval-panel.ts +220 -42
- package/src/panels/file-explorer-panel.ts +202 -122
- package/src/panels/file-preview-panel.ts +132 -57
- package/src/panels/git-panel.ts +310 -129
- package/src/panels/hooks-panel.ts +150 -22
- package/src/panels/incident-review-panel.ts +223 -35
- package/src/panels/index.ts +0 -4
- package/src/panels/intelligence-panel.ts +212 -86
- package/src/panels/knowledge-graph-panel.ts +461 -101
- package/src/panels/local-auth-panel.ts +240 -28
- package/src/panels/marketplace-panel.ts +193 -26
- package/src/panels/memory-panel.ts +78 -77
- package/src/panels/ops-control-panel.ts +146 -29
- package/src/panels/ops-strategy-panel.ts +72 -4
- package/src/panels/orchestration-panel.ts +231 -69
- package/src/panels/panel-list-panel.ts +154 -131
- package/src/panels/panel-manager.ts +97 -9
- package/src/panels/plan-dashboard-panel.ts +333 -17
- package/src/panels/plugins-panel.ts +184 -29
- package/src/panels/policy-panel.ts +210 -38
- package/src/panels/polish-core.ts +7 -2
- package/src/panels/polish.ts +23 -4
- package/src/panels/project-planning-answer-actions.ts +134 -0
- package/src/panels/project-planning-panel.ts +62 -113
- package/src/panels/provider-health-panel.ts +434 -518
- package/src/panels/provider-health-routes.ts +203 -0
- package/src/panels/provider-health-tracker.ts +194 -6
- package/src/panels/provider-health-views.ts +560 -0
- package/src/panels/qr-panel.ts +116 -30
- package/src/panels/remote-panel.ts +114 -36
- package/src/panels/routes-panel.ts +63 -22
- package/src/panels/sandbox-panel.ts +174 -42
- package/src/panels/scrollable-list-panel.ts +19 -135
- package/src/panels/security-panel.ts +133 -33
- package/src/panels/services-panel.ts +116 -64
- package/src/panels/session-browser-panel.ts +73 -2
- package/src/panels/session-maintenance.ts +4 -122
- package/src/panels/settings-sync-panel.ts +335 -72
- package/src/panels/skills-panel.ts +157 -89
- package/src/panels/subscription-panel.ts +86 -33
- package/src/panels/symbol-outline-panel.ts +248 -108
- package/src/panels/system-messages-panel.ts +114 -13
- package/src/panels/tasks-panel.ts +326 -139
- package/src/panels/thinking-panel.ts +43 -10
- package/src/panels/token-budget-panel.ts +194 -18
- package/src/panels/tool-inspector-panel.ts +144 -34
- package/src/panels/types.ts +40 -4
- package/src/panels/work-plan-panel.ts +280 -17
- package/src/panels/worktree-panel.ts +175 -42
- package/src/panels/wrfc-panel.ts +116 -24
- package/src/renderer/agent-detail-modal.ts +37 -8
- package/src/renderer/code-block.ts +58 -28
- package/src/renderer/context-inspector.ts +3 -6
- package/src/renderer/conversation-overlays.ts +25 -11
- package/src/renderer/conversation-surface.ts +1 -21
- package/src/renderer/diff-view.ts +6 -4
- package/src/renderer/footer-tips.ts +41 -0
- package/src/renderer/fullscreen-primitives.ts +23 -17
- package/src/renderer/fullscreen-workspace.ts +2 -7
- package/src/renderer/help-overlay.ts +91 -14
- package/src/renderer/hint-grammar.ts +52 -0
- package/src/renderer/history-search-overlay.ts +10 -16
- package/src/renderer/layout.ts +7 -7
- package/src/renderer/markdown.ts +9 -1
- package/src/renderer/modal-factory.ts +23 -15
- package/src/renderer/model-picker-overlay.ts +8 -485
- package/src/renderer/model-workspace.ts +9 -24
- package/src/renderer/overlay-box.ts +23 -19
- package/src/renderer/process-indicator.ts +15 -25
- package/src/renderer/profile-picker-modal.ts +13 -14
- package/src/renderer/prompt-content-width.ts +16 -0
- package/src/renderer/search-overlay.ts +27 -6
- package/src/renderer/selection-modal-overlay.ts +3 -1
- package/src/renderer/semantic-diff.ts +1 -1
- package/src/renderer/session-picker-modal.ts +6 -4
- package/src/renderer/settings-modal-helpers.ts +10 -34
- package/src/renderer/settings-modal.ts +70 -10
- package/src/renderer/shell-surface.ts +54 -15
- package/src/renderer/status-glyphs.ts +11 -9
- package/src/renderer/surface-layout.ts +0 -12
- package/src/renderer/term-caps.ts +1 -1
- package/src/renderer/theme.ts +60 -3
- package/src/renderer/tool-call.ts +6 -20
- package/src/renderer/ui-factory.ts +48 -37
- package/src/renderer/ui-primitives.ts +41 -3
- package/src/runtime/bootstrap-shell.ts +35 -18
- package/src/runtime/diagnostics/panels/index.ts +0 -3
- package/src/runtime/render-scheduler.ts +80 -0
- package/src/shell/ui-openers.ts +14 -22
- package/src/utils/format-duration.ts +2 -2
- package/src/utils/splash-lines.ts +54 -5
- package/src/version.ts +1 -1
- package/src/work-plans/work-plan-store.ts +13 -0
- package/src/panels/agent-logs-panel.ts +0 -635
- package/src/panels/agent-logs-shared.ts +0 -129
- package/src/panels/context-visualizer-panel.ts +0 -238
- package/src/panels/forensics-panel.ts +0 -378
- package/src/panels/provider-account-snapshot.ts +0 -259
- package/src/panels/provider-accounts-panel.ts +0 -255
- package/src/panels/provider-stats-panel.ts +0 -391
- package/src/panels/schedule-panel.ts +0 -365
- package/src/panels/watchers-panel.ts +0 -213
- package/src/renderer/file-tree.ts +0 -153
- package/src/renderer/progress.ts +0 -99
- package/src/renderer/status-token.ts +0 -67
- package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
- package/src/runtime/diagnostics/panels/policy.ts +0 -177
|
@@ -2,11 +2,12 @@ import type { Line } from '../types/grid.ts';
|
|
|
2
2
|
import { createEmptyLine } from '../types/grid.ts';
|
|
3
3
|
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
4
4
|
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
5
|
-
import type { RuntimeTask, TaskLifecycleState } from '@/runtime/index.ts';
|
|
5
|
+
import type { OpsApi, RuntimeTask, TaskLifecycleState } from '@/runtime/index.ts';
|
|
6
6
|
import type { ManagedWorktreeMeta } from '@/runtime/index.ts';
|
|
7
7
|
import type { UiReadModel, UiTasksSnapshot, UiWorktreeSnapshot } from '../runtime/ui-read-models.ts';
|
|
8
|
+
import { type ConfirmState, handleConfirmInput, renderConfirmLines } from './confirm-state.ts';
|
|
9
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
8
10
|
import {
|
|
9
|
-
buildDetailBlock,
|
|
10
11
|
buildEmptyState,
|
|
11
12
|
buildGuidanceLine,
|
|
12
13
|
buildKeyboardHints,
|
|
@@ -14,24 +15,26 @@ import {
|
|
|
14
15
|
buildPanelLine,
|
|
15
16
|
buildSummaryBlock,
|
|
16
17
|
buildPanelWorkspace,
|
|
18
|
+
resolveScrollablePanelSection,
|
|
17
19
|
DEFAULT_PANEL_PALETTE,
|
|
20
|
+
extendPalette,
|
|
18
21
|
} from './polish.ts';
|
|
19
22
|
import { formatElapsed } from '../utils/format-elapsed.ts';
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
running: '#22c55e',
|
|
27
|
-
blocked: '#f59e0b',
|
|
28
|
-
failed: '#ef4444',
|
|
29
|
-
completed: '#a78bfa',
|
|
30
|
-
selectBg: '#0f172a',
|
|
31
|
-
} as const;
|
|
24
|
+
// Domain accents only; base chrome (header/headerBg/info/good/warn/bad/
|
|
25
|
+
// selectBg) comes from DEFAULT_PANEL_PALETTE.
|
|
26
|
+
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
27
|
+
completed: '#a78bfa', // completed-task badge — distinct from running/good
|
|
28
|
+
} as const);
|
|
32
29
|
|
|
33
30
|
const STATUS_ORDER: TaskLifecycleState[] = ['queued', 'running', 'blocked', 'failed', 'completed'];
|
|
34
31
|
|
|
32
|
+
/** The follow-up action Enter dispatches once the panel-integration router runs, per WO-131. */
|
|
33
|
+
type TaskFollowUp =
|
|
34
|
+
| { readonly kind: 'agent-jump'; readonly agentId: string }
|
|
35
|
+
| { readonly kind: 'worktree-review'; readonly taskId: string }
|
|
36
|
+
| { readonly kind: 'teamwork-review' };
|
|
37
|
+
|
|
35
38
|
function formatWhen(value?: number): string {
|
|
36
39
|
return value ? new Date(value).toLocaleString() : 'n/a';
|
|
37
40
|
}
|
|
@@ -57,13 +60,13 @@ function kindLabel(kind: RuntimeTask['kind']): string {
|
|
|
57
60
|
function statusColor(status: TaskLifecycleState): string {
|
|
58
61
|
switch (status) {
|
|
59
62
|
case 'queued':
|
|
60
|
-
return C.
|
|
63
|
+
return C.info;
|
|
61
64
|
case 'running':
|
|
62
|
-
return C.
|
|
65
|
+
return C.good;
|
|
63
66
|
case 'blocked':
|
|
64
|
-
return C.
|
|
67
|
+
return C.warn;
|
|
65
68
|
case 'failed':
|
|
66
|
-
return C.
|
|
69
|
+
return C.bad;
|
|
67
70
|
case 'completed':
|
|
68
71
|
return C.completed;
|
|
69
72
|
case 'cancelled':
|
|
@@ -146,28 +149,39 @@ function reviewTaskWorktreeAttachments(
|
|
|
146
149
|
export class TasksPanel extends ScrollableListPanel<RuntimeTask> {
|
|
147
150
|
private readonly readModel?: UiReadModel<UiTasksSnapshot>;
|
|
148
151
|
private readonly worktrees?: UiReadModel<UiWorktreeSnapshot>;
|
|
152
|
+
private readonly opsApi?: OpsApi;
|
|
149
153
|
private readonly unsubscribers: readonly (() => void)[];
|
|
150
154
|
|
|
155
|
+
/** Task id currently expanded into the detail view, or null while browsing the list. */
|
|
156
|
+
private _detailTaskId: string | null = null;
|
|
157
|
+
private _detailScroll = 0;
|
|
158
|
+
|
|
159
|
+
/** Pending cancel confirmation — subject is the task id being cancelled. */
|
|
160
|
+
private _confirm: ConfirmState<string> | null = null;
|
|
161
|
+
|
|
162
|
+
/** Set by handleInput on Enter; consumed by the panel-integration router immediately after. */
|
|
163
|
+
private _pendingFollowUp: TaskFollowUp | null = null;
|
|
164
|
+
|
|
151
165
|
public constructor(
|
|
152
166
|
readModel: UiReadModel<UiTasksSnapshot> | undefined,
|
|
153
167
|
worktrees?: UiReadModel<UiWorktreeSnapshot>,
|
|
168
|
+
opsApi?: OpsApi,
|
|
154
169
|
) {
|
|
155
|
-
super('tasks', 'Tasks', '
|
|
170
|
+
super('tasks', 'Tasks', '▦', 'runtime-ops');
|
|
156
171
|
this.showSelectionGutter = true; // I5: non-color selection affordance
|
|
157
172
|
this.filterEnabled = true;
|
|
158
173
|
this.filterLabel = 'Filter tasks';
|
|
159
174
|
this.readModel = readModel;
|
|
160
175
|
this.worktrees = worktrees;
|
|
176
|
+
this.opsApi = opsApi;
|
|
161
177
|
this.unsubscribers = [
|
|
162
178
|
readModel?.subscribe(() => this.markDirty()),
|
|
163
179
|
worktrees?.subscribe(() => this.markDirty()),
|
|
164
180
|
].filter((unsubscribe): unsubscribe is () => void => Boolean(unsubscribe));
|
|
165
181
|
}
|
|
166
182
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
this.selectedIndex = 0;
|
|
170
|
-
}
|
|
183
|
+
// WO-131: selection is preserved across onActivate — BasePanel's default
|
|
184
|
+
// (mark dirty, no index reset) is exactly what we want, so no override.
|
|
171
185
|
|
|
172
186
|
public override onDestroy(): void {
|
|
173
187
|
for (const unsubscribe of this.unsubscribers) unsubscribe();
|
|
@@ -203,27 +217,179 @@ export class TasksPanel extends ScrollableListPanel<RuntimeTask> {
|
|
|
203
217
|
], C, { selected });
|
|
204
218
|
}
|
|
205
219
|
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
// Input
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
|
|
206
224
|
public handleInput(key: string): boolean {
|
|
225
|
+
if (this.lastError !== null) this.clearError();
|
|
226
|
+
|
|
227
|
+
if (this._confirm) {
|
|
228
|
+
const outcome = handleConfirmInput(this._confirm, key);
|
|
229
|
+
if (outcome === 'confirmed') {
|
|
230
|
+
this._executeCancel(this._confirm.subject);
|
|
231
|
+
this._confirm = null;
|
|
232
|
+
this.markDirty();
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
if (outcome === 'cancelled') {
|
|
236
|
+
this._confirm = null;
|
|
237
|
+
this.markDirty();
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
return true; // absorbed — keep the confirm dialog pending
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const tasks = this.getVisibleItems();
|
|
244
|
+
|
|
245
|
+
// --- Detail mode: WO-131 moves the deep per-task field dump behind an
|
|
246
|
+
// Enter-toggled view so the list itself keeps full viewport in normal
|
|
247
|
+
// browsing. Esc/Left collapses back to the list; Enter (again, while
|
|
248
|
+
// already expanded) dispatches the task's advertised worktree follow-up.
|
|
249
|
+
if (this._detailTaskId !== null) {
|
|
250
|
+
if (key === 'escape' || key === 'left') {
|
|
251
|
+
this._detailTaskId = null;
|
|
252
|
+
this._detailScroll = 0;
|
|
253
|
+
this.markDirty();
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
if (key === 'enter' || key === 'return') {
|
|
257
|
+
const task = tasks.find((t) => t.id === this._detailTaskId);
|
|
258
|
+
if (task) this._pendingFollowUp = { kind: 'worktree-review', taskId: task.id };
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
switch (key) {
|
|
262
|
+
case 'up':
|
|
263
|
+
case 'k':
|
|
264
|
+
this._detailScroll = Math.max(0, this._detailScroll - 1);
|
|
265
|
+
this.markDirty();
|
|
266
|
+
return true;
|
|
267
|
+
case 'down':
|
|
268
|
+
case 'j':
|
|
269
|
+
this._detailScroll += 1;
|
|
270
|
+
this.markDirty();
|
|
271
|
+
return true;
|
|
272
|
+
case 'pageup':
|
|
273
|
+
this._detailScroll = Math.max(0, this._detailScroll - this.getPageSize());
|
|
274
|
+
this.markDirty();
|
|
275
|
+
return true;
|
|
276
|
+
case 'pagedown':
|
|
277
|
+
this._detailScroll += this.getPageSize();
|
|
278
|
+
this.markDirty();
|
|
279
|
+
return true;
|
|
280
|
+
case 'home':
|
|
281
|
+
case 'g':
|
|
282
|
+
this._detailScroll = 0;
|
|
283
|
+
this.markDirty();
|
|
284
|
+
return true;
|
|
285
|
+
default:
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// --- List mode ---
|
|
207
291
|
if (key === 'home') {
|
|
208
292
|
this.selectedIndex = 0;
|
|
209
293
|
this.markDirty();
|
|
210
294
|
return true;
|
|
211
295
|
}
|
|
212
296
|
if (key === 'end') {
|
|
213
|
-
const tasks = this.getVisibleItems();
|
|
214
297
|
this.selectedIndex = Math.max(0, tasks.length - 1);
|
|
215
298
|
this.markDirty();
|
|
216
299
|
return true;
|
|
217
300
|
}
|
|
301
|
+
|
|
302
|
+
if (key === 'c' && !this.filterActive) {
|
|
303
|
+
const task = this.getSelectedItem();
|
|
304
|
+
if (!task || !task.cancellable) return false;
|
|
305
|
+
this._confirm = { subject: task.id, label: `task "${task.title}"`, verb: 'Cancel' };
|
|
306
|
+
this.markDirty();
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// The task-family posture surface is bound for real: w stages a follow-up
|
|
311
|
+
// the integration router dispatches as '/teamwork review' via executeCommand.
|
|
312
|
+
if (key === 'w' && !this.filterActive) {
|
|
313
|
+
this._pendingFollowUp = { kind: 'teamwork-review' };
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Enter commits the filter query while actively typing (ScrollableListPanel
|
|
318
|
+
// contract) — only treated as detail-mode/agent-jump once filtering isn't active.
|
|
319
|
+
if ((key === 'enter' || key === 'return') && !this.filterActive) {
|
|
320
|
+
const task = this.getSelectedItem();
|
|
321
|
+
if (!task) return false;
|
|
322
|
+
// Agent-kind tasks jump straight to the Agent Inspector — that panel
|
|
323
|
+
// already owns the deep per-agent timeline, so Tasks does not duplicate it.
|
|
324
|
+
if (task.kind === 'agent') {
|
|
325
|
+
this._pendingFollowUp = { kind: 'agent-jump', agentId: task.owner };
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
this._detailTaskId = task.id;
|
|
329
|
+
this._detailScroll = 0;
|
|
330
|
+
this.markDirty();
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
|
|
218
334
|
return super.handleInput(key);
|
|
219
335
|
}
|
|
220
336
|
|
|
337
|
+
/**
|
|
338
|
+
* Consumed by the panel-integration router immediately after handleInput
|
|
339
|
+
* returns true for the same Enter keystroke. Returns and clears whatever
|
|
340
|
+
* follow-up (if any) that keystroke queued.
|
|
341
|
+
*/
|
|
342
|
+
public consumePendingFollowUp(): TaskFollowUp | null {
|
|
343
|
+
const pending = this._pendingFollowUp;
|
|
344
|
+
this._pendingFollowUp = null;
|
|
345
|
+
return pending;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private _executeCancel(taskId: string): void {
|
|
349
|
+
if (!this.opsApi) {
|
|
350
|
+
this.setError('Ops API is not wired for this runtime.');
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
try {
|
|
354
|
+
this.opsApi.tasks.cancel(taskId);
|
|
355
|
+
} catch (err) {
|
|
356
|
+
this.setError(summarizeError(err));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ---------------------------------------------------------------------------
|
|
361
|
+
// Render
|
|
362
|
+
// ---------------------------------------------------------------------------
|
|
363
|
+
|
|
221
364
|
public render(width: number, height: number): Line[] {
|
|
222
365
|
this.clampSelection();
|
|
366
|
+
|
|
367
|
+
if (this._detailTaskId !== null) {
|
|
368
|
+
const task = this.getVisibleItems().find((t) => t.id === this._detailTaskId);
|
|
369
|
+
if (task) return this._renderDetail(width, height, task);
|
|
370
|
+
this._detailTaskId = null; // task vanished (pruned/completed away) — fall back to list
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return this._renderListView(width, height);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
private _renderListView(width: number, height: number): Line[] {
|
|
223
377
|
const intro = 'Live task lifecycle, ownership, retries, and result/error details across runtime execution domains.';
|
|
224
|
-
const
|
|
378
|
+
const visibleTasks = this.getVisibleItems();
|
|
379
|
+
const selected = this.getSelectedItem();
|
|
225
380
|
// Context-aware footer: position + only the keys that apply in the current
|
|
226
|
-
// (filtering vs browsing) state.
|
|
381
|
+
// (filtering vs browsing) state, and to the selected task's kind/cancellable flag.
|
|
382
|
+
const browseHints: Array<{ keys: string; label: string }> = [
|
|
383
|
+
{ keys: visibleTasks.length > 0 ? `${this.selectedIndex + 1}/${visibleTasks.length}` : '0/0', label: 'task' },
|
|
384
|
+
{ keys: '↑/↓', label: 'move' },
|
|
385
|
+
{ keys: 'Home/End', label: 'jump' },
|
|
386
|
+
{ keys: '/', label: 'filter' },
|
|
387
|
+
{ keys: 'w', label: 'teamwork review' },
|
|
388
|
+
];
|
|
389
|
+
if (selected) {
|
|
390
|
+
browseHints.push({ keys: 'Enter', label: selected.kind === 'agent' ? 'agent detail' : 'task detail' });
|
|
391
|
+
if (selected.cancellable) browseHints.push({ keys: 'c', label: 'cancel' });
|
|
392
|
+
}
|
|
227
393
|
const footerLines = [
|
|
228
394
|
this.filterActive
|
|
229
395
|
? buildKeyboardHints(width, [
|
|
@@ -231,12 +397,7 @@ export class TasksPanel extends ScrollableListPanel<RuntimeTask> {
|
|
|
231
397
|
{ keys: 'Enter', label: 'apply' },
|
|
232
398
|
{ keys: 'Esc', label: 'clear' },
|
|
233
399
|
], C)
|
|
234
|
-
: buildKeyboardHints(width,
|
|
235
|
-
{ keys: visibleCount > 0 ? `${this.selectedIndex + 1}/${visibleCount}` : '0/0', label: 'task' },
|
|
236
|
-
{ keys: '↑/↓', label: 'move' },
|
|
237
|
-
{ keys: 'Home/End', label: 'jump' },
|
|
238
|
-
{ keys: '/', label: 'filter' },
|
|
239
|
-
], C),
|
|
400
|
+
: buildKeyboardHints(width, browseHints, C),
|
|
240
401
|
];
|
|
241
402
|
|
|
242
403
|
if (!this.readModel) {
|
|
@@ -272,19 +433,18 @@ export class TasksPanel extends ScrollableListPanel<RuntimeTask> {
|
|
|
272
433
|
const postureLines: Line[] = [
|
|
273
434
|
buildPanelLine(width, [
|
|
274
435
|
[' queued ', C.label],
|
|
275
|
-
[String(queuedCount), queuedCount > 0 ? C.
|
|
436
|
+
[String(queuedCount), queuedCount > 0 ? C.info : C.dim],
|
|
276
437
|
[' running ', C.label],
|
|
277
|
-
[String(runningCount), runningCount > 0 ? C.
|
|
438
|
+
[String(runningCount), runningCount > 0 ? C.good : C.dim],
|
|
278
439
|
[' blocked ', C.label],
|
|
279
|
-
[String(blockedCount), blockedCount > 0 ? C.
|
|
440
|
+
[String(blockedCount), blockedCount > 0 ? C.warn : C.dim],
|
|
280
441
|
[' failed ', C.label],
|
|
281
|
-
[String(failedCount), failedCount > 0 ? C.
|
|
442
|
+
[String(failedCount), failedCount > 0 ? C.bad : C.dim],
|
|
282
443
|
[' completed ', C.label],
|
|
283
444
|
[String(completedCount), completedCount > 0 ? C.completed : C.dim],
|
|
284
445
|
]),
|
|
285
446
|
];
|
|
286
447
|
|
|
287
|
-
const selected = tasks[this.selectedIndex];
|
|
288
448
|
if (selected) {
|
|
289
449
|
postureLines.push(buildPanelLine(width, [
|
|
290
450
|
[' selected ', C.label],
|
|
@@ -298,124 +458,151 @@ export class TasksPanel extends ScrollableListPanel<RuntimeTask> {
|
|
|
298
458
|
]));
|
|
299
459
|
}
|
|
300
460
|
postureLines.push(
|
|
301
|
-
buildGuidanceLine(width, '/teamwork review', 'inspect task-family posture, archetype metadata, and recovery options for active work', C),
|
|
302
|
-
buildGuidanceLine(width, '/worktree task <task-id>', 'review worktree ownership, restore, and merge posture for the selected task', C),
|
|
461
|
+
buildGuidanceLine(width, '/teamwork review', 'inspect task-family posture, archetype metadata, and recovery options for active work — press w to run it', C),
|
|
303
462
|
);
|
|
304
463
|
|
|
464
|
+
const headerLines: Line[] = buildSummaryBlock(width, 'Task posture', postureLines, C);
|
|
465
|
+
|
|
466
|
+
const effectiveFooter = this._confirm
|
|
467
|
+
? [...renderConfirmLines(width, this._confirm), ...footerLines]
|
|
468
|
+
: footerLines;
|
|
469
|
+
|
|
470
|
+
return this.renderList(width, height, {
|
|
471
|
+
title: 'Task Control Room',
|
|
472
|
+
header: headerLines,
|
|
473
|
+
footer: effectiveFooter,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/** Detail-mode body for one task — the deep field dump previously always shown inline. */
|
|
478
|
+
private _renderDetail(width: number, height: number, task: RuntimeTask): Line[] {
|
|
479
|
+
this.needsRender = false;
|
|
480
|
+
const descriptor = task.description ? parseTaskDescriptor(task.description) : null;
|
|
305
481
|
const detailRows: Line[] = [];
|
|
306
|
-
|
|
307
|
-
|
|
482
|
+
detailRows.push(buildPanelLine(width, [
|
|
483
|
+
[' Title: ', C.label],
|
|
484
|
+
[task.title, C.value],
|
|
485
|
+
[' Status: ', C.label],
|
|
486
|
+
[task.status, statusColor(task.status)],
|
|
487
|
+
[' Kind: ', C.label],
|
|
488
|
+
[task.kind, C.value],
|
|
489
|
+
]));
|
|
490
|
+
detailRows.push(buildPanelLine(width, [
|
|
491
|
+
[' Owner: ', C.label],
|
|
492
|
+
[task.owner, C.value],
|
|
493
|
+
[' Cancellable: ', C.label],
|
|
494
|
+
[task.cancellable ? 'yes' : 'no', task.cancellable ? C.good : C.bad],
|
|
495
|
+
[' Queue: ', C.label],
|
|
496
|
+
[formatWhen(task.queuedAt), C.dim],
|
|
497
|
+
]));
|
|
498
|
+
detailRows.push(buildPanelLine(width, [
|
|
499
|
+
[' Started: ', C.label],
|
|
500
|
+
[formatWhen(task.startedAt), C.dim],
|
|
501
|
+
[' Ended: ', C.label],
|
|
502
|
+
[formatWhen(task.endedAt), C.dim],
|
|
503
|
+
[' Duration: ', C.label],
|
|
504
|
+
[formatDuration(task.startedAt, task.endedAt), C.dim],
|
|
505
|
+
]));
|
|
506
|
+
if (descriptor?.mode || descriptor?.family || descriptor?.source) {
|
|
308
507
|
detailRows.push(buildPanelLine(width, [
|
|
309
|
-
['
|
|
310
|
-
[
|
|
311
|
-
['
|
|
312
|
-
[
|
|
313
|
-
['
|
|
314
|
-
[
|
|
508
|
+
[' Mode: ', C.label],
|
|
509
|
+
[descriptor?.mode ?? 'n/a', C.value],
|
|
510
|
+
[' Family: ', C.label],
|
|
511
|
+
[descriptor?.family ?? 'n/a', C.info],
|
|
512
|
+
[' Source: ', C.label],
|
|
513
|
+
[descriptor?.source ?? 'builtin/runtime', C.dim],
|
|
315
514
|
]));
|
|
515
|
+
}
|
|
516
|
+
if (descriptor?.reviewMode || descriptor?.executionProtocol || descriptor?.template) {
|
|
316
517
|
detailRows.push(buildPanelLine(width, [
|
|
317
|
-
['
|
|
318
|
-
[
|
|
319
|
-
['
|
|
320
|
-
[
|
|
321
|
-
['
|
|
322
|
-
[
|
|
518
|
+
[' Review: ', C.label],
|
|
519
|
+
[descriptor?.reviewMode ?? 'n/a', C.value],
|
|
520
|
+
[' Protocol: ', C.label],
|
|
521
|
+
[descriptor?.executionProtocol ?? 'n/a', C.value],
|
|
522
|
+
[' Template: ', C.label],
|
|
523
|
+
[descriptor?.template ?? 'n/a', C.dim],
|
|
323
524
|
]));
|
|
525
|
+
}
|
|
526
|
+
if (task.correlationId || task.turnId) {
|
|
324
527
|
detailRows.push(buildPanelLine(width, [
|
|
325
|
-
['
|
|
326
|
-
[
|
|
327
|
-
['
|
|
328
|
-
[
|
|
329
|
-
[' Duration: ', C.label],
|
|
330
|
-
[formatDuration(selected.startedAt, selected.endedAt), C.dim],
|
|
528
|
+
[' Correlation: ', C.label],
|
|
529
|
+
[task.correlationId ?? 'n/a', C.dim],
|
|
530
|
+
[' Turn: ', C.label],
|
|
531
|
+
[task.turnId ?? 'n/a', C.dim],
|
|
331
532
|
]));
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
if (selected.correlationId || selected.turnId) {
|
|
353
|
-
detailRows.push(buildPanelLine(width, [
|
|
354
|
-
[' Correlation: ', C.label],
|
|
355
|
-
[selected.correlationId ?? 'n/a', C.dim],
|
|
356
|
-
[' Turn: ', C.label],
|
|
357
|
-
[selected.turnId ?? 'n/a', C.dim],
|
|
358
|
-
]));
|
|
359
|
-
}
|
|
360
|
-
if (selected.parentTaskId || selected.childTaskIds.length > 0) {
|
|
361
|
-
detailRows.push(buildPanelLine(width, [
|
|
362
|
-
[' Parent: ', C.label],
|
|
363
|
-
[selected.parentTaskId ?? 'none', C.dim],
|
|
364
|
-
[' Children: ', C.label],
|
|
365
|
-
[selected.childTaskIds.length > 0 ? selected.childTaskIds.join(', ') : 'none', C.dim],
|
|
366
|
-
]));
|
|
367
|
-
}
|
|
368
|
-
const attachedWorktrees = reviewTaskWorktreeAttachments(selected.id, this.worktrees);
|
|
369
|
-
if (attachedWorktrees.total > 0) {
|
|
370
|
-
detailRows.push(buildPanelLine(width, [
|
|
371
|
-
[' Worktrees: ', C.label],
|
|
372
|
-
[`${attachedWorktrees.total} tracked`, C.info],
|
|
373
|
-
[' Active: ', C.label],
|
|
374
|
-
[String(attachedWorktrees.active), attachedWorktrees.active > 0 ? C.running : C.dim],
|
|
375
|
-
[' Paused: ', C.label],
|
|
376
|
-
[String(attachedWorktrees.paused), attachedWorktrees.paused > 0 ? C.blocked : C.dim],
|
|
377
|
-
]));
|
|
533
|
+
}
|
|
534
|
+
if (task.parentTaskId || task.childTaskIds.length > 0) {
|
|
535
|
+
detailRows.push(buildPanelLine(width, [
|
|
536
|
+
[' Parent: ', C.label],
|
|
537
|
+
[task.parentTaskId ?? 'none', C.dim],
|
|
538
|
+
[' Children: ', C.label],
|
|
539
|
+
[task.childTaskIds.length > 0 ? task.childTaskIds.join(', ') : 'none', C.dim],
|
|
540
|
+
]));
|
|
541
|
+
}
|
|
542
|
+
const attachedWorktrees = reviewTaskWorktreeAttachments(task.id, this.worktrees);
|
|
543
|
+
if (attachedWorktrees.total > 0) {
|
|
544
|
+
detailRows.push(buildPanelLine(width, [
|
|
545
|
+
[' Worktrees: ', C.label],
|
|
546
|
+
[`${attachedWorktrees.total} tracked`, C.info],
|
|
547
|
+
[' Active: ', C.label],
|
|
548
|
+
[String(attachedWorktrees.active), attachedWorktrees.active > 0 ? C.good : C.dim],
|
|
549
|
+
[' Paused: ', C.label],
|
|
550
|
+
[String(attachedWorktrees.paused), attachedWorktrees.paused > 0 ? C.warn : C.dim],
|
|
551
|
+
]));
|
|
552
|
+
for (const record of attachedWorktrees.records.slice(0, 2)) {
|
|
378
553
|
detailRows.push(buildPanelLine(width, [[
|
|
379
|
-
`
|
|
380
|
-
C.dim,
|
|
554
|
+
truncateDisplay(` ${record.state.padEnd(15)} ${record.path}`, Math.max(0, width - 2)),
|
|
555
|
+
record.state === 'active' ? C.good : record.state === 'paused' ? C.warn : C.dim,
|
|
381
556
|
]]));
|
|
382
|
-
for (const record of attachedWorktrees.records.slice(0, 2)) {
|
|
383
|
-
detailRows.push(buildPanelLine(width, [[
|
|
384
|
-
truncateDisplay(` ${record.state.padEnd(15)} ${record.path}`, Math.max(0, width - 2)),
|
|
385
|
-
record.state === 'active' ? C.running : record.state === 'paused' ? C.blocked : C.dim,
|
|
386
|
-
]]));
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
if (selected.retryPolicy) {
|
|
390
|
-
detailRows.push(buildPanelLine(width, [
|
|
391
|
-
[' Retry: ', C.label],
|
|
392
|
-
[`${selected.retryPolicy.currentAttempt}/${selected.retryPolicy.maxAttempts} ${selected.retryPolicy.backoff}`, C.value],
|
|
393
|
-
]));
|
|
394
|
-
}
|
|
395
|
-
if (selected.error) {
|
|
396
|
-
detailRows.push(buildPanelLine(width, [
|
|
397
|
-
[' Error: ', C.label],
|
|
398
|
-
[truncateDisplay(selected.error, Math.max(0, width - 10)), C.failed],
|
|
399
|
-
]));
|
|
400
|
-
}
|
|
401
|
-
if (selected.result !== undefined) {
|
|
402
|
-
const resultText = safeJson(selected.result);
|
|
403
|
-
detailRows.push(buildPanelLine(width, [
|
|
404
|
-
[' Result: ', C.label],
|
|
405
|
-
[truncateDisplay(resultText, Math.max(0, width - 11)), C.dim],
|
|
406
|
-
]));
|
|
407
557
|
}
|
|
408
558
|
}
|
|
559
|
+
if (task.retryPolicy) {
|
|
560
|
+
detailRows.push(buildPanelLine(width, [
|
|
561
|
+
[' Retry: ', C.label],
|
|
562
|
+
[`${task.retryPolicy.currentAttempt}/${task.retryPolicy.maxAttempts} ${task.retryPolicy.backoff}`, C.value],
|
|
563
|
+
]));
|
|
564
|
+
}
|
|
565
|
+
if (task.error) {
|
|
566
|
+
detailRows.push(buildPanelLine(width, [
|
|
567
|
+
[' Error: ', C.label],
|
|
568
|
+
[truncateDisplay(task.error, Math.max(0, width - 10)), C.bad],
|
|
569
|
+
]));
|
|
570
|
+
}
|
|
571
|
+
if (task.result !== undefined) {
|
|
572
|
+
const resultText = safeJson(task.result);
|
|
573
|
+
detailRows.push(buildPanelLine(width, [
|
|
574
|
+
[' Result: ', C.label],
|
|
575
|
+
[truncateDisplay(resultText, Math.max(0, width - 11)), C.dim],
|
|
576
|
+
]));
|
|
577
|
+
}
|
|
409
578
|
|
|
410
|
-
|
|
579
|
+
// The actionable follow-up replaces the old printed "/worktree task <task-id>"
|
|
580
|
+
// signpost — pressing Enter here dispatches it for real via ctx.executeCommand.
|
|
581
|
+
detailRows.push(buildGuidanceLine(width, `/worktree task ${task.id}`, 'review worktree ownership, restore, and merge posture for this task — press Enter to run it', C));
|
|
411
582
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
583
|
+
const footer = buildKeyboardHints(width, [
|
|
584
|
+
{ keys: '↑/↓', label: 'scroll' },
|
|
585
|
+
{ keys: 'Enter', label: 'review worktree ownership' },
|
|
586
|
+
{ keys: 'Esc', label: 'back' },
|
|
587
|
+
], C);
|
|
588
|
+
|
|
589
|
+
const resolved = resolveScrollablePanelSection(width, height, {
|
|
590
|
+
palette: C,
|
|
591
|
+
afterSections: [{ lines: [footer] }],
|
|
592
|
+
section: {
|
|
593
|
+
scrollableLines: detailRows,
|
|
594
|
+
scrollOffset: this._detailScroll,
|
|
595
|
+
appendWindowSummary: detailRows.length > 5 ? { dimColor: C.dim } : undefined,
|
|
596
|
+
},
|
|
597
|
+
});
|
|
598
|
+
this._detailScroll = resolved.scrollOffset;
|
|
599
|
+
|
|
600
|
+
const lines = buildPanelWorkspace(width, height, {
|
|
601
|
+
title: `Task · ${task.title}`,
|
|
602
|
+
sections: [resolved.section, { lines: [footer] }],
|
|
603
|
+
palette: C,
|
|
419
604
|
});
|
|
605
|
+
while (lines.length < height) lines.push(createEmptyLine(width));
|
|
606
|
+
return lines.slice(0, height);
|
|
420
607
|
}
|
|
421
608
|
}
|