@pellux/goodvibes-tui 0.28.0 → 0.29.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 +101 -0
- package/README.md +1 -1
- package/package.json +1 -1
- 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-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/keybindings.ts +48 -8
- package/src/input/panel-integration-actions.ts +107 -1
- package/src/main.ts +5 -1
- 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/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 +203 -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 +309 -128
- 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/conversation-overlays.ts +25 -11
- package/src/renderer/footer-tips.ts +41 -0
- package/src/renderer/fullscreen-primitives.ts +22 -16
- package/src/renderer/help-overlay.ts +91 -14
- package/src/renderer/hint-grammar.ts +52 -0
- package/src/renderer/layout.ts +7 -7
- package/src/renderer/modal-factory.ts +23 -15
- package/src/renderer/model-picker-overlay.ts +21 -7
- package/src/renderer/overlay-box.ts +16 -10
- package/src/renderer/process-indicator.ts +3 -1
- package/src/renderer/progress.ts +5 -4
- package/src/renderer/search-overlay.ts +27 -6
- package/src/renderer/session-picker-modal.ts +6 -4
- package/src/renderer/settings-modal.ts +70 -10
- package/src/renderer/shell-surface.ts +41 -15
- package/src/renderer/status-glyphs.ts +11 -9
- package/src/renderer/theme.ts +60 -3
- package/src/renderer/ui-factory.ts +41 -30
- package/src/renderer/ui-primitives.ts +23 -2
- package/src/runtime/bootstrap-shell.ts +35 -18
- package/src/runtime/diagnostics/panels/index.ts +0 -3
- package/src/shell/ui-openers.ts +14 -22
- package/src/utils/format-duration.ts +2 -2
- package/src/utils/splash-lines.ts +44 -3
- 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/runtime/diagnostics/panels/panel-resources.ts +0 -118
- package/src/runtime/diagnostics/panels/policy.ts +0 -177
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { Line } from '../types/grid.ts';
|
|
2
2
|
import { createEmptyLine, createStyledCell } from '../types/grid.ts';
|
|
3
3
|
import { getDisplayWidth } from '../utils/terminal-width.ts';
|
|
4
|
-
import {
|
|
4
|
+
import { resolveUiTones } from '../renderer/theme.ts';
|
|
5
|
+
|
|
6
|
+
// DEFAULT_PANEL_PALETTE is built from the mode-resolved chrome tones
|
|
7
|
+
// (resolveUiTones) rather than the static UI_TONES constant — WO-001 single
|
|
8
|
+
// read path. Mode is fixed to 'dark' until the terminal-bg-probe lands.
|
|
9
|
+
const UI_TONES = resolveUiTones('dark');
|
|
5
10
|
|
|
6
11
|
// ---------------------------------------------------------------------------
|
|
7
12
|
// Panel palette + core line primitives.
|
|
@@ -41,7 +46,7 @@ export const DEFAULT_PANEL_PALETTE: Readonly<Required<PanelPalette>> = {
|
|
|
41
46
|
good: UI_TONES.state.good,
|
|
42
47
|
warn: UI_TONES.state.warn,
|
|
43
48
|
bad: UI_TONES.state.bad,
|
|
44
|
-
empty:
|
|
49
|
+
empty: UI_TONES.fg.empty,
|
|
45
50
|
surfaceBg: UI_TONES.bg.surface,
|
|
46
51
|
sectionBg: UI_TONES.bg.section,
|
|
47
52
|
summaryBg: UI_TONES.bg.summary,
|
package/src/panels/polish.ts
CHANGED
|
@@ -447,16 +447,26 @@ export function resolveScrollablePanelSection(
|
|
|
447
447
|
afterSections: options.afterSections,
|
|
448
448
|
minRows: options.section.minRows ?? 1,
|
|
449
449
|
});
|
|
450
|
+
// When a window-summary row will render (list longer than the budget can
|
|
451
|
+
// show), it occupies one of the section's rows. Reserve that row up front so
|
|
452
|
+
// the summary is accounted INSIDE the budget; otherwise the appended summary
|
|
453
|
+
// pushes total output one line past the panel's exact-height contract and the
|
|
454
|
+
// tail line (typically the footer's keyboard-hints row) is silently dropped.
|
|
455
|
+
const summaryReserved = options.section.appendWindowSummary
|
|
456
|
+
&& options.section.scrollableLines.length > budget
|
|
457
|
+
? 1
|
|
458
|
+
: 0;
|
|
459
|
+
const windowBudget = Math.max(0, budget - summaryReserved);
|
|
450
460
|
const window = options.section.selectedIndex === undefined
|
|
451
461
|
? getVisibleWindow(
|
|
452
462
|
options.section.scrollableLines.length,
|
|
453
463
|
options.section.scrollOffset,
|
|
454
|
-
|
|
464
|
+
windowBudget,
|
|
455
465
|
)
|
|
456
466
|
: getTrackedVisibleWindow(
|
|
457
467
|
options.section.scrollableLines.length,
|
|
458
468
|
options.section.selectedIndex,
|
|
459
|
-
|
|
469
|
+
windowBudget,
|
|
460
470
|
options.section.scrollOffset,
|
|
461
471
|
options.section.guardRows ?? 1,
|
|
462
472
|
);
|
|
@@ -529,12 +539,21 @@ export function resolveStackedScrollableSections(
|
|
|
529
539
|
|
|
530
540
|
return options.sections.map((section, index) => {
|
|
531
541
|
const fixedLines = [...(section.fixedLines ?? [])];
|
|
542
|
+
// Same reservation as resolveScrollablePanelSection: a window-summary row
|
|
543
|
+
// occupies one of this section's allocated rows, so shrink the visible
|
|
544
|
+
// window rather than overflowing the stack's exact-height budget.
|
|
545
|
+
const budget = budgets[index]!;
|
|
546
|
+
const summaryReserved = section.appendWindowSummary
|
|
547
|
+
&& section.scrollableLines.length > budget
|
|
548
|
+
? 1
|
|
549
|
+
: 0;
|
|
550
|
+
const windowBudget = Math.max(0, budget - summaryReserved);
|
|
532
551
|
const window = section.selectedIndex === undefined
|
|
533
|
-
? getVisibleWindow(section.scrollableLines.length, section.scrollOffset,
|
|
552
|
+
? getVisibleWindow(section.scrollableLines.length, section.scrollOffset, windowBudget)
|
|
534
553
|
: getTrackedVisibleWindow(
|
|
535
554
|
section.scrollableLines.length,
|
|
536
555
|
section.selectedIndex,
|
|
537
|
-
|
|
556
|
+
windowBudget,
|
|
538
557
|
section.scrollOffset,
|
|
539
558
|
section.guardRows ?? 1,
|
|
540
559
|
);
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { ProjectPlanningQuestion } from '@pellux/goodvibes-sdk/platform/knowledge';
|
|
2
|
+
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Answer-action helpers for the Project Planning panel.
|
|
5
|
+
//
|
|
6
|
+
// Extracted from project-planning-panel.ts to keep that module under the
|
|
7
|
+
// architecture line-count cap. Leaf module (only the SDK question type);
|
|
8
|
+
// the panel is their public import site.
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
export interface PlanningAnswerAction {
|
|
12
|
+
readonly id: string;
|
|
13
|
+
readonly label: string;
|
|
14
|
+
readonly detail: string;
|
|
15
|
+
readonly answer: string;
|
|
16
|
+
readonly kind?: 'answer' | 'approve' | 'dismiss';
|
|
17
|
+
readonly disabled?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** SDK placeholder recommendations that carry no real signal — never surfaced as an answer action. */
|
|
21
|
+
export function isGenericRecommendation(value: string): boolean {
|
|
22
|
+
return /\bdefine the first-pass scope\b/i.test(value)
|
|
23
|
+
|| /\bcreate task records\b/i.test(value)
|
|
24
|
+
|| /\brecord concrete tests\b/i.test(value)
|
|
25
|
+
|| /\bseparate out-of-scope work\b/i.test(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function compactAnswerDetail(text: string): string {
|
|
29
|
+
const normalized = text.replace(/\s+/g, ' ').trim();
|
|
30
|
+
return normalized.length > 86 ? `${normalized.slice(0, 83)}...` : normalized;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Builds the answer-action list for the current question: keyword-matched
|
|
35
|
+
* canned suggestions first (de-duplicated by normalized answer text — a
|
|
36
|
+
* question that matches more than one keyword category, e.g. scope +
|
|
37
|
+
* recommendedAnswer echoing one of the scope answers verbatim, must not show
|
|
38
|
+
* the same suggested answer twice), then the fixed ask-narrower/custom/
|
|
39
|
+
* dismiss actions.
|
|
40
|
+
*/
|
|
41
|
+
export function buildAnswerActions(question: ProjectPlanningQuestion, draftAnswer: string): PlanningAnswerAction[] {
|
|
42
|
+
const canned: PlanningAnswerAction[] = [];
|
|
43
|
+
const prompt = question.prompt.toLowerCase();
|
|
44
|
+
const isScopeQuestion = prompt.includes('scope') || prompt.includes('in or out');
|
|
45
|
+
const isTaskQuestion = prompt.includes('task') || prompt.includes('dependency') || prompt.includes('work breakdown');
|
|
46
|
+
const isVerificationQuestion = prompt.includes('verification') || prompt.includes('test') || prompt.includes('prove');
|
|
47
|
+
const isApprovalQuestion = prompt.includes('approved') || prompt.includes('approve') || prompt.includes('execution');
|
|
48
|
+
if (isApprovalQuestion) {
|
|
49
|
+
canned.push({
|
|
50
|
+
id: 'approve-execution',
|
|
51
|
+
label: 'Approve execution',
|
|
52
|
+
detail: 'Mark this plan approved so execution may proceed.',
|
|
53
|
+
answer: 'Approve this planning state for execution.',
|
|
54
|
+
kind: 'approve',
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (isScopeQuestion) {
|
|
58
|
+
canned.push({
|
|
59
|
+
id: 'scope-focused-first-pass',
|
|
60
|
+
label: 'Use focused first-pass scope',
|
|
61
|
+
detail: 'Fill a concrete end-to-end scope for this goal and keep unrelated work out.',
|
|
62
|
+
answer: 'Use a focused first-pass scope for this goal.',
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
if (isTaskQuestion) {
|
|
66
|
+
canned.push({
|
|
67
|
+
id: 'tasks-default-breakdown',
|
|
68
|
+
label: 'Create default task breakdown',
|
|
69
|
+
detail: 'Create inspect, implement, wire, and verify tasks with dependencies.',
|
|
70
|
+
answer: 'Create the default task breakdown for this goal.',
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
if (isVerificationQuestion) {
|
|
74
|
+
canned.push({
|
|
75
|
+
id: 'verification-default-gates',
|
|
76
|
+
label: 'Use standard verification gates',
|
|
77
|
+
detail: 'Require focused regression coverage, typecheck/build validation, and a runtime smoke where feasible.',
|
|
78
|
+
answer: 'Use standard verification gates for this goal.',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (question.recommendedAnswer?.trim() && !isGenericRecommendation(question.recommendedAnswer)) {
|
|
82
|
+
canned.push({
|
|
83
|
+
id: 'recommended',
|
|
84
|
+
label: 'Use recommended answer',
|
|
85
|
+
detail: compactAnswerDetail(question.recommendedAnswer),
|
|
86
|
+
answer: question.recommendedAnswer,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
if (isScopeQuestion) {
|
|
90
|
+
canned.push({
|
|
91
|
+
id: 'scope-end-to-end',
|
|
92
|
+
label: 'End-to-end required scope',
|
|
93
|
+
detail: 'Let the plan include every component needed to make this work, but avoid unrelated cleanup.',
|
|
94
|
+
answer: 'Scope is everything required to make the requested outcome work end-to-end. Include TUI, daemon composition, configuration, docs, and tests if they are required. Do not include unrelated cleanup or broad refactors unless they are necessary for this task.',
|
|
95
|
+
});
|
|
96
|
+
canned.push({
|
|
97
|
+
id: 'scope-tui-first',
|
|
98
|
+
label: 'TUI-first scope',
|
|
99
|
+
detail: 'Fix TUI behavior here; report SDK blockers instead of patching around SDK-owned bugs.',
|
|
100
|
+
answer: 'Scope is TUI-owned behavior first. If a blocker is SDK-owned, report the exact SDK contract/runtime issue instead of patching around it in the TUI. Include daemon composition only where the TUI owns the wiring.',
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const seenAnswers = new Set<string>();
|
|
105
|
+
const actions: PlanningAnswerAction[] = [];
|
|
106
|
+
for (const action of canned) {
|
|
107
|
+
const key = action.answer.trim().toLowerCase().replace(/\s+/g, ' ');
|
|
108
|
+
if (key.length > 0 && seenAnswers.has(key)) continue;
|
|
109
|
+
if (key.length > 0) seenAnswers.add(key);
|
|
110
|
+
actions.push(action);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
actions.push({
|
|
114
|
+
id: 'ask-narrower',
|
|
115
|
+
label: 'I am not sure yet',
|
|
116
|
+
detail: 'Break this into smaller concrete choices with examples and a recommended default.',
|
|
117
|
+
answer: `I do not know enough to answer "${question.prompt}" as asked. Break it into smaller concrete questions with 2-4 specific choices, explain the tradeoffs, recommend a default, and ask me the first one.`,
|
|
118
|
+
});
|
|
119
|
+
actions.push({
|
|
120
|
+
id: 'custom',
|
|
121
|
+
label: 'Submit typed answer',
|
|
122
|
+
detail: draftAnswer ? compactAnswerDetail(draftAnswer) : 'Type an answer first; this row becomes the custom answer.',
|
|
123
|
+
answer: draftAnswer.trim(),
|
|
124
|
+
disabled: !draftAnswer.trim(),
|
|
125
|
+
});
|
|
126
|
+
actions.push({
|
|
127
|
+
id: 'dismiss-planning',
|
|
128
|
+
label: 'Close planning and continue without it',
|
|
129
|
+
detail: 'Pause project planning for this workspace. Normal chat continues; /plan can reopen it later.',
|
|
130
|
+
answer: 'Pause project planning for this workspace and continue without the planning panel.',
|
|
131
|
+
kind: 'dismiss',
|
|
132
|
+
});
|
|
133
|
+
return actions;
|
|
134
|
+
}
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
type PanelWorkspaceSection,
|
|
25
25
|
} from './polish.ts';
|
|
26
26
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
27
|
+
import { buildAnswerActions, isGenericRecommendation, type PlanningAnswerAction } from './project-planning-answer-actions.ts';
|
|
27
28
|
|
|
28
29
|
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
29
30
|
planning: '#38bdf8',
|
|
@@ -48,15 +49,6 @@ export interface ProjectPlanningPanelOptions {
|
|
|
48
49
|
readonly dismissPlanning?: () => void;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
interface PlanningAnswerAction {
|
|
52
|
-
readonly id: string;
|
|
53
|
-
readonly label: string;
|
|
54
|
-
readonly detail: string;
|
|
55
|
-
readonly answer: string;
|
|
56
|
-
readonly kind?: 'answer' | 'approve' | 'dismiss';
|
|
57
|
-
readonly disabled?: boolean;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
52
|
interface RenderedPlanningSection extends PanelWorkspaceSection {
|
|
61
53
|
readonly selectedLineIndex?: number;
|
|
62
54
|
}
|
|
@@ -111,9 +103,32 @@ export class ProjectPlanningPanel extends BasePanel {
|
|
|
111
103
|
}
|
|
112
104
|
// confirmResult === 'inactive': proceed with normal dispatch.
|
|
113
105
|
|
|
106
|
+
// Ctrl+R (refresh) / Ctrl+A (approve) are alternate bindings for 'r'/'a'
|
|
107
|
+
// that stay reachable while a question is active, where plain 'r'/'a'
|
|
108
|
+
// are swallowed into the draft-answer text below instead. They are
|
|
109
|
+
// checked before the question gate so they work in both modes.
|
|
110
|
+
//
|
|
111
|
+
// NOTE: the input-routing layer (src/input/handler-feed-routes.ts, owned
|
|
112
|
+
// by WO-150 in this wave) does not yet forward ctrl-modified keys to the
|
|
113
|
+
// active panel — it currently intercepts all `token.ctrl` input before
|
|
114
|
+
// panel.handleInput is ever called, and a bare Ctrl+R is already bound
|
|
115
|
+
// globally to 'history-search'. This handler recognizes the logical key
|
|
116
|
+
// strings 'ctrl+r'/'ctrl+a' so the panel-side behavior is correct and
|
|
117
|
+
// testable now; wiring a real Ctrl+R/Ctrl+A keypress through to these
|
|
118
|
+
// strings is an input-handler change outside this work order's file
|
|
119
|
+
// scope.
|
|
120
|
+
if (key === 'ctrl+r') {
|
|
121
|
+
this.refresh(true);
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
if (key === 'ctrl+a') {
|
|
125
|
+
this.approveExecution();
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
114
129
|
const question = this.getCurrentQuestion();
|
|
115
130
|
if (question) {
|
|
116
|
-
const actions = this.
|
|
131
|
+
const actions = buildAnswerActions(question, this.draftAnswer);
|
|
117
132
|
this.selectedActionIndex = this.clampActionIndex(actions.length);
|
|
118
133
|
if (key === 'up') {
|
|
119
134
|
this.selectedActionIndex = Math.max(0, this.selectedActionIndex - 1);
|
|
@@ -204,9 +219,15 @@ export class ProjectPlanningPanel extends BasePanel {
|
|
|
204
219
|
{ label: 'space', value: status?.knowledgeSpaceId ?? `project:${this.projectId}`, valueColor: C.value },
|
|
205
220
|
{ label: 'mode', value: 'TUI-owned passive backing store', valueColor: C.info },
|
|
206
221
|
], C),
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
222
|
+
// Live artifact counts from the SDK status route (same counts /plan
|
|
223
|
+
// prints at planning-runtime.ts:90) instead of static filler prose.
|
|
224
|
+
status
|
|
225
|
+
? buildKeyValueLine(width, [
|
|
226
|
+
{ label: 'states', value: String(status.counts.states), valueColor: status.counts.states > 0 ? C.value : C.dim },
|
|
227
|
+
{ label: 'decisions', value: String(status.counts.decisions), valueColor: status.counts.decisions > 0 ? C.value : C.dim },
|
|
228
|
+
{ label: 'language', value: String(status.counts.languageArtifacts), valueColor: status.counts.languageArtifacts > 0 ? C.value : C.dim },
|
|
229
|
+
], C)
|
|
230
|
+
: buildPanelLine(width, [[' Artifact counts unavailable — refreshing…', C.dim]]),
|
|
210
231
|
],
|
|
211
232
|
});
|
|
212
233
|
|
|
@@ -227,6 +248,7 @@ export class ProjectPlanningPanel extends BasePanel {
|
|
|
227
248
|
if (question) sections.push(this.buildQuestionSection(width, question));
|
|
228
249
|
sections.push(this.buildGapsSection(width, evaluation));
|
|
229
250
|
sections.push(this.buildTasksSection(width, state));
|
|
251
|
+
sections.push(this.buildAnsweredHistorySection(width, state));
|
|
230
252
|
sections.push(this.buildDecisionsSection(width, state, decisions));
|
|
231
253
|
sections.push(this.buildLanguageSection(width, language));
|
|
232
254
|
}
|
|
@@ -278,6 +300,12 @@ export class ProjectPlanningPanel extends BasePanel {
|
|
|
278
300
|
['Enter', C.info],
|
|
279
301
|
[' submit Esc prompt focus Ctrl+X close panel', C.dim],
|
|
280
302
|
]),
|
|
303
|
+
buildPanelLine(width, [
|
|
304
|
+
[' Ctrl+R', C.info],
|
|
305
|
+
[' refresh ', C.dim],
|
|
306
|
+
['Ctrl+A', C.info],
|
|
307
|
+
[' approve (r/a type into the draft while a question is active)', C.dim],
|
|
308
|
+
]),
|
|
281
309
|
];
|
|
282
310
|
}
|
|
283
311
|
return [
|
|
@@ -311,7 +339,7 @@ export class ProjectPlanningPanel extends BasePanel {
|
|
|
311
339
|
}
|
|
312
340
|
|
|
313
341
|
private buildQuestionSection(width: number, question: ProjectPlanningQuestion): RenderedPlanningSection {
|
|
314
|
-
const actions = this.
|
|
342
|
+
const actions = buildAnswerActions(question, this.draftAnswer);
|
|
315
343
|
this.selectedActionIndex = this.clampActionIndex(actions.length);
|
|
316
344
|
// When a clear-draft confirmation is pending, show the confirm prompt
|
|
317
345
|
// inline above the draft line instead of the normal content.
|
|
@@ -329,7 +357,7 @@ export class ProjectPlanningPanel extends BasePanel {
|
|
|
329
357
|
if (question.whyItMatters) {
|
|
330
358
|
lines.push(...buildBodyText(width, `Why this matters: ${question.whyItMatters}`, C, C.dim));
|
|
331
359
|
}
|
|
332
|
-
if (question.recommendedAnswer && !
|
|
360
|
+
if (question.recommendedAnswer && !isGenericRecommendation(question.recommendedAnswer)) {
|
|
333
361
|
lines.push(...buildBodyText(width, `Recommendation: ${question.recommendedAnswer}`, C, C.good));
|
|
334
362
|
}
|
|
335
363
|
lines.push(...buildBodyText(
|
|
@@ -406,7 +434,7 @@ export class ProjectPlanningPanel extends BasePanel {
|
|
|
406
434
|
if (evaluation.nextQuestion.whyItMatters) {
|
|
407
435
|
lines.push(...buildBodyText(width, `Why it matters: ${evaluation.nextQuestion.whyItMatters}`, C, C.dim));
|
|
408
436
|
}
|
|
409
|
-
if (evaluation.nextQuestion.recommendedAnswer && !
|
|
437
|
+
if (evaluation.nextQuestion.recommendedAnswer && !isGenericRecommendation(evaluation.nextQuestion.recommendedAnswer)) {
|
|
410
438
|
lines.push(...buildBodyText(width, `Recommended answer: ${evaluation.nextQuestion.recommendedAnswer}`, C, C.good));
|
|
411
439
|
}
|
|
412
440
|
}
|
|
@@ -469,6 +497,24 @@ export class ProjectPlanningPanel extends BasePanel {
|
|
|
469
497
|
return { title: 'Task Graph', lines };
|
|
470
498
|
}
|
|
471
499
|
|
|
500
|
+
private buildAnsweredHistorySection(width: number, state: ProjectPlanningState): PanelWorkspaceSection {
|
|
501
|
+
const answered = state.answeredQuestions;
|
|
502
|
+
if (answered.length === 0) {
|
|
503
|
+
return {
|
|
504
|
+
title: 'Answered Questions',
|
|
505
|
+
lines: [buildPanelLine(width, [[' No questions answered yet.', C.dim]])],
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
const ordered = [...answered].sort((a, b) => (b.answeredAt ?? 0) - (a.answeredAt ?? 0));
|
|
509
|
+
return {
|
|
510
|
+
title: 'Answered Questions',
|
|
511
|
+
lines: ordered.slice(0, 12).flatMap((entry) => [
|
|
512
|
+
...buildBodyText(width, `Q: ${entry.prompt}`, C, C.value),
|
|
513
|
+
...buildBodyText(width, `A: ${entry.answer?.trim() || '(no answer recorded)'}`, C, entry.answer?.trim() ? C.good : C.dim),
|
|
514
|
+
]),
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
472
518
|
private buildDecisionsSection(
|
|
473
519
|
width: number,
|
|
474
520
|
state: ProjectPlanningState,
|
|
@@ -550,98 +596,6 @@ export class ProjectPlanningPanel extends BasePanel {
|
|
|
550
596
|
return open ?? this.snapshot?.evaluation?.nextQuestion ?? null;
|
|
551
597
|
}
|
|
552
598
|
|
|
553
|
-
private getAnswerActions(question: ProjectPlanningQuestion): PlanningAnswerAction[] {
|
|
554
|
-
const actions: PlanningAnswerAction[] = [];
|
|
555
|
-
const prompt = question.prompt.toLowerCase();
|
|
556
|
-
const isScopeQuestion = prompt.includes('scope') || prompt.includes('in or out');
|
|
557
|
-
const isTaskQuestion = prompt.includes('task') || prompt.includes('dependency') || prompt.includes('work breakdown');
|
|
558
|
-
const isVerificationQuestion = prompt.includes('verification') || prompt.includes('test') || prompt.includes('prove');
|
|
559
|
-
const isApprovalQuestion = prompt.includes('approved') || prompt.includes('approve') || prompt.includes('execution');
|
|
560
|
-
if (isApprovalQuestion) {
|
|
561
|
-
actions.push({
|
|
562
|
-
id: 'approve-execution',
|
|
563
|
-
label: 'Approve execution',
|
|
564
|
-
detail: 'Mark this plan approved so execution may proceed.',
|
|
565
|
-
answer: 'Approve this planning state for execution.',
|
|
566
|
-
kind: 'approve',
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
if (isScopeQuestion) {
|
|
570
|
-
actions.push({
|
|
571
|
-
id: 'scope-focused-first-pass',
|
|
572
|
-
label: 'Use focused first-pass scope',
|
|
573
|
-
detail: 'Fill a concrete end-to-end scope for this goal and keep unrelated work out.',
|
|
574
|
-
answer: 'Use a focused first-pass scope for this goal.',
|
|
575
|
-
});
|
|
576
|
-
}
|
|
577
|
-
if (isTaskQuestion) {
|
|
578
|
-
actions.push({
|
|
579
|
-
id: 'tasks-default-breakdown',
|
|
580
|
-
label: 'Create default task breakdown',
|
|
581
|
-
detail: 'Create inspect, implement, wire, and verify tasks with dependencies.',
|
|
582
|
-
answer: 'Create the default task breakdown for this goal.',
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
|
-
if (isVerificationQuestion) {
|
|
586
|
-
actions.push({
|
|
587
|
-
id: 'verification-default-gates',
|
|
588
|
-
label: 'Use standard verification gates',
|
|
589
|
-
detail: 'Require focused regression coverage, typecheck/build validation, and a runtime smoke where feasible.',
|
|
590
|
-
answer: 'Use standard verification gates for this goal.',
|
|
591
|
-
});
|
|
592
|
-
}
|
|
593
|
-
if (question.recommendedAnswer?.trim() && !this.isGenericRecommendation(question.recommendedAnswer)) {
|
|
594
|
-
actions.push({
|
|
595
|
-
id: 'recommended',
|
|
596
|
-
label: 'Use recommended answer',
|
|
597
|
-
detail: this.compact(question.recommendedAnswer),
|
|
598
|
-
answer: question.recommendedAnswer,
|
|
599
|
-
});
|
|
600
|
-
}
|
|
601
|
-
if (isScopeQuestion) {
|
|
602
|
-
actions.push({
|
|
603
|
-
id: 'scope-end-to-end',
|
|
604
|
-
label: 'End-to-end required scope',
|
|
605
|
-
detail: 'Let the plan include every component needed to make this work, but avoid unrelated cleanup.',
|
|
606
|
-
answer: 'Scope is everything required to make the requested outcome work end-to-end. Include TUI, daemon composition, configuration, docs, and tests if they are required. Do not include unrelated cleanup or broad refactors unless they are necessary for this task.',
|
|
607
|
-
});
|
|
608
|
-
actions.push({
|
|
609
|
-
id: 'scope-tui-first',
|
|
610
|
-
label: 'TUI-first scope',
|
|
611
|
-
detail: 'Fix TUI behavior here; report SDK blockers instead of patching around SDK-owned bugs.',
|
|
612
|
-
answer: 'Scope is TUI-owned behavior first. If a blocker is SDK-owned, report the exact SDK contract/runtime issue instead of patching around it in the TUI. Include daemon composition only where the TUI owns the wiring.',
|
|
613
|
-
});
|
|
614
|
-
}
|
|
615
|
-
actions.push({
|
|
616
|
-
id: 'ask-narrower',
|
|
617
|
-
label: 'I am not sure yet',
|
|
618
|
-
detail: 'Break this into smaller concrete choices with examples and a recommended default.',
|
|
619
|
-
answer: `I do not know enough to answer "${question.prompt}" as asked. Break it into smaller concrete questions with 2-4 specific choices, explain the tradeoffs, recommend a default, and ask me the first one.`,
|
|
620
|
-
});
|
|
621
|
-
actions.push({
|
|
622
|
-
id: 'custom',
|
|
623
|
-
label: 'Submit typed answer',
|
|
624
|
-
detail: this.draftAnswer ? this.compact(this.draftAnswer) : 'Type an answer first; this row becomes the custom answer.',
|
|
625
|
-
answer: this.draftAnswer.trim(),
|
|
626
|
-
disabled: !this.draftAnswer.trim(),
|
|
627
|
-
});
|
|
628
|
-
actions.push({
|
|
629
|
-
id: 'dismiss-planning',
|
|
630
|
-
label: 'Close planning and continue without it',
|
|
631
|
-
detail: 'Pause project planning for this workspace. Normal chat continues; /plan can reopen it later.',
|
|
632
|
-
answer: 'Pause project planning for this workspace and continue without the planning panel.',
|
|
633
|
-
kind: 'dismiss',
|
|
634
|
-
});
|
|
635
|
-
return actions;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
private isGenericRecommendation(value: string): boolean {
|
|
639
|
-
return /\bdefine the first-pass scope\b/i.test(value)
|
|
640
|
-
|| /\bcreate task records\b/i.test(value)
|
|
641
|
-
|| /\brecord concrete tests\b/i.test(value)
|
|
642
|
-
|| /\bseparate out-of-scope work\b/i.test(value);
|
|
643
|
-
}
|
|
644
|
-
|
|
645
599
|
private submitSelectedAction(question: ProjectPlanningQuestion, actions: readonly PlanningAnswerAction[]): void {
|
|
646
600
|
const action = actions[this.clampActionIndex(actions.length)];
|
|
647
601
|
if (!action || action.disabled || !action.answer.trim()) {
|
|
@@ -736,11 +690,6 @@ export class ProjectPlanningPanel extends BasePanel {
|
|
|
736
690
|
return key.length === 1 && key >= ' ';
|
|
737
691
|
}
|
|
738
692
|
|
|
739
|
-
private compact(text: string): string {
|
|
740
|
-
const normalized = text.replace(/\s+/g, ' ').trim();
|
|
741
|
-
return normalized.length > 86 ? `${normalized.slice(0, 83)}...` : normalized;
|
|
742
|
-
}
|
|
743
|
-
|
|
744
693
|
private approveExecution(): void {
|
|
745
694
|
const state = this.snapshot?.state;
|
|
746
695
|
if (!state) {
|