@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,608 +0,0 @@
|
|
|
1
|
-
import type { Line } from '../types/grid.ts';
|
|
2
|
-
import { createEmptyLine } from '../types/grid.ts';
|
|
3
|
-
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
4
|
-
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
5
|
-
import type { OpsApi, RuntimeTask, TaskLifecycleState } from '@/runtime/index.ts';
|
|
6
|
-
import type { ManagedWorktreeMeta } from '@/runtime/index.ts';
|
|
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';
|
|
10
|
-
import {
|
|
11
|
-
buildEmptyState,
|
|
12
|
-
buildGuidanceLine,
|
|
13
|
-
buildKeyboardHints,
|
|
14
|
-
buildPanelListRow,
|
|
15
|
-
buildPanelLine,
|
|
16
|
-
buildSummaryBlock,
|
|
17
|
-
buildPanelWorkspace,
|
|
18
|
-
resolveScrollablePanelSection,
|
|
19
|
-
DEFAULT_PANEL_PALETTE,
|
|
20
|
-
extendPalette,
|
|
21
|
-
} from './polish.ts';
|
|
22
|
-
import { formatElapsed } from '../utils/format-elapsed.ts';
|
|
23
|
-
|
|
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);
|
|
29
|
-
|
|
30
|
-
const STATUS_ORDER: TaskLifecycleState[] = ['queued', 'running', 'blocked', 'failed', 'completed'];
|
|
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
|
-
|
|
38
|
-
function formatWhen(value?: number): string {
|
|
39
|
-
return value ? new Date(value).toLocaleString() : 'n/a';
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function formatDuration(startedAt?: number, endedAt?: number): string {
|
|
43
|
-
if (!startedAt) return 'n/a';
|
|
44
|
-
return formatElapsed(Math.max(0, (endedAt ?? Date.now()) - startedAt));
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function kindLabel(kind: RuntimeTask['kind']): string {
|
|
48
|
-
switch (kind) {
|
|
49
|
-
case 'exec': return 'exec';
|
|
50
|
-
case 'agent': return 'agent';
|
|
51
|
-
case 'acp': return 'acp';
|
|
52
|
-
case 'scheduler': return 'scheduler';
|
|
53
|
-
case 'daemon': return 'daemon';
|
|
54
|
-
case 'mcp': return 'mcp';
|
|
55
|
-
case 'plugin': return 'plugin';
|
|
56
|
-
case 'integration': return 'integration';
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function statusColor(status: TaskLifecycleState): string {
|
|
61
|
-
switch (status) {
|
|
62
|
-
case 'queued':
|
|
63
|
-
return C.info;
|
|
64
|
-
case 'running':
|
|
65
|
-
return C.good;
|
|
66
|
-
case 'blocked':
|
|
67
|
-
return C.warn;
|
|
68
|
-
case 'failed':
|
|
69
|
-
return C.bad;
|
|
70
|
-
case 'completed':
|
|
71
|
-
return C.completed;
|
|
72
|
-
case 'cancelled':
|
|
73
|
-
return C.dim;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function sortTasks(tasks: RuntimeTask[]): RuntimeTask[] {
|
|
78
|
-
const order = new Map(STATUS_ORDER.map((status, index) => [status, index] as const));
|
|
79
|
-
return [...tasks].sort((a, b) => {
|
|
80
|
-
const statusDelta = (order.get(a.status) ?? 99) - (order.get(b.status) ?? 99);
|
|
81
|
-
if (statusDelta !== 0) return statusDelta;
|
|
82
|
-
const aTime = a.startedAt ?? a.queuedAt;
|
|
83
|
-
const bTime = b.startedAt ?? b.queuedAt;
|
|
84
|
-
return bTime - aTime || a.title.localeCompare(b.title);
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function safeJson(value: unknown): string {
|
|
89
|
-
try {
|
|
90
|
-
return JSON.stringify(value);
|
|
91
|
-
} catch {
|
|
92
|
-
return String(value);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
interface TaskDescriptorMeta {
|
|
97
|
-
readonly mode?: string;
|
|
98
|
-
readonly template?: string;
|
|
99
|
-
readonly reviewMode?: string;
|
|
100
|
-
readonly executionProtocol?: string;
|
|
101
|
-
readonly source?: string;
|
|
102
|
-
readonly family?: string;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function parseTaskDescriptor(description: string): TaskDescriptorMeta | null {
|
|
106
|
-
try {
|
|
107
|
-
const parsed = JSON.parse(description) as TaskDescriptorMeta;
|
|
108
|
-
if (!parsed || typeof parsed !== 'object') return null;
|
|
109
|
-
return parsed;
|
|
110
|
-
} catch {
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
interface TaskWorktreeAttachmentReview {
|
|
116
|
-
readonly total: number;
|
|
117
|
-
readonly active: number;
|
|
118
|
-
readonly paused: number;
|
|
119
|
-
readonly kept: number;
|
|
120
|
-
readonly discard: number;
|
|
121
|
-
readonly pendingCleanup: number;
|
|
122
|
-
readonly records: readonly ManagedWorktreeMeta[];
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function reviewTaskWorktreeAttachments(
|
|
126
|
-
taskId: string,
|
|
127
|
-
worktrees?: UiReadModel<UiWorktreeSnapshot>,
|
|
128
|
-
): TaskWorktreeAttachmentReview {
|
|
129
|
-
const records = (worktrees?.getSnapshot().records ?? []).filter((record) => record.taskId === taskId);
|
|
130
|
-
return records.reduce<TaskWorktreeAttachmentReview>((summary, record) => ({
|
|
131
|
-
total: summary.total + 1,
|
|
132
|
-
active: summary.active + (record.state === 'active' ? 1 : 0),
|
|
133
|
-
paused: summary.paused + (record.state === 'paused' ? 1 : 0),
|
|
134
|
-
kept: summary.kept + (record.state === 'kept' ? 1 : 0),
|
|
135
|
-
discard: summary.discard + (record.state === 'discard' ? 1 : 0),
|
|
136
|
-
pendingCleanup: summary.pendingCleanup + (record.state === 'pending-cleanup' ? 1 : 0),
|
|
137
|
-
records: [...summary.records, record],
|
|
138
|
-
}), {
|
|
139
|
-
total: 0,
|
|
140
|
-
active: 0,
|
|
141
|
-
paused: 0,
|
|
142
|
-
kept: 0,
|
|
143
|
-
discard: 0,
|
|
144
|
-
pendingCleanup: 0,
|
|
145
|
-
records: [],
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export class TasksPanel extends ScrollableListPanel<RuntimeTask> {
|
|
150
|
-
private readonly readModel?: UiReadModel<UiTasksSnapshot>;
|
|
151
|
-
private readonly worktrees?: UiReadModel<UiWorktreeSnapshot>;
|
|
152
|
-
private readonly opsApi?: OpsApi;
|
|
153
|
-
private readonly unsubscribers: readonly (() => void)[];
|
|
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
|
-
|
|
165
|
-
public constructor(
|
|
166
|
-
readModel: UiReadModel<UiTasksSnapshot> | undefined,
|
|
167
|
-
worktrees?: UiReadModel<UiWorktreeSnapshot>,
|
|
168
|
-
opsApi?: OpsApi,
|
|
169
|
-
) {
|
|
170
|
-
super('tasks', 'Tasks', '▦', 'runtime-ops');
|
|
171
|
-
this.showSelectionGutter = true; // I5: non-color selection affordance
|
|
172
|
-
this.filterEnabled = true;
|
|
173
|
-
this.filterLabel = 'Filter tasks';
|
|
174
|
-
this.readModel = readModel;
|
|
175
|
-
this.worktrees = worktrees;
|
|
176
|
-
this.opsApi = opsApi;
|
|
177
|
-
this.unsubscribers = [
|
|
178
|
-
readModel?.subscribe(() => this.markDirty()),
|
|
179
|
-
worktrees?.subscribe(() => this.markDirty()),
|
|
180
|
-
].filter((unsubscribe): unsubscribe is () => void => Boolean(unsubscribe));
|
|
181
|
-
}
|
|
182
|
-
|
|
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.
|
|
185
|
-
|
|
186
|
-
public override onDestroy(): void {
|
|
187
|
-
for (const unsubscribe of this.unsubscribers) unsubscribe();
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
protected override getPalette() { return C; }
|
|
191
|
-
protected override getEmptyStateMessage() { return ' No tasks recorded yet.'; }
|
|
192
|
-
protected override getEmptyStateActions() {
|
|
193
|
-
return [
|
|
194
|
-
{ command: '/tasks create', summary: 'create a tracked task from the shell' },
|
|
195
|
-
{ command: '/orchestration', summary: 'review graph-native task execution and WRFC flows' },
|
|
196
|
-
];
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
protected override filterMatches(task: RuntimeTask, q: string): boolean {
|
|
200
|
-
return task.title.toLowerCase().includes(q)
|
|
201
|
-
|| task.status.toLowerCase().includes(q)
|
|
202
|
-
|| task.id.toLowerCase().includes(q)
|
|
203
|
-
|| String(task.kind).toLowerCase().includes(q);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
protected getItems(): readonly RuntimeTask[] {
|
|
207
|
-
if (!this.readModel) return [];
|
|
208
|
-
return sortTasks([...this.readModel.getSnapshot().tasks]);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
protected renderItem(task: RuntimeTask, index: number, selected: boolean, width: number): Line {
|
|
212
|
-
return buildPanelListRow(width, [
|
|
213
|
-
{ text: task.status.padEnd(10), fg: statusColor(task.status) },
|
|
214
|
-
{ text: ` ${kindLabel(task.kind).padEnd(12)}`, fg: C.value },
|
|
215
|
-
{ text: ` ${task.id.slice(0, 8)} `, fg: C.dim },
|
|
216
|
-
{ text: truncateDisplay(task.title, Math.max(0, width - 37)), fg: C.value },
|
|
217
|
-
], C, { selected });
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// ---------------------------------------------------------------------------
|
|
221
|
-
// Input
|
|
222
|
-
// ---------------------------------------------------------------------------
|
|
223
|
-
|
|
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 ---
|
|
291
|
-
if (key === 'home') {
|
|
292
|
-
this.selectedIndex = 0;
|
|
293
|
-
this.markDirty();
|
|
294
|
-
return true;
|
|
295
|
-
}
|
|
296
|
-
if (key === 'end') {
|
|
297
|
-
this.selectedIndex = Math.max(0, tasks.length - 1);
|
|
298
|
-
this.markDirty();
|
|
299
|
-
return true;
|
|
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
|
-
|
|
334
|
-
return super.handleInput(key);
|
|
335
|
-
}
|
|
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
|
-
|
|
364
|
-
public render(width: number, height: number): Line[] {
|
|
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[] {
|
|
377
|
-
const intro = 'Live task lifecycle, ownership, retries, and result/error details across runtime execution domains.';
|
|
378
|
-
const visibleTasks = this.getVisibleItems();
|
|
379
|
-
const selected = this.getSelectedItem();
|
|
380
|
-
// Context-aware footer: position + only the keys that apply in the current
|
|
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
|
-
}
|
|
393
|
-
const footerLines = [
|
|
394
|
-
this.filterActive
|
|
395
|
-
? buildKeyboardHints(width, [
|
|
396
|
-
{ keys: 'type', label: 'filter tasks' },
|
|
397
|
-
{ keys: 'Enter', label: 'apply' },
|
|
398
|
-
{ keys: 'Esc', label: 'clear' },
|
|
399
|
-
], C)
|
|
400
|
-
: buildKeyboardHints(width, browseHints, C),
|
|
401
|
-
];
|
|
402
|
-
|
|
403
|
-
if (!this.readModel) {
|
|
404
|
-
const workspace = buildPanelWorkspace(width, height, {
|
|
405
|
-
title: 'Task Control Room',
|
|
406
|
-
intro,
|
|
407
|
-
sections: [{
|
|
408
|
-
lines: buildEmptyState(
|
|
409
|
-
width,
|
|
410
|
-
' Runtime store not wired into this panel yet.',
|
|
411
|
-
'Use the Tasks panel with a live runtime store to review active execution, cancellations, retries, and completion results.',
|
|
412
|
-
[{ command: '/tasks', summary: 'open or operate the task surface from a shell-owned runtime' }],
|
|
413
|
-
C,
|
|
414
|
-
),
|
|
415
|
-
}],
|
|
416
|
-
palette: C,
|
|
417
|
-
});
|
|
418
|
-
while (workspace.length < height) workspace.push(createEmptyLine(width));
|
|
419
|
-
return workspace;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
const tasks = this.getItems();
|
|
423
|
-
const counts = STATUS_ORDER.map((status) => ({
|
|
424
|
-
status,
|
|
425
|
-
count: tasks.filter((task) => task.status === status).length,
|
|
426
|
-
}));
|
|
427
|
-
const blockedCount = counts.find((entry) => entry.status === 'blocked')?.count ?? 0;
|
|
428
|
-
const failedCount = counts.find((entry) => entry.status === 'failed')?.count ?? 0;
|
|
429
|
-
const runningCount = counts.find((entry) => entry.status === 'running')?.count ?? 0;
|
|
430
|
-
const queuedCount = counts.find((entry) => entry.status === 'queued')?.count ?? 0;
|
|
431
|
-
const completedCount = counts.find((entry) => entry.status === 'completed')?.count ?? 0;
|
|
432
|
-
|
|
433
|
-
const postureLines: Line[] = [
|
|
434
|
-
buildPanelLine(width, [
|
|
435
|
-
[' queued ', C.label],
|
|
436
|
-
[String(queuedCount), queuedCount > 0 ? C.info : C.dim],
|
|
437
|
-
[' running ', C.label],
|
|
438
|
-
[String(runningCount), runningCount > 0 ? C.good : C.dim],
|
|
439
|
-
[' blocked ', C.label],
|
|
440
|
-
[String(blockedCount), blockedCount > 0 ? C.warn : C.dim],
|
|
441
|
-
[' failed ', C.label],
|
|
442
|
-
[String(failedCount), failedCount > 0 ? C.bad : C.dim],
|
|
443
|
-
[' completed ', C.label],
|
|
444
|
-
[String(completedCount), completedCount > 0 ? C.completed : C.dim],
|
|
445
|
-
]),
|
|
446
|
-
];
|
|
447
|
-
|
|
448
|
-
if (selected) {
|
|
449
|
-
postureLines.push(buildPanelLine(width, [
|
|
450
|
-
[' selected ', C.label],
|
|
451
|
-
[selected.id, C.info],
|
|
452
|
-
[' status ', C.label],
|
|
453
|
-
[selected.status, statusColor(selected.status)],
|
|
454
|
-
[' kind ', C.label],
|
|
455
|
-
[selected.kind, C.value],
|
|
456
|
-
[' owner ', C.label],
|
|
457
|
-
[truncateDisplay(selected.owner, Math.max(0, width - 46)), C.dim],
|
|
458
|
-
]));
|
|
459
|
-
}
|
|
460
|
-
postureLines.push(
|
|
461
|
-
buildGuidanceLine(width, '/teamwork review', 'inspect task-family posture, archetype metadata, and recovery options for active work — press w to run it', C),
|
|
462
|
-
);
|
|
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;
|
|
481
|
-
const detailRows: Line[] = [];
|
|
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) {
|
|
507
|
-
detailRows.push(buildPanelLine(width, [
|
|
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],
|
|
514
|
-
]));
|
|
515
|
-
}
|
|
516
|
-
if (descriptor?.reviewMode || descriptor?.executionProtocol || descriptor?.template) {
|
|
517
|
-
detailRows.push(buildPanelLine(width, [
|
|
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],
|
|
524
|
-
]));
|
|
525
|
-
}
|
|
526
|
-
if (task.correlationId || task.turnId) {
|
|
527
|
-
detailRows.push(buildPanelLine(width, [
|
|
528
|
-
[' Correlation: ', C.label],
|
|
529
|
-
[task.correlationId ?? 'n/a', C.dim],
|
|
530
|
-
[' Turn: ', C.label],
|
|
531
|
-
[task.turnId ?? 'n/a', C.dim],
|
|
532
|
-
]));
|
|
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)) {
|
|
553
|
-
detailRows.push(buildPanelLine(width, [[
|
|
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,
|
|
556
|
-
]]));
|
|
557
|
-
}
|
|
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
|
-
}
|
|
578
|
-
|
|
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));
|
|
582
|
-
|
|
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,
|
|
604
|
-
});
|
|
605
|
-
while (lines.length < height) lines.push(createEmptyLine(width));
|
|
606
|
-
return lines.slice(0, height);
|
|
607
|
-
}
|
|
608
|
-
}
|