@pellux/goodvibes-tui 0.19.81 → 0.19.82

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 CHANGED
@@ -4,6 +4,11 @@ All notable changes to GoodVibes TUI.
4
4
 
5
5
  ---
6
6
 
7
+ ## [0.19.82] — 2026-05-09
8
+
9
+ ### Changes
10
+ - e13b0885 Update SDK and repair planning flow
11
+
7
12
  ## [0.19.81] — 2026-05-08
8
13
 
9
14
  ### Changes
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![CI](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml/badge.svg)](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
- [![Version](https://img.shields.io/badge/version-0.19.81-blue.svg)](https://github.com/mgd34msu/goodvibes-tui)
5
+ [![Version](https://img.shields.io/badge/version-0.19.82-blue.svg)](https://github.com/mgd34msu/goodvibes-tui)
6
6
 
7
7
  A terminal-native AI coding, operations, automation, knowledge, and integration console with a typed runtime, omnichannel surfaces, structured memory/knowledge, and a raw ANSI renderer.
8
8
 
@@ -3,7 +3,7 @@
3
3
  "product": {
4
4
  "id": "goodvibes",
5
5
  "surface": "operator",
6
- "version": "0.33.18"
6
+ "version": "0.33.19"
7
7
  },
8
8
  "auth": {
9
9
  "modes": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-tui",
3
- "version": "0.19.81",
3
+ "version": "0.19.82",
4
4
  "description": "Terminal-native GoodVibes product for coding, operations, automation, knowledge, channels, and daemon-backed control-plane workflows.",
5
5
  "type": "module",
6
6
  "main": "src/main.ts",
@@ -97,7 +97,7 @@
97
97
  "@anthropic-ai/vertex-sdk": "^0.16.0",
98
98
  "@ast-grep/napi": "^0.42.0",
99
99
  "@aws/bedrock-token-generator": "^1.1.0",
100
- "@pellux/goodvibes-sdk": "0.33.18",
100
+ "@pellux/goodvibes-sdk": "0.33.19",
101
101
  "bash-language-server": "^5.6.0",
102
102
  "fuse.js": "^7.1.0",
103
103
  "graphql": "^16.13.2",
@@ -1,6 +1,52 @@
1
+ import type {
2
+ ProjectPlanningEvaluation,
3
+ ProjectPlanningQuestion,
4
+ ProjectPlanningService,
5
+ ProjectPlanningState,
6
+ } from '@pellux/goodvibes-sdk/platform/knowledge';
1
7
  import type { CommandRegistry } from '../command-registry.ts';
2
8
  import { requirePlanManager, requireSessionLineageTracker } from './runtime-services.ts';
3
9
 
10
+ function recordNextQuestion(
11
+ state: Partial<ProjectPlanningState>,
12
+ question: ProjectPlanningQuestion | undefined,
13
+ ): Partial<ProjectPlanningState> {
14
+ if (!question) return state;
15
+ const answered = new Set((state.answeredQuestions ?? []).map((entry) => entry.id));
16
+ if (answered.has(question.id)) return state;
17
+ const openQuestions = [...(state.openQuestions ?? [])];
18
+ const existingIndex = openQuestions.findIndex((entry) => entry.id === question.id);
19
+ const normalized = { ...question, status: question.status ?? 'open' } satisfies ProjectPlanningQuestion;
20
+ if (existingIndex >= 0) openQuestions[existingIndex] = normalized;
21
+ else openQuestions.unshift(normalized);
22
+ return { ...state, openQuestions };
23
+ }
24
+
25
+ async function persistEvaluatedNextQuestion(
26
+ service: ProjectPlanningService,
27
+ projectId: string,
28
+ state: ProjectPlanningState,
29
+ evaluation: ProjectPlanningEvaluation,
30
+ ): Promise<{ state: ProjectPlanningState; evaluation: ProjectPlanningEvaluation }> {
31
+ if (!evaluation.nextQuestion) return { state, evaluation };
32
+ if (state.openQuestions.some((question) => question.id === evaluation.nextQuestion?.id)) {
33
+ return { state, evaluation };
34
+ }
35
+ const withQuestion = recordNextQuestion(evaluation.state ?? state, evaluation.nextQuestion);
36
+ const saved = await service.upsertState({ projectId, state: withQuestion });
37
+ const nextState = saved.state ?? state;
38
+ const nextEvaluation = await service.evaluate({ projectId, state: nextState });
39
+ return { state: nextState, evaluation: nextEvaluation };
40
+ }
41
+
42
+ function formatNextQuestion(question: ProjectPlanningQuestion | undefined): string {
43
+ if (!question) return 'No next question recorded.';
44
+ const lines = [`Next question: ${question.prompt}`];
45
+ if (question.recommendedAnswer) lines.push(`Recommended answer: ${question.recommendedAnswer}`);
46
+ lines.push('Answer in the prompt, or focus the Planning panel to choose/type an answer.');
47
+ return lines.join('\n');
48
+ }
49
+
4
50
  export function registerPlanningRuntimeCommands(registry: CommandRegistry): void {
5
51
  registry.register({
6
52
  name: 'plan',
@@ -25,17 +71,24 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
25
71
 
26
72
  if (args.length === 0) {
27
73
  if (projectPlanningService && projectId) {
28
- const [status, evaluation] = await Promise.all([
74
+ const [status, stateResult] = await Promise.all([
29
75
  projectPlanningService.status({ projectId }),
30
- projectPlanningService.evaluate({ projectId }),
76
+ projectPlanningService.getState({ projectId }),
31
77
  ]);
78
+ const initialEvaluation = await projectPlanningService.evaluate({
79
+ projectId,
80
+ ...(stateResult.state ? { state: stateResult.state } : {}),
81
+ });
82
+ const { evaluation } = stateResult.state
83
+ ? await persistEvaluatedNextQuestion(projectPlanningService, projectId, stateResult.state, initialEvaluation)
84
+ : { evaluation: initialEvaluation };
32
85
  openProjectPlanningPanel();
33
86
  ctx.print(
34
87
  `Project planning: ${evaluation.readiness}\n` +
35
88
  `Project: ${status.projectId}\n` +
36
89
  `Knowledge space: ${status.knowledgeSpaceId}\n` +
37
90
  `Artifacts: ${status.counts.states} state, ${status.counts.decisions} decisions, ${status.counts.languageArtifacts} language\n` +
38
- (evaluation.nextQuestion ? `Next question: ${evaluation.nextQuestion.prompt}` : 'No next question recorded.'),
91
+ formatNextQuestion(evaluation.nextQuestion),
39
92
  );
40
93
  return;
41
94
  }
@@ -132,14 +185,20 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
132
185
  },
133
186
  },
134
187
  });
135
- const evaluation = await projectPlanningService.evaluate({ projectId });
188
+ const initialEvaluation = await projectPlanningService.evaluate({
189
+ projectId,
190
+ ...(result.state ? { state: result.state } : {}),
191
+ });
192
+ const { state, evaluation } = result.state
193
+ ? await persistEvaluatedNextQuestion(projectPlanningService, projectId, result.state, initialEvaluation)
194
+ : { state: result.state, evaluation: initialEvaluation };
136
195
  sessionLineageTracker.setOriginalTask(taskDescription.slice(0, 200));
137
196
  openProjectPlanningPanel();
138
197
 
139
198
  ctx.print(
140
- `Project planning seeded: "${result.state?.goal ?? taskDescription}"\n` +
199
+ `Project planning seeded: "${state?.goal ?? taskDescription}"\n` +
141
200
  `Readiness: ${evaluation.readiness}\n` +
142
- (evaluation.nextQuestion ? `Next question: ${evaluation.nextQuestion.prompt}` : 'No next question recorded.'),
201
+ formatNextQuestion(evaluation.nextQuestion),
143
202
  );
144
203
  },
145
204
  });
@@ -90,6 +90,7 @@ export function registerAgentPanels(manager: PanelManager, deps: ResolvedBuiltin
90
90
  service: deps.projectPlanningService,
91
91
  projectId: deps.projectPlanningProjectId,
92
92
  requestRender: deps.requestRender,
93
+ submitAnswer: deps.submitPlanningAnswer,
93
94
  }),
94
95
  });
95
96
 
@@ -55,6 +55,8 @@ export interface BuiltinPanelDeps {
55
55
  resumeSession?: (sessionId: string) => void;
56
56
  /** Request a shell repaint directly rather than routing through a retired event path. */
57
57
  requestRender?: () => void;
58
+ /** Submit a Planning panel answer through the normal TUI chat/planning coordinator path. */
59
+ submitPlanningAnswer?: (answer: string) => void;
58
60
  /** ForensicsRegistry for the Forensics panel. */
59
61
  forensicsRegistry?: import('@/runtime/index.ts').ForensicsRegistry;
60
62
  /** EvalRegistry for the Eval panel. */
@@ -553,7 +553,12 @@ export class PanelManager {
553
553
  private _activateByIdInPane(panelId: string, which: 'top' | 'bottom'): void {
554
554
  const p = this._getPane(which);
555
555
  const index = p.panels.findIndex(panel => panel.id === panelId);
556
- if (index >= 0 && index !== p.activeIndex) {
556
+ if (index < 0) return;
557
+ if (index === p.activeIndex) {
558
+ p.panels[index]?.onActivate();
559
+ return;
560
+ }
561
+ if (index !== p.activeIndex) {
557
562
  const oldPanel = p.panels[p.activeIndex];
558
563
  if (oldPanel) oldPanel.onDeactivate();
559
564
  p.activeIndex = index;
@@ -2,6 +2,7 @@ import type {
2
2
  ProjectPlanningDecision,
3
3
  ProjectPlanningEvaluation,
4
4
  ProjectPlanningLanguageArtifact,
5
+ ProjectPlanningQuestion,
5
6
  ProjectPlanningService,
6
7
  ProjectPlanningState,
7
8
  ProjectPlanningStatus,
@@ -13,6 +14,7 @@ import {
13
14
  buildEmptyState,
14
15
  buildKeyValueLine,
15
16
  buildPanelLine,
17
+ buildPanelListRow,
16
18
  buildPanelWorkspace,
17
19
  DEFAULT_PANEL_PALETTE,
18
20
  extendPalette,
@@ -39,21 +41,35 @@ export interface ProjectPlanningPanelOptions {
39
41
  readonly service: ProjectPlanningService;
40
42
  readonly projectId: string;
41
43
  readonly requestRender?: () => void;
44
+ readonly submitAnswer?: (answer: string) => void;
45
+ }
46
+
47
+ interface PlanningAnswerAction {
48
+ readonly id: string;
49
+ readonly label: string;
50
+ readonly detail: string;
51
+ readonly answer: string;
52
+ readonly kind?: 'answer' | 'approve';
53
+ readonly disabled?: boolean;
42
54
  }
43
55
 
44
56
  export class ProjectPlanningPanel extends BasePanel {
45
57
  private readonly service: ProjectPlanningService;
46
58
  private readonly projectId: string;
47
59
  private readonly requestRender: () => void;
60
+ private readonly submitAnswer: ((answer: string) => void) | undefined;
48
61
  private snapshot: ProjectPlanningPanelSnapshot | null = null;
49
62
  private loading = false;
50
63
  private scrollOffset = 0;
64
+ private selectedActionIndex = 0;
65
+ private draftAnswer = '';
51
66
 
52
67
  public constructor(options: ProjectPlanningPanelOptions) {
53
68
  super('project-planning', 'Planning', 'P', 'agent');
54
69
  this.service = options.service;
55
70
  this.projectId = options.projectId;
56
71
  this.requestRender = options.requestRender ?? (() => {});
72
+ this.submitAnswer = options.submitAnswer;
57
73
  }
58
74
 
59
75
  public override onActivate(): void {
@@ -62,6 +78,58 @@ export class ProjectPlanningPanel extends BasePanel {
62
78
  }
63
79
 
64
80
  public handleInput(key: string): boolean {
81
+ if (this.lastError !== null) this.clearError();
82
+ const question = this.getCurrentQuestion();
83
+ if (question) {
84
+ const actions = this.getAnswerActions(question);
85
+ this.selectedActionIndex = this.clampActionIndex(actions.length);
86
+ if (key === 'up') {
87
+ this.selectedActionIndex = Math.max(0, this.selectedActionIndex - 1);
88
+ this.markDirty();
89
+ return true;
90
+ }
91
+ if (key === 'down') {
92
+ this.selectedActionIndex = Math.min(Math.max(0, actions.length - 1), this.selectedActionIndex + 1);
93
+ this.markDirty();
94
+ return true;
95
+ }
96
+ if (key === 'enter' || key === 'return') {
97
+ this.submitSelectedAction(question, actions);
98
+ return true;
99
+ }
100
+ if (key === 'backspace') {
101
+ this.draftAnswer = this.draftAnswer.slice(0, -1);
102
+ this.markDirty();
103
+ return true;
104
+ }
105
+ if (key === 'delete') {
106
+ this.draftAnswer = '';
107
+ this.markDirty();
108
+ return true;
109
+ }
110
+ if (key === 'space') {
111
+ this.draftAnswer += ' ';
112
+ this.markDirty();
113
+ return true;
114
+ }
115
+ if (key === 'pageup') {
116
+ this.scrollOffset = Math.max(0, this.scrollOffset - 6);
117
+ this.markDirty();
118
+ return true;
119
+ }
120
+ if (key === 'pagedown') {
121
+ this.scrollOffset += 6;
122
+ this.markDirty();
123
+ return true;
124
+ }
125
+ if (this.isPrintableKey(key)) {
126
+ this.draftAnswer += key;
127
+ this.markDirty();
128
+ return true;
129
+ }
130
+ return false;
131
+ }
132
+
65
133
  if (key === 'r' || key === 'R') {
66
134
  this.refresh(true);
67
135
  return true;
@@ -121,6 +189,8 @@ export class ProjectPlanningPanel extends BasePanel {
121
189
  });
122
190
  } else {
123
191
  sections.push(this.buildStateSection(width, state, evaluation));
192
+ const question = this.getCurrentQuestion();
193
+ if (question) sections.push(this.buildQuestionSection(width, question));
124
194
  sections.push(this.buildGapsSection(width, evaluation));
125
195
  sections.push(this.buildTasksSection(width, state));
126
196
  sections.push(this.buildDecisionsSection(width, state, decisions));
@@ -161,6 +231,21 @@ export class ProjectPlanningPanel extends BasePanel {
161
231
  }
162
232
 
163
233
  private footerLines(width: number): Line[] {
234
+ const hasQuestion = this.getCurrentQuestion() !== null;
235
+ if (hasQuestion) {
236
+ return [
237
+ buildPanelLine(width, [
238
+ [' Up/Down', C.info],
239
+ [' choose answer ', C.dim],
240
+ ['type', C.info],
241
+ [' draft ', C.dim],
242
+ ['Backspace/Delete', C.info],
243
+ [' edit ', C.dim],
244
+ ['Enter', C.info],
245
+ [' submit Esc close panel focus', C.dim],
246
+ ]),
247
+ ];
248
+ }
164
249
  return [
165
250
  buildPanelLine(width, [
166
251
  [' Up/Down', C.info],
@@ -173,6 +258,41 @@ export class ProjectPlanningPanel extends BasePanel {
173
258
  ];
174
259
  }
175
260
 
261
+ private buildQuestionSection(width: number, question: ProjectPlanningQuestion): PanelWorkspaceSection {
262
+ const actions = this.getAnswerActions(question);
263
+ this.selectedActionIndex = this.clampActionIndex(actions.length);
264
+ const lines: Line[] = [
265
+ ...buildBodyText(width, question.prompt, C, C.planning),
266
+ ];
267
+ if (question.whyItMatters) {
268
+ lines.push(...buildBodyText(width, `Why this matters: ${question.whyItMatters}`, C, C.dim));
269
+ }
270
+ if (question.recommendedAnswer) {
271
+ lines.push(...buildBodyText(width, `Recommendation: ${question.recommendedAnswer}`, C, C.good));
272
+ }
273
+ lines.push(...buildBodyText(
274
+ width,
275
+ `Typed answer: ${this.draftAnswer || '(type here while this panel is focused)'}`,
276
+ C,
277
+ this.draftAnswer ? C.value : C.dim,
278
+ ));
279
+ lines.push(buildPanelLine(width, [[
280
+ ' Select an answer below or type your own. Enter sends it through the normal planning chat path.',
281
+ C.dim,
282
+ ]]));
283
+ actions.forEach((action, index) => {
284
+ const selected = index === this.selectedActionIndex;
285
+ lines.push(buildPanelListRow(width, [
286
+ { text: action.label, fg: action.disabled ? C.dim : C.value, bold: selected },
287
+ { text: ` ${action.detail}`, fg: C.dim },
288
+ ], C, {
289
+ selected,
290
+ marker: selected ? '▶' : ' ',
291
+ }));
292
+ });
293
+ return { title: 'Answer Current Question', lines };
294
+ }
295
+
176
296
  private buildStateSection(
177
297
  width: number,
178
298
  state: ProjectPlanningState,
@@ -339,6 +459,124 @@ export class ProjectPlanningPanel extends BasePanel {
339
459
  })();
340
460
  }
341
461
 
462
+ private getCurrentQuestion(): ProjectPlanningQuestion | null {
463
+ const state = this.snapshot?.state;
464
+ const open = state?.openQuestions.find((question) => (question.status ?? 'open') === 'open');
465
+ return open ?? this.snapshot?.evaluation?.nextQuestion ?? null;
466
+ }
467
+
468
+ private getAnswerActions(question: ProjectPlanningQuestion): PlanningAnswerAction[] {
469
+ const actions: PlanningAnswerAction[] = [];
470
+ const prompt = question.prompt.toLowerCase();
471
+ const isScopeQuestion = prompt.includes('scope') || prompt.includes('in or out');
472
+ const isApprovalQuestion = prompt.includes('approved') || prompt.includes('approve') || prompt.includes('execution');
473
+ if (isApprovalQuestion) {
474
+ actions.push({
475
+ id: 'approve-execution',
476
+ label: 'Approve execution',
477
+ detail: 'Mark this plan approved so execution may proceed.',
478
+ answer: 'Approve this planning state for execution.',
479
+ kind: 'approve',
480
+ });
481
+ }
482
+ if (question.recommendedAnswer?.trim()) {
483
+ actions.push({
484
+ id: 'recommended',
485
+ label: 'Use recommended answer',
486
+ detail: this.compact(question.recommendedAnswer),
487
+ answer: question.recommendedAnswer,
488
+ });
489
+ }
490
+ if (isScopeQuestion) {
491
+ actions.push({
492
+ id: 'scope-end-to-end',
493
+ label: 'End-to-end required scope',
494
+ detail: 'Let the plan include every component needed to make this work, but avoid unrelated cleanup.',
495
+ 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.',
496
+ });
497
+ actions.push({
498
+ id: 'scope-tui-first',
499
+ label: 'TUI-first scope',
500
+ detail: 'Fix TUI behavior here; report SDK blockers instead of patching around SDK-owned bugs.',
501
+ 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.',
502
+ });
503
+ }
504
+ actions.push({
505
+ id: 'ask-narrower',
506
+ label: 'I am not sure yet',
507
+ detail: 'Break this into smaller concrete choices with examples and a recommended default.',
508
+ 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.`,
509
+ });
510
+ actions.push({
511
+ id: 'custom',
512
+ label: 'Submit typed answer',
513
+ detail: this.draftAnswer ? this.compact(this.draftAnswer) : 'Type an answer first; this row becomes the custom answer.',
514
+ answer: this.draftAnswer.trim(),
515
+ disabled: !this.draftAnswer.trim(),
516
+ });
517
+ return actions;
518
+ }
519
+
520
+ private submitSelectedAction(question: ProjectPlanningQuestion, actions: readonly PlanningAnswerAction[]): void {
521
+ const action = actions[this.clampActionIndex(actions.length)];
522
+ if (!action || action.disabled || !action.answer.trim()) {
523
+ this.setError('Type an answer or choose a non-empty answer option.');
524
+ this.requestRender();
525
+ return;
526
+ }
527
+ if (action.kind === 'approve') {
528
+ this.approveExecution();
529
+ return;
530
+ }
531
+ if (!this.submitAnswer) {
532
+ this.setError('Planning answer submission is not wired in this runtime.');
533
+ this.requestRender();
534
+ return;
535
+ }
536
+ void (async () => {
537
+ try {
538
+ await this.persistQuestionIfNeeded(question);
539
+ this.draftAnswer = '';
540
+ this.submitAnswer?.(action.answer.trim());
541
+ this.refresh(true);
542
+ this.registerTimer(setTimeout(() => this.refresh(true), 250));
543
+ } catch (err) {
544
+ this.setError(err instanceof Error ? err.message : String(err));
545
+ this.requestRender();
546
+ }
547
+ })();
548
+ }
549
+
550
+ private async persistQuestionIfNeeded(question: ProjectPlanningQuestion): Promise<void> {
551
+ const state = this.snapshot?.state;
552
+ if (!state) return;
553
+ if (state.openQuestions.some((entry) => entry.id === question.id)) return;
554
+ await this.service.upsertState({
555
+ projectId: this.projectId,
556
+ state: {
557
+ ...state,
558
+ openQuestions: [
559
+ { ...question, status: question.status ?? 'open' },
560
+ ...state.openQuestions,
561
+ ],
562
+ },
563
+ });
564
+ }
565
+
566
+ private clampActionIndex(count: number): number {
567
+ if (count <= 0) return 0;
568
+ return Math.max(0, Math.min(count - 1, this.selectedActionIndex));
569
+ }
570
+
571
+ private isPrintableKey(key: string): boolean {
572
+ return key.length === 1 && key >= ' ';
573
+ }
574
+
575
+ private compact(text: string): string {
576
+ const normalized = text.replace(/\s+/g, ' ').trim();
577
+ return normalized.length > 86 ? `${normalized.slice(0, 83)}...` : normalized;
578
+ }
579
+
342
580
  private approveExecution(): void {
343
581
  const state = this.snapshot?.state;
344
582
  if (!state) {
@@ -205,12 +205,16 @@ export class ProjectPlanningCoordinator {
205
205
  const tasks = state.tasks
206
206
  .map((task) => `- ${task.id}: ${task.title}`)
207
207
  .join('\n') || '- none recorded yet';
208
+ const recentAnswers = state.answeredQuestions
209
+ .slice(-3)
210
+ .map((question) => `- ${question.prompt}\n Answer: ${question.answer ?? '(no answer recorded)'}`)
211
+ .join('\n') || '- none recorded yet';
208
212
 
209
213
  return [
210
214
  'TUI-owned project planning loop is active for this turn.',
211
215
  'Do not execute code changes, spawn agents, or claim implementation is complete unless the user explicitly approves execution after the plan is structurally ready.',
212
216
  'Be relentless and thorough: challenge vague wording, inspect relevant context before proposing execution, and ask exactly one focused question when information is missing.',
213
- 'Prefer concrete examples and recommended answers so the user can answer quickly.',
217
+ 'Do not ask broad questions like "what is in scope?" without examples. Break broad planning gaps into concrete choices, explain tradeoffs, and recommend a default the user can accept or correct.',
214
218
  '',
215
219
  `Project id: ${this.projectId}`,
216
220
  `Knowledge space: ${state.knowledgeSpaceId}`,
@@ -222,11 +226,14 @@ export class ProjectPlanningCoordinator {
222
226
  'Readiness gaps:',
223
227
  gaps,
224
228
  '',
229
+ 'Recent answered planning questions:',
230
+ recentAnswers,
231
+ '',
225
232
  'Recorded tasks:',
226
233
  tasks,
227
234
  '',
228
235
  nextQuestion
229
- ? `Ask this exact next planning question unless the user already answered it: ${nextQuestion.prompt}`
236
+ ? `Resolve this next planning gap. If this wording is broad, turn it into a smaller concrete multiple-choice question before asking: ${nextQuestion.prompt}`
230
237
  : 'If the plan is structurally ready, summarize the plan and ask for explicit execution approval. Do not start execution yourself.',
231
238
  ].join('\n');
232
239
  }
@@ -103,6 +103,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
103
103
  providerRegistry: services.providerRegistry,
104
104
  });
105
105
 
106
+ let commandContextRef: CommandContext | null = null;
106
107
  registerBuiltinPanels(services.panelManager, {
107
108
  configManager,
108
109
  getOrchestratorUsage: () => orchestrator.usage as { input: number; output: number; cacheRead: number; cacheWrite: number; model?: string },
@@ -113,6 +114,12 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
113
114
  getCtxWindow: () => services.providerRegistry.getContextWindowForModel(services.providerRegistry.getCurrentModel()),
114
115
  resumeSession,
115
116
  requestRender,
117
+ submitPlanningAnswer: (answer) => {
118
+ if (!commandContextRef?.submitInput) {
119
+ throw new Error('Planning answer submission is not wired yet.');
120
+ }
121
+ commandContextRef.submitInput(answer);
122
+ },
116
123
  forensicsRegistry,
117
124
  policyRuntimeState,
118
125
  approvalBroker: services.approvalBroker,
@@ -243,6 +250,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
243
250
  completeModelSelectionSideEffect,
244
251
  componentHealthMonitor: services.componentHealthMonitor,
245
252
  });
253
+ commandContextRef = commandContext;
246
254
 
247
255
  const gitStatusProvider = new GitStatusProvider(services.workingDirectory);
248
256
  const lastGitInfoRef = { value: undefined as GitHeaderInfo | undefined };
package/src/version.ts CHANGED
@@ -6,7 +6,7 @@ import { join } from 'node:path';
6
6
  // The prebuild script updates the fallback value before compilation.
7
7
  // Uses import.meta.dir (Bun) to locate package.json relative to this file,
8
8
  // which is correct regardless of the process working directory.
9
- let _version = '0.19.81';
9
+ let _version = '0.19.82';
10
10
  try {
11
11
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
12
12
  _version = pkg.version ?? _version;