@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.
Files changed (267) hide show
  1. package/CHANGELOG.md +85 -21
  2. package/README.md +28 -18
  3. package/docs/foundation-artifacts/operator-contract.json +4045 -1763
  4. package/package.json +2 -2
  5. package/src/audio/spoken-turn-controller.ts +12 -2
  6. package/src/audio/spoken-turn-wiring.ts +2 -1
  7. package/src/cli/help.ts +8 -1
  8. package/src/cli/service-posture.ts +2 -1
  9. package/src/cli/status.ts +2 -1
  10. package/src/cli/surface-command.ts +2 -2
  11. package/src/core/alert-gating.ts +67 -0
  12. package/src/core/approval-alert.ts +72 -0
  13. package/src/core/budget-breach-notifier.ts +106 -0
  14. package/src/core/composer-state.ts +11 -3
  15. package/src/core/conversation-line-cache.ts +27 -3
  16. package/src/core/conversation-rendering.ts +90 -14
  17. package/src/core/conversation.ts +18 -0
  18. package/src/core/focus-tracker.ts +41 -0
  19. package/src/core/long-task-notifier.ts +33 -6
  20. package/src/core/stream-event-wiring.ts +20 -1
  21. package/src/core/system-message-noise.ts +87 -0
  22. package/src/core/system-message-router.ts +104 -51
  23. package/src/core/turn-cancellation.ts +25 -0
  24. package/src/core/turn-event-wiring.ts +72 -3
  25. package/src/daemon/cli.ts +29 -2
  26. package/src/daemon/handlers/register.ts +8 -1
  27. package/src/daemon/service-commands.ts +329 -0
  28. package/src/export/cost-utils.ts +36 -0
  29. package/src/input/autocomplete.ts +27 -1
  30. package/src/input/command-registry.ts +84 -5
  31. package/src/input/commands/checkpoint-runtime.ts +280 -0
  32. package/src/input/commands/codebase-runtime.ts +232 -0
  33. package/src/input/commands/config.ts +43 -3
  34. package/src/input/commands/eval.ts +1 -1
  35. package/src/input/commands/health-runtime.ts +10 -2
  36. package/src/input/commands/image-runtime.ts +112 -0
  37. package/src/input/commands/intelligence-runtime.ts +11 -1
  38. package/src/input/commands/local-auth-runtime.ts +4 -1
  39. package/src/input/commands/local-runtime.ts +9 -3
  40. package/src/input/commands/marketplace-runtime.ts +2 -2
  41. package/src/input/commands/memory.ts +73 -35
  42. package/src/input/commands/operator-panel-runtime.ts +71 -31
  43. package/src/input/commands/planning-runtime.ts +116 -4
  44. package/src/input/commands/platform-sandbox-runtime.ts +1 -6
  45. package/src/input/commands/plugin-runtime.ts +2 -2
  46. package/src/input/commands/policy-dispatch.ts +21 -0
  47. package/src/input/commands/provider-accounts-runtime.ts +1 -1
  48. package/src/input/commands/qrcode-runtime.ts +26 -6
  49. package/src/input/commands/recall-review.ts +35 -0
  50. package/src/input/commands/remote-runtime-setup.ts +5 -3
  51. package/src/input/commands/remote-runtime.ts +3 -3
  52. package/src/input/commands/runtime-services.ts +54 -13
  53. package/src/input/commands/services-runtime.ts +1 -1
  54. package/src/input/commands/session-content.ts +20 -9
  55. package/src/input/commands/session-workflow.ts +16 -1
  56. package/src/input/commands/settings-sync-runtime.ts +16 -4
  57. package/src/input/commands/shell-core.ts +25 -8
  58. package/src/input/commands/skills-runtime.ts +2 -8
  59. package/src/input/commands/subscription-runtime.ts +2 -2
  60. package/src/input/commands/test-runtime.ts +277 -0
  61. package/src/input/commands/websearch-runtime.ts +101 -0
  62. package/src/input/commands/work-plan-runtime.ts +36 -10
  63. package/src/input/commands/workstream-runtime.ts +488 -0
  64. package/src/input/commands.ts +12 -0
  65. package/src/input/config-modal-types.ts +175 -0
  66. package/src/input/config-modal.ts +592 -0
  67. package/src/input/feed-context-factory.ts +10 -8
  68. package/src/input/handler-command-route.ts +37 -20
  69. package/src/input/handler-content-actions.ts +17 -2
  70. package/src/input/handler-feed-routes.ts +74 -114
  71. package/src/input/handler-feed.ts +79 -27
  72. package/src/input/handler-interactions.ts +1 -3
  73. package/src/input/handler-modal-routes.ts +136 -6
  74. package/src/input/handler-modal-stack.ts +14 -7
  75. package/src/input/handler-modal-token-routes.ts +16 -49
  76. package/src/input/handler-onboarding-daemon-adopt.ts +149 -0
  77. package/src/input/handler-onboarding.ts +71 -59
  78. package/src/input/handler-picker-routes.ts +26 -102
  79. package/src/input/handler-shortcuts.ts +74 -14
  80. package/src/input/handler-types.ts +2 -6
  81. package/src/input/handler-ui-state.ts +10 -22
  82. package/src/input/handler.ts +12 -29
  83. package/src/input/keybindings.ts +23 -8
  84. package/src/input/model-picker-types.ts +24 -6
  85. package/src/input/model-picker.ts +77 -2
  86. package/src/input/onboarding/onboarding-runtime-status.ts +10 -1
  87. package/src/input/onboarding/onboarding-wizard-apply.ts +8 -1
  88. package/src/input/onboarding/onboarding-wizard-constants.ts +4 -1
  89. package/src/input/onboarding/onboarding-wizard-network-adopt.ts +136 -0
  90. package/src/input/onboarding/onboarding-wizard-steps.ts +9 -6
  91. package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
  92. package/src/input/panel-integration-actions.ts +9 -170
  93. package/src/input/panel-mouse-geometry.ts +97 -0
  94. package/src/input/panel-paste-flood-guard.ts +86 -0
  95. package/src/input/selection-modal.ts +11 -0
  96. package/src/input/session-picker-modal.ts +44 -4
  97. package/src/input/settings-modal-data.ts +146 -2
  98. package/src/input/settings-modal-mutations.ts +6 -4
  99. package/src/input/settings-modal-types.ts +4 -2
  100. package/src/main.ts +49 -51
  101. package/src/panels/agent-inspector-shared.ts +2 -1
  102. package/src/panels/base-panel.ts +22 -1
  103. package/src/panels/builtin/agent.ts +21 -107
  104. package/src/panels/builtin/development.ts +15 -61
  105. package/src/panels/builtin/knowledge.ts +8 -32
  106. package/src/panels/builtin/operations.ts +84 -428
  107. package/src/panels/builtin/session.ts +21 -112
  108. package/src/panels/builtin/shared.ts +18 -46
  109. package/src/panels/builtin-modals.ts +218 -0
  110. package/src/panels/builtin-panels.ts +5 -0
  111. package/src/panels/cost-tracker-panel.ts +36 -10
  112. package/src/panels/diff-panel.ts +12 -43
  113. package/src/panels/eval-registry.ts +60 -0
  114. package/src/panels/fleet-deep-link.ts +31 -0
  115. package/src/panels/fleet-panel-format.ts +62 -0
  116. package/src/panels/fleet-panel-worktree-detail.ts +48 -0
  117. package/src/panels/fleet-panel.ts +758 -0
  118. package/src/panels/fleet-read-model.ts +520 -0
  119. package/src/panels/fleet-steer.ts +125 -0
  120. package/src/panels/fleet-stop.ts +153 -0
  121. package/src/panels/fleet-tabs.ts +230 -0
  122. package/src/panels/fleet-transcript.ts +356 -0
  123. package/src/panels/index.ts +7 -31
  124. package/src/panels/modals/hooks-modal.ts +187 -0
  125. package/src/panels/modals/keybindings-modal.ts +166 -0
  126. package/src/panels/modals/knowledge-modal.ts +158 -0
  127. package/src/panels/modals/local-auth-modal.ts +132 -0
  128. package/src/panels/modals/marketplace-modal.ts +218 -0
  129. package/src/panels/modals/memory-modal.ts +180 -0
  130. package/src/panels/modals/modal-surface-helpers.ts +54 -0
  131. package/src/panels/modals/modal-theme.ts +42 -0
  132. package/src/panels/modals/pairing-modal.ts +163 -0
  133. package/src/panels/modals/planning-modal.ts +304 -0
  134. package/src/panels/modals/plugins-modal.ts +174 -0
  135. package/src/panels/modals/policy-modal.ts +256 -0
  136. package/src/panels/modals/provider-health-modal.ts +136 -0
  137. package/src/panels/modals/remote-modal.ts +115 -0
  138. package/src/panels/modals/sandbox-modal.ts +150 -0
  139. package/src/panels/modals/security-modal.ts +217 -0
  140. package/src/panels/modals/services-modal.ts +179 -0
  141. package/src/panels/modals/settings-sync-modal.ts +142 -0
  142. package/src/panels/modals/skills-modal.ts +189 -0
  143. package/src/panels/modals/subscription-modal.ts +172 -0
  144. package/src/panels/modals/work-plan-modal.ts +177 -0
  145. package/src/panels/panel-confirm-overlay.ts +72 -0
  146. package/src/panels/panel-manager.ts +123 -9
  147. package/src/panels/polish-core.ts +38 -25
  148. package/src/panels/project-planning-answer-actions.ts +8 -11
  149. package/src/panels/skills-panel.ts +7 -51
  150. package/src/panels/types.ts +37 -0
  151. package/src/permissions/hunk-selection.ts +179 -0
  152. package/src/permissions/prompt.ts +216 -13
  153. package/src/renderer/autocomplete-overlay.ts +28 -2
  154. package/src/renderer/compaction-history-modal.ts +19 -3
  155. package/src/renderer/compaction-preview.ts +13 -2
  156. package/src/renderer/compaction-quality.ts +100 -0
  157. package/src/renderer/compositor.ts +2 -3
  158. package/src/renderer/config-modal.ts +95 -0
  159. package/src/renderer/conversation-overlays.ts +10 -17
  160. package/src/renderer/fleet-tab-strip.ts +56 -0
  161. package/src/renderer/footer-tips.ts +15 -3
  162. package/src/renderer/fullscreen-primitives.ts +32 -22
  163. package/src/renderer/git-status.ts +43 -1
  164. package/src/renderer/help-overlay.ts +2 -2
  165. package/src/renderer/layout.ts +0 -4
  166. package/src/renderer/markdown.ts +7 -3
  167. package/src/renderer/modal-factory.ts +25 -20
  168. package/src/renderer/model-workspace.ts +62 -4
  169. package/src/renderer/overlay-box.ts +21 -17
  170. package/src/renderer/panel-workspace-bar.ts +8 -2
  171. package/src/renderer/process-indicator.ts +14 -3
  172. package/src/renderer/selection-modal-overlay.ts +6 -1
  173. package/src/renderer/session-picker-modal.ts +196 -3
  174. package/src/renderer/settings-modal-helpers.ts +2 -0
  175. package/src/renderer/settings-modal.ts +7 -0
  176. package/src/renderer/shell-surface.ts +8 -1
  177. package/src/renderer/status-glyphs.ts +14 -15
  178. package/src/renderer/system-message.ts +15 -3
  179. package/src/renderer/terminal-bg-probe.ts +339 -0
  180. package/src/renderer/terminal-escapes.ts +20 -0
  181. package/src/renderer/theme-mode-config.ts +67 -0
  182. package/src/renderer/theme.ts +91 -1
  183. package/src/renderer/thinking.ts +11 -3
  184. package/src/renderer/tool-call.ts +15 -9
  185. package/src/renderer/tool-result-summary.ts +148 -0
  186. package/src/renderer/turn-injection.ts +133 -0
  187. package/src/renderer/ui-factory.ts +154 -85
  188. package/src/renderer/ui-primitives.ts +30 -129
  189. package/src/runtime/bootstrap-command-context.ts +27 -1
  190. package/src/runtime/bootstrap-command-parts.ts +41 -10
  191. package/src/runtime/bootstrap-core.ts +47 -15
  192. package/src/runtime/bootstrap-hook-bridge.ts +7 -0
  193. package/src/runtime/bootstrap-shell.ts +48 -17
  194. package/src/runtime/bootstrap.ts +129 -22
  195. package/src/runtime/code-index-services.ts +135 -0
  196. package/src/runtime/legacy-daemon-migration.ts +516 -0
  197. package/src/runtime/memory-fold.ts +26 -0
  198. package/src/runtime/onboarding/derivation.ts +7 -2
  199. package/src/runtime/onboarding/snapshot.ts +27 -1
  200. package/src/runtime/onboarding/types.ts +31 -1
  201. package/src/runtime/operator-token-cleanup.ts +82 -1
  202. package/src/runtime/orchestrator-core-services.ts +59 -0
  203. package/src/runtime/process-lifecycle.ts +3 -1
  204. package/src/runtime/resume-notice.ts +209 -0
  205. package/src/runtime/services.ts +98 -46
  206. package/src/runtime/session-inbound-inputs.ts +252 -0
  207. package/src/runtime/session-spine-transport.ts +64 -0
  208. package/src/runtime/terminal-output-guard.ts +15 -8
  209. package/src/runtime/ui-services.ts +27 -3
  210. package/src/runtime/workstream-services.ts +327 -0
  211. package/src/runtime/wrfc-persistence.ts +124 -17
  212. package/src/shell/blocking-input.ts +68 -4
  213. package/src/shell/recovery-input-helpers.ts +170 -1
  214. package/src/shell/ui-openers.ts +171 -26
  215. package/src/utils/format-duration.ts +8 -18
  216. package/src/utils/splash-lines.ts +1 -1
  217. package/src/utils/terminal-width.ts +52 -0
  218. package/src/version.ts +1 -1
  219. package/src/panels/agent-inspector-panel.ts +0 -786
  220. package/src/panels/approval-panel.ts +0 -252
  221. package/src/panels/automation-control-panel.ts +0 -479
  222. package/src/panels/cockpit-panel.ts +0 -481
  223. package/src/panels/cockpit-read-model.ts +0 -238
  224. package/src/panels/communication-panel.ts +0 -256
  225. package/src/panels/control-plane-panel.ts +0 -470
  226. package/src/panels/debug-panel.ts +0 -615
  227. package/src/panels/docs-panel.ts +0 -384
  228. package/src/panels/eval-panel.ts +0 -627
  229. package/src/panels/file-explorer-panel.ts +0 -673
  230. package/src/panels/file-preview-panel.ts +0 -517
  231. package/src/panels/hooks-panel.ts +0 -401
  232. package/src/panels/incident-review-panel.ts +0 -406
  233. package/src/panels/intelligence-panel.ts +0 -383
  234. package/src/panels/knowledge-graph-panel.ts +0 -515
  235. package/src/panels/marketplace-panel.ts +0 -399
  236. package/src/panels/memory-panel.ts +0 -558
  237. package/src/panels/ops-control-panel.ts +0 -329
  238. package/src/panels/ops-strategy-panel.ts +0 -321
  239. package/src/panels/orchestration-panel.ts +0 -465
  240. package/src/panels/panel-list-panel.ts +0 -566
  241. package/src/panels/plan-dashboard-panel.ts +0 -707
  242. package/src/panels/policy-panel.ts +0 -517
  243. package/src/panels/project-planning-panel.ts +0 -731
  244. package/src/panels/provider-health-panel.ts +0 -678
  245. package/src/panels/provider-health-tracker.ts +0 -310
  246. package/src/panels/provider-health-views.ts +0 -567
  247. package/src/panels/qr-panel.ts +0 -280
  248. package/src/panels/remote-panel.ts +0 -534
  249. package/src/panels/routes-panel.ts +0 -241
  250. package/src/panels/sandbox-panel.ts +0 -456
  251. package/src/panels/security-panel.ts +0 -447
  252. package/src/panels/services-panel.ts +0 -329
  253. package/src/panels/session-browser-panel.ts +0 -496
  254. package/src/panels/settings-sync-panel.ts +0 -398
  255. package/src/panels/subscription-panel.ts +0 -342
  256. package/src/panels/symbol-outline-panel.ts +0 -619
  257. package/src/panels/system-messages-panel.ts +0 -364
  258. package/src/panels/tasks-panel.ts +0 -608
  259. package/src/panels/thinking-panel.ts +0 -333
  260. package/src/panels/tool-inspector-panel.ts +0 -537
  261. package/src/panels/work-plan-panel.ts +0 -540
  262. package/src/panels/worktree-panel.ts +0 -360
  263. package/src/panels/wrfc-panel.ts +0 -790
  264. package/src/renderer/agent-detail-modal.ts +0 -466
  265. package/src/renderer/live-tail-modal.ts +0 -156
  266. package/src/renderer/process-modal.ts +0 -671
  267. package/src/renderer/qr-renderer.ts +0 -120
@@ -0,0 +1,112 @@
1
+ import type { CommandRegistry } from '../command-registry.ts';
2
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
3
+
4
+ /**
5
+ * registerImageRuntimeCommands - `/imagine <prompt>`.
6
+ *
7
+ * Named 'imagine', not 'image' — `/image <path> [prompt]` already exists
8
+ * (local-runtime.ts) and does something entirely different (attach a local
9
+ * image file to the next message). Re-verified against the current registry
10
+ * before wiring this in: the brief that scoped this command was written
11
+ * before checking for the collision.
12
+ *
13
+ * First production caller of MediaProviderRegistry.generate() — the SDK
14
+ * plumbing existed with zero non-test call sites before this command.
15
+ * findProvider('generate') returns whichever generation-capable provider is
16
+ * configured; when none are, the command prints the registry's own
17
+ * per-provider status (id/state/detail) verbatim — those detail strings
18
+ * already name the exact env var per builtin provider
19
+ * (builtin-generation-providers.ts's buildStatus()), so this command never
20
+ * has to (and never should) guess or hardcode an env-var name itself.
21
+ */
22
+ export function registerImageRuntimeCommands(registry: CommandRegistry): void {
23
+ registry.register({
24
+ name: 'imagine',
25
+ description: 'Generate an image from a prompt via a configured media provider',
26
+ usage: '<prompt>',
27
+ argsHint: '<prompt>',
28
+ async handler(args, ctx) {
29
+ const mediaProviders = ctx.platform.mediaProviders;
30
+ const artifactStore = ctx.platform.artifactStore;
31
+ if (!mediaProviders || !artifactStore) {
32
+ ctx.print('Image generation is not available in this session.');
33
+ return;
34
+ }
35
+
36
+ const prompt = args.join(' ').trim();
37
+ if (!prompt) {
38
+ ctx.print('Usage: /imagine <prompt>');
39
+ return;
40
+ }
41
+
42
+ const provider = mediaProviders.findProvider('generate');
43
+ if (!provider?.generate) {
44
+ const statuses = await mediaProviders.status();
45
+ const generationStatuses = statuses.filter((status) => status.capabilities.includes('generate'));
46
+ const lines = ['No image-generation provider is configured.', ''];
47
+ if (generationStatuses.length === 0) {
48
+ lines.push('No media-generation providers are registered in this build.');
49
+ } else {
50
+ for (const status of generationStatuses) {
51
+ lines.push(` ${status.id} (${status.label}): ${status.state}${status.detail ? ` — ${status.detail}` : ''}`);
52
+ }
53
+ }
54
+ ctx.print(lines.join('\n'));
55
+ return;
56
+ }
57
+
58
+ try {
59
+ const result = await provider.generate({
60
+ prompt,
61
+ outputMimeType: 'image/png',
62
+ metadata: { source: 'tui-image-command' },
63
+ });
64
+
65
+ if (result.artifacts.length === 0) {
66
+ ctx.print(`${provider.label} returned no artifacts for this prompt.`);
67
+ return;
68
+ }
69
+
70
+ const lines = [`Generated ${result.artifacts.length} artifact(s) via ${provider.label} (${result.providerId}).`];
71
+ for (const artifact of result.artifacts) {
72
+ if (!artifact.mimeType.startsWith('image/')) {
73
+ lines.push(` Note: ${provider.label} produced ${artifact.mimeType}, not an image — no image-capable provider is configured in this build.`);
74
+ }
75
+ if (artifact.dataBase64) {
76
+ const descriptor = await artifactStore.create({
77
+ dataBase64: artifact.dataBase64,
78
+ mimeType: artifact.mimeType,
79
+ filename: artifact.filename ?? 'generated-image',
80
+ metadata: { ...artifact.metadata, generatedBy: result.providerId },
81
+ });
82
+ lines.push(` artifact: ${descriptor.id} (${descriptor.mimeType}, ${descriptor.sizeBytes} bytes)`);
83
+ } else if (artifact.uri) {
84
+ // Design note: this is the first production caller of generate(), and
85
+ // returned artifacts may be provider-hosted remote URLs rather than
86
+ // inline bytes. There is no existing precedent for whether to eagerly
87
+ // download remote-only output into ArtifactStore, and doing so
88
+ // unconditionally would turn a text command into an unbounded
89
+ // background fetch of arbitrary provider-hosted content. Conservative
90
+ // default: store a small JSON pointer record (not the media bytes) so
91
+ // the generation is still discoverable via the artifact store, and
92
+ // render the URL directly so the result stays immediately usable.
93
+ const descriptor = await artifactStore.create({
94
+ text: JSON.stringify({ remoteUrl: artifact.uri, mimeType: artifact.mimeType, providerId: result.providerId }, null, 2),
95
+ mimeType: 'application/json',
96
+ filename: `${artifact.filename ?? 'generated-image'}.reference.json`,
97
+ sourceUri: artifact.uri,
98
+ metadata: { ...artifact.metadata, generatedBy: result.providerId, remote: true },
99
+ });
100
+ lines.push(` artifact: ${descriptor.id} (remote reference — not downloaded)`);
101
+ lines.push(` url: ${artifact.uri}`);
102
+ } else {
103
+ lines.push(' artifact: (no retrievable content returned)');
104
+ }
105
+ }
106
+ ctx.print(lines.join('\n'));
107
+ } catch (error) {
108
+ ctx.print(`Image generation failed: ${summarizeError(error)}`);
109
+ }
110
+ },
111
+ });
112
+ }
@@ -55,7 +55,17 @@ export function registerIntelligenceRuntimeCommands(registry: CommandRegistry):
55
55
  async handler(args, ctx) {
56
56
  const sub = (args[0] ?? 'review').toLowerCase();
57
57
  if (sub === 'panel' || sub === 'open') {
58
- openCommandPanel(ctx, 'intelligence');
58
+ // W6.1 (the purge): 'intelligence'/IntelligencePanel was
59
+ // DELETE-disposition (no surviving human surface — its read model
60
+ // stays and still backs every subcommand below). There is no alias
61
+ // to resolve through, so this prints an honest notice and opens
62
+ // Fleet instead of throwing "Unknown panel".
63
+ ctx.print('Intelligence panel retired — use /intelligence review|diagnostics|symbols|outline|definition|references|hover|repair for the live data. Opening Fleet.');
64
+ try {
65
+ openCommandPanel(ctx, 'fleet');
66
+ } catch {
67
+ // Panel registry may be unavailable in lightweight command-only contexts.
68
+ }
59
69
  return;
60
70
  }
61
71
 
@@ -10,7 +10,10 @@ export function handleLocalAuthCommand(args: string[], ctx: CommandContext): voi
10
10
  const sub = (args[0] ?? 'review').toLowerCase();
11
11
  const auth = requireLocalUserAuthManager(ctx);
12
12
  if (sub === 'panel' || sub === 'open') {
13
- openCommandPanel(ctx, 'local-auth');
13
+ // W6.1: browse view moved to local-auth-modal. The LocalAuthPanel itself is
14
+ // kept (masked password-entry host) but is no longer the /local-auth panel
15
+ // destination — masked entry is reached via add-user/rotate-password below.
16
+ ctx.openModal?.('local-auth-modal');
14
17
  return;
15
18
  }
16
19
 
@@ -70,8 +70,14 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
70
70
  handler(args, ctx) {
71
71
  const sub = (args[0] ?? '').toLowerCase();
72
72
  if (sub === 'panel' || sub === 'review') {
73
+ // W6.1 (the purge): 'tools'/ToolInspectorPanel was DELETE-disposition
74
+ // (no surviving human surface — tool results render inline in the
75
+ // transcript, plus a per-node tool list in Fleet). There is no alias
76
+ // to resolve through, so this prints an honest notice and opens
77
+ // Fleet instead of the retired inspector.
78
+ ctx.print('Tools panel retired — tool activity now renders inline in the transcript and per-agent in the Fleet panel. Opening Fleet.');
73
79
  try {
74
- openCommandPanel(ctx, 'tools');
80
+ openCommandPanel(ctx, 'fleet');
75
81
  } catch {
76
82
  // Panel registry may be unavailable in lightweight command-only contexts.
77
83
  }
@@ -79,9 +85,9 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
79
85
  ctx.print([
80
86
  'Tool Surface Review',
81
87
  ' Native file tools stay compact by default.',
82
- ' Read/write/edit/notebook capabilities are available through the native tool stack, with detail routed to the tools panel and approval surfaces instead of transcript bloat.',
88
+ ' Read/write/edit/notebook capabilities are available through the native tool stack, with detail routed inline and to approval surfaces instead of transcript bloat.',
83
89
  ' Shell and native tool approvals classify work into read, mutation, destructive, dependency, config, notebook, network, remote, and lifecycle risk families.',
84
- ' Use /tools panel to inspect risk class, output-policy actions, spill posture, compact summaries, and approval posture for recent calls.',
90
+ ' Use /tools panel to jump to the Fleet panel for live per-agent tool activity.',
85
91
  ' Use /approval review shell or /approval review file when you need the action-specific why-prompted posture.',
86
92
  ].join('\n'));
87
93
  }
@@ -19,7 +19,7 @@ import {
19
19
  type EcosystemCatalogEntry,
20
20
  type EcosystemEntryKind,
21
21
  } from '@/runtime/index.ts';
22
- import { openCommandPanel, requireEcosystemCatalogPaths, requireReadModels, requireShellPaths } from './runtime-services.ts';
22
+ import { openModalCommand, requireEcosystemCatalogPaths, requireReadModels, requireShellPaths } from './runtime-services.ts';
23
23
 
24
24
  function resolveMarketplaceEntry(
25
25
  kind: EcosystemEntryKind,
@@ -45,7 +45,7 @@ export function registerMarketplaceRuntimeCommands(registry: CommandRegistry): v
45
45
  const ecosystemPaths = requireEcosystemCatalogPaths(ctx);
46
46
  const sub = args[0] ?? 'open';
47
47
  if (sub === 'open' || sub === 'panel') {
48
- openCommandPanel(ctx, 'marketplace');
48
+ openModalCommand(ctx, 'marketplace-modal');
49
49
  return;
50
50
  }
51
51
  if (sub === 'overview') {
@@ -17,21 +17,82 @@ import type { SlashCommand, CommandContext } from '../command-registry.ts';
17
17
  import { handleRecallAdd, handleRecallCapture } from './recall-capture.ts';
18
18
  import { handleRecallExport, handleRecallHandoffExport, handleRecallHandoffImport, handleRecallHandoffInspect, handleRecallImport } from './recall-bundle.ts';
19
19
  import { handleRecallGet, handleRecallLink, handleRecallList, handleRecallRemove, handleRecallSearch, handleRecallVector } from './recall-query.ts';
20
- import { handleRecallExplain, handleRecallPromote, handleRecallQueue, handleRecallReview } from './recall-review.ts';
20
+ import { handleRecallExplain, handleRecallInjections, handleRecallPromote, handleRecallQueue, handleRecallReview } from './recall-review.ts';
21
21
  import { VALID_CLASSES, VALID_REVIEW_STATES, VALID_SCOPES } from './recall-shared.ts';
22
22
 
23
23
  // ── Top-level command ─────────────────────────────────────────────────────────
24
24
 
25
+ /**
26
+ * DEBT-5 item 3 divergence note (historical): the work order that shipped
27
+ * this command named its front door "/memory", but at the time `/memory` was
28
+ * already a distinct, unrelated command (session-pinned sticky notes,
29
+ * src/input/commands/session-content.ts) with no modal surface — so that
30
+ * work order deliberately did NOT touch /memory and used /recall instead.
31
+ *
32
+ * W6-C3 update (Wave 6 core-verb pass, MEMORY fragmentation — worst-class
33
+ * collision #2): the agent's own `/memory` command was a plain alias for its
34
+ * `/recall`-equivalent the whole time, meaning "/memory" meant two unrelated
35
+ * things depending which surface you were on. The session-notes command was
36
+ * renamed to `/note` (session-content.ts) to free the word, and `/memory` is
37
+ * now registered here as a real alias of `/recall` — the word means the same
38
+ * durable Project Memory Substrate on both surfaces. The modal that exists
39
+ * for this data — the Project Memory Substrate — is still `memory-modal.ts`,
40
+ * owned by THIS command, confirmed by the panel-id redirect
41
+ * `registerModalRedirect('memory', 'memory-modal')` in builtin-modals.ts.
42
+ */
43
+ function printRecallUsage(context: CommandContext): void {
44
+ const usage = [
45
+ 'Usage: /recall <subcommand>',
46
+ ' add <class> <summary> [--scope <session|project|team>] [--detail <text>] [--tags <t,t>] [--session <id>] [--task <id>] [--file <path>]',
47
+ ` classes: ${VALID_CLASSES.join(', ')}`,
48
+ ' capture incident <id|latest> — Capture a forensics incident as durable memory',
49
+ ' capture policy — Capture the latest policy preflight review as durable memory',
50
+ ' capture mcp <server> — Capture MCP trust/quarantine posture as durable memory',
51
+ ' capture plugin <name> — Capture plugin trust/quarantine posture as durable memory',
52
+ ' search [query] [--semantic] [--cls <class>] [--scope <scope>] [--limit <n>] — Full-text or sqlite-vec semantic search',
53
+ ' vector [status|doctor|rebuild] — Inspect or rebuild the sqlite-vec memory index',
54
+ ' get <id> — Show record with provenance + links',
55
+ ' link <fromId> <toId> <relation> — Create a directed relation between records',
56
+ ' queue [limit] — Show the operator review queue',
57
+ ' review <id> <state> [--confidence <n>] [--by <name>] [--reason <text>]',
58
+ ' stale <id> [reason...] — Mark a record stale with an operator reason',
59
+ ' contradict <id> [reason...] — Mark a record contradicted with an operator reason',
60
+ ' explain <task...> [--scope <path> ...] — Show the knowledge records that would be injected for a task',
61
+ ' injections [agentId] — Show per-turn passive knowledge injection records; no id shows the main session, an id shows that spawned agent',
62
+ ' promote <id> <scope> — Promote a memory record into session|project|team scope',
63
+ ' export <path> [--scope <scope>] [--cls <class>] — Export a durable knowledge bundle',
64
+ ' import <path> — Import a durable knowledge bundle',
65
+ ' handoff-export <path> [--scope <scope>] — Export a reviewable handoff bundle for team/shared use',
66
+ ' handoff-inspect <path> — Inspect a handoff bundle before import',
67
+ ' handoff-import <path> — Import a handoff bundle into durable memory',
68
+ ' list [class] [--scope <scope>] — List all records grouped by class',
69
+ ' remove <id> — Delete a record',
70
+ ].join('\n');
71
+ context.print(usage);
72
+ }
73
+
25
74
  export const recallCommand: SlashCommand = {
26
75
  name: 'recall',
27
- aliases: ['rc'],
28
- description: 'Project memory: add decisions, constraints, incidents, and patterns with provenance',
29
- usage: '<subcommand> [args]',
30
- argsHint: 'add|search|link|get|list|remove',
76
+ aliases: ['rc', 'memory', 'mem'],
77
+ description: 'Bare opens the Memory modal; project memory subcommands add decisions, constraints, incidents, and patterns with provenance',
78
+ usage: '[<subcommand> [args]] — bare opens the modal; report prints the subcommand usage text',
79
+ argsHint: 'add|search|link|get|list|remove|report',
31
80
  handler: async (args: string[], context: CommandContext): Promise<void> => {
32
81
  const [sub, ...rest] = args;
33
82
 
83
+ // DEBT-5 item 3: bare `/recall` opens the memory-modal surface — the old
84
+ // bare/unknown-subcommand usage block moved to an explicit `report`
85
+ // subcommand (scriptability preserved: /recall report).
86
+ if (sub === undefined) {
87
+ context.openModal?.('memory-modal');
88
+ return;
89
+ }
90
+
34
91
  switch (sub) {
92
+ case 'report':
93
+ printRecallUsage(context);
94
+ break;
95
+
35
96
  case 'add':
36
97
  await handleRecallAdd(rest, context);
37
98
  break;
@@ -76,6 +137,10 @@ export const recallCommand: SlashCommand = {
76
137
  handleRecallExplain(rest, context);
77
138
  break;
78
139
 
140
+ case 'injections':
141
+ handleRecallInjections(rest, context);
142
+ break;
143
+
79
144
  case 'promote':
80
145
  handleRecallPromote(rest, context);
81
146
  break;
@@ -116,36 +181,9 @@ export const recallCommand: SlashCommand = {
116
181
  await handleRecallHandoffImport(rest, context);
117
182
  break;
118
183
 
119
- default: {
120
- const usage = [
121
- 'Usage: /recall <subcommand>',
122
- ' add <class> <summary> [--scope <session|project|team>] [--detail <text>] [--tags <t,t>] [--session <id>] [--task <id>] [--file <path>]',
123
- ` classes: ${VALID_CLASSES.join(', ')}`,
124
- ' capture incident <id|latest> — Capture a forensics incident as durable memory',
125
- ' capture policy — Capture the latest policy preflight review as durable memory',
126
- ' capture mcp <server> — Capture MCP trust/quarantine posture as durable memory',
127
- ' capture plugin <name> — Capture plugin trust/quarantine posture as durable memory',
128
- ' search [query] [--semantic] [--cls <class>] [--scope <scope>] [--limit <n>] — Full-text or sqlite-vec semantic search',
129
- ' vector [status|doctor|rebuild] — Inspect or rebuild the sqlite-vec memory index',
130
- ' get <id> — Show record with provenance + links',
131
- ' link <fromId> <toId> <relation> — Create a directed relation between records',
132
- ' queue [limit] — Show the operator review queue',
133
- ' review <id> <state> [--confidence <n>] [--by <name>] [--reason <text>]',
134
- ' stale <id> [reason...] — Mark a record stale with an operator reason',
135
- ' contradict <id> [reason...] — Mark a record contradicted with an operator reason',
136
- ' explain <task...> [--scope <path> ...] — Show the knowledge records that would be injected for a task',
137
- ' promote <id> <scope> — Promote a memory record into session|project|team scope',
138
- ' export <path> [--scope <scope>] [--cls <class>] — Export a durable knowledge bundle',
139
- ' import <path> — Import a durable knowledge bundle',
140
- ' handoff-export <path> [--scope <scope>] — Export a reviewable handoff bundle for team/shared use',
141
- ' handoff-inspect <path> — Inspect a handoff bundle before import',
142
- ' handoff-import <path> — Import a handoff bundle into durable memory',
143
- ' list [class] [--scope <scope>] — List all records grouped by class',
144
- ' remove <id> — Delete a record',
145
- ].join('\n');
146
- context.print(usage);
147
- break;
148
- }
184
+ default:
185
+ printRecallUsage(context);
186
+ break;
149
187
  }
150
188
  },
151
189
  };
@@ -1,62 +1,101 @@
1
1
  import type { CommandRegistry } from '../command-registry.ts';
2
+ import type { PanelDeepLinkTarget } from '../../panels/panel-manager.ts';
2
3
  import { requirePanelManager } from './runtime-services.ts';
3
4
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
4
5
 
6
+ /**
7
+ * Parse `--target <id>[:<kind>]` out of the args following `<panel-id>
8
+ * [top|bottom]` (DEBT-5 item 4). Splits the args array in place (removing the
9
+ * flag + its value) so positional `pane` parsing downstream is unaffected by
10
+ * where the flag appears. Only `open fleet` currently has a deep-link
11
+ * consumer (FleetPanel.receiveDeepLink) — other panel ids just ignore an
12
+ * unused target via PanelManager's optional-chaining delivery.
13
+ */
14
+ function extractTargetFlag(rest: string[]): PanelDeepLinkTarget | undefined {
15
+ const flagIdx = rest.indexOf('--target');
16
+ if (flagIdx < 0) return undefined;
17
+ const raw = rest[flagIdx + 1];
18
+ rest.splice(flagIdx, 2);
19
+ if (!raw) return undefined;
20
+ const sep = raw.indexOf(':');
21
+ return sep >= 0 ? { id: raw.slice(0, sep), kind: raw.slice(sep + 1) } : { id: raw };
22
+ }
23
+
5
24
  export function registerOperatorPanelCommand(registry: CommandRegistry): void {
6
25
  registry.register({
7
26
  name: 'panel',
8
27
  aliases: ['panels'],
9
28
  description: 'Open, place, resize, or list panels. Usage: /panel [open <id> [top|bottom]|close <id>|list|toggle|move|focus|split|width|height]',
10
- usage: '[open <id> [top|bottom]|close <id>|list|toggle|move <top|bottom|other> [id]|focus <top|bottom|toggle>|split [show|hide|toggle]|width <left|right|reset>|height <up|down|reset>]',
29
+ usage: '[open <id> [top|bottom] [--target <id>[:<kind>]]|close <id>|list|toggle|move <top|bottom|other> [id]|focus <top|bottom|toggle>|split [show|hide|toggle]|width <left|right|reset>|height <up|down|reset>]',
11
30
  argsHint: '<open|close|list|toggle|move|focus|split|width|height> [id]',
12
31
  handler(args, ctx) {
13
32
  const pm = requirePanelManager(ctx);
14
33
  const sub = args[0]?.toLowerCase() ?? '';
15
34
  if (!sub || sub === 'toggle') {
16
- try {
17
- if (ctx.showPanel) ctx.showPanel('panel-list');
18
- else {
19
- pm.open('panel-list');
20
- pm.show();
21
- ctx.focusPanels?.();
22
- ctx.renderRequest();
23
- }
24
- } catch {
25
- pm.toggle();
26
- ctx.renderRequest();
27
- }
35
+ // W6.1 (the purge): 'panel-list' (the browse-all-panels picker) was
36
+ // DELETE-disposition — a picker over a handful of panels is dead
37
+ // weight now (see the Ctrl+P selectionModal repoint in
38
+ // shell/ui-openers.ts for its interactive replacement). Bare
39
+ // `/panel` and `/panel toggle` now do what "toggle" actually means:
40
+ // toggle the workspace, which opens 'fleet' by default when nothing
41
+ // is open (PanelManager.toggle()) rather than force-opening a
42
+ // specific browse panel.
43
+ pm.toggle();
44
+ ctx.renderRequest();
28
45
  } else if (sub === 'list') {
29
- try {
30
- if (ctx.showPanel) ctx.showPanel('panel-list');
31
- else {
32
- pm.open('panel-list');
33
- pm.show();
34
- ctx.focusPanels?.();
35
- ctx.renderRequest();
46
+ // Print a text listing instead of opening a picker panel — grouped
47
+ // by category from the live registry, so this can never drift from
48
+ // what's actually registered.
49
+ const byCategory = pm.getTypesByCategory();
50
+ const lines: string[] = [];
51
+ for (const [category, entries] of byCategory) {
52
+ lines.push(`${category}:`);
53
+ for (const entry of entries) {
54
+ lines.push(` ${entry.icon} ${entry.id} — ${entry.name}`);
36
55
  }
37
- } catch {
38
- pm.show();
39
- ctx.renderRequest();
40
56
  }
57
+ ctx.print(lines.length > 0 ? lines.join('\n') : 'No panels registered.');
41
58
  } else if (sub === 'open') {
42
59
  const id = args[1];
43
- const pane = args[2]?.toLowerCase();
60
+ const rest = args.slice(2);
61
+ const target = extractTargetFlag(rest);
62
+ const pane = rest[0]?.toLowerCase();
44
63
  if (!id) { ctx.print('Usage: /panel open <panel-id>'); return; }
45
64
  if (pane && pane !== 'top' && pane !== 'bottom') {
46
- ctx.print('Usage: /panel open <panel-id> [top|bottom]');
65
+ ctx.print('Usage: /panel open <panel-id> [top|bottom] [--target <id>[:<kind>]]');
47
66
  return;
48
67
  }
49
68
  try {
50
- if (ctx.showPanel) ctx.showPanel(id, pane as 'top' | 'bottom' | undefined);
69
+ // A MIGRATE-TO-MODAL id (e.g. 'sessions' -> the session-picker modal)
70
+ // resolves to a modal, not a panel: opening it fires the redirect and
71
+ // no panel lands in the workspace. Report that honestly rather than
72
+ // claiming "Panel opened: <id>".
73
+ const redirectTarget = pm.getModalRedirect(id);
74
+ // UX-C focus rule 1a: the command path leaves focus in the composer
75
+ // ("mid-command-flow") — showPanel does not grab panel focus here.
76
+ // DEBT-5: forward the deep-link target so the panel lands on the row.
77
+ if (ctx.showPanel) ctx.showPanel(id, pane as 'top' | 'bottom' | undefined, target);
51
78
  else {
52
- pm.open(id, pane as 'top' | 'bottom' | undefined);
79
+ pm.open(id, pane as 'top' | 'bottom' | undefined, target);
53
80
  pm.show();
54
- ctx.focusPanels?.();
55
81
  ctx.renderRequest();
56
82
  }
57
- ctx.print(`Panel opened: ${id}${pane ? ` (${pane} pane)` : ''}`);
83
+ if (redirectTarget) {
84
+ ctx.print(`"${id}" moved to the ${redirectTarget} modal — opening it.`);
85
+ } else {
86
+ ctx.print(`Panel opened: ${id}${pane ? ` (${pane} pane)` : ''}`);
87
+ }
58
88
  } catch (e) {
59
- ctx.print(`Error: ${summarizeError(e)}`);
89
+ // A deleted/unknown panel id throws "No panel type registered with
90
+ // id: <id>" from PanelManager.open — surface the same friendly line
91
+ // the bare-/panel path uses instead of leaking the raw error. Any
92
+ // genuinely unexpected failure still shows its real message.
93
+ const message = summarizeError(e);
94
+ if (message.includes('No panel type registered with id')) {
95
+ ctx.print(`Unknown panel "${id}". Use /panel list to see available panels.`);
96
+ } else {
97
+ ctx.print(`Error: ${message}`);
98
+ }
60
99
  }
61
100
  } else if (sub === 'close') {
62
101
  const id = args[1];
@@ -130,11 +169,12 @@ export function registerOperatorPanelCommand(registry: CommandRegistry): void {
130
169
  } else {
131
170
  const id = args[0]!;
132
171
  try {
172
+ // UX-C: bare `/panel <id>` is the same command path as `/panel open
173
+ // <id>` — composer stays focused.
133
174
  if (ctx.showPanel) ctx.showPanel(id);
134
175
  else {
135
176
  pm.open(id);
136
177
  pm.show();
137
- ctx.focusPanels?.();
138
178
  ctx.renderRequest();
139
179
  }
140
180
  } catch {
@@ -5,7 +5,18 @@ import type {
5
5
  ProjectPlanningState,
6
6
  } from '@pellux/goodvibes-sdk/platform/knowledge';
7
7
  import type { CommandRegistry } from '../command-registry.ts';
8
- import { requirePlanManager, requireSessionLineageTracker } from './runtime-services.ts';
8
+ import { openModalCommand, requirePlanManager, requireSessionLineageTracker } from './runtime-services.ts';
9
+
10
+ /**
11
+ * Single-token verbs that look like a `/plan` subcommand but are not real ones.
12
+ * A lone one of these is refused rather than seeded as a goal, so a stray verb
13
+ * can never overwrite the project goal with itself.
14
+ *
15
+ * DEBT-3: `dismiss` and `answer` are now REAL subcommands (handled above this
16
+ * guard), so they were removed from the refuse-list. `pause`/`stop`/`cancel`
17
+ * remain here — they still have no backing verb and must not seed a goal.
18
+ */
19
+ const PSEUDO_SUBCOMMAND_VERBS = new Set(['pause', 'stop', 'cancel']);
9
20
 
10
21
  function recordNextQuestion(
11
22
  state: Partial<ProjectPlanningState>,
@@ -43,7 +54,7 @@ function formatNextQuestion(question: ProjectPlanningQuestion | undefined): stri
43
54
  if (!question) return 'No next question recorded.';
44
55
  const lines = [`Next question: ${question.prompt}`];
45
56
  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.');
57
+ lines.push('Answer in the prompt, or open the Planning modal to choose/type an answer.');
47
58
  return lines.join('\n');
48
59
  }
49
60
 
@@ -51,7 +62,7 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
51
62
  registry.register({
52
63
  name: 'plan',
53
64
  description: 'Inspect or seed TUI-owned project planning state',
54
- usage: '[panel | approve | list | show <id> | mode | explain | override <strategy> | status | clear | <planning goal>]',
65
+ usage: '[panel | approve | dismiss | answer <n> <text> | list | show <id> | mode | explain | override <strategy> | status | clear | <planning goal>]',
55
66
  argsHint: '[panel|approve|status|<goal>]',
56
67
  async handler(args, ctx) {
57
68
  const planManager = requirePlanManager(ctx);
@@ -67,7 +78,7 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
67
78
 
68
79
  const projectPlanningService = ctx.workspace.projectPlanningService;
69
80
  const projectId = ctx.workspace.projectPlanningProjectId;
70
- const openProjectPlanningPanel = () => ctx.showPanel?.('project-planning');
81
+ const openProjectPlanningPanel = () => openModalCommand(ctx, 'planning-modal');
71
82
 
72
83
  if (args.length === 0) {
73
84
  if (projectPlanningService && projectId) {
@@ -165,6 +176,107 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
165
176
  return;
166
177
  }
167
178
 
179
+ // DEBT-3: /plan dismiss — archive the current plan. Dismisses the active
180
+ // execution plan (ExecutionPlanManager.dismiss, honest per-state) AND
181
+ // deactivates the project-planning interview state shown in the modal so a
182
+ // later /plan <goal> starts fresh. Mid-execution is refused outright.
183
+ if (args[0] === 'dismiss') {
184
+ const dismissal = planManager.dismiss(ctx.session.runtime.sessionId);
185
+ if (dismissal.outcome === 'requires-cancel') {
186
+ ctx.print(
187
+ `Plan "${dismissal.blockedBy?.title ?? 'active plan'}" is mid-execution and was not dismissed. ` +
188
+ `Run /workstream cancel to stop it first, then /plan dismiss.`,
189
+ );
190
+ return;
191
+ }
192
+ let planningNote = '';
193
+ if (projectPlanningService && projectId) {
194
+ const current = await projectPlanningService.getState({ projectId });
195
+ if (current.state && current.state.metadata?.['active'] === true) {
196
+ await projectPlanningService.upsertState({
197
+ projectId,
198
+ state: {
199
+ ...current.state,
200
+ metadata: {
201
+ ...(current.state.metadata ?? {}),
202
+ active: false,
203
+ dismissedAt: Date.now(),
204
+ dismissedFrom: 'plan-command',
205
+ },
206
+ },
207
+ });
208
+ planningNote = ' Project planning interview marked inactive.';
209
+ }
210
+ }
211
+ if (dismissal.outcome === 'dismissed') {
212
+ ctx.print(
213
+ `Dismissed plan "${dismissal.plan?.title ?? 'active plan'}" ` +
214
+ `(archived as dismissed; retained in /plan list; /plan <goal> starts fresh).${planningNote}`,
215
+ );
216
+ } else if (planningNote) {
217
+ ctx.print(`No active execution plan to dismiss.${planningNote}`);
218
+ } else {
219
+ ctx.print('No active plan or planning state to dismiss.');
220
+ }
221
+ return;
222
+ }
223
+
224
+ // DEBT-3: /plan answer <n|question-id> <text> — record a real answer to an
225
+ // open planning question (moves open → answered, consumed on next refine).
226
+ if (args[0] === 'answer') {
227
+ if (!projectPlanningService || !projectId) {
228
+ ctx.print('Project planning service is not available in this runtime.');
229
+ return;
230
+ }
231
+ const ref = args[1];
232
+ const answerText = args.slice(2).join(' ').trim();
233
+ if (!ref || !answerText) {
234
+ ctx.print('Usage: /plan answer <question-number|question-id> <your answer>');
235
+ return;
236
+ }
237
+ const asNum = Number(ref);
238
+ const selector = Number.isInteger(asNum) && asNum >= 1
239
+ ? { questionIndex: asNum - 1 }
240
+ : { questionId: ref };
241
+ const answerResult = await projectPlanningService.answerQuestion({ projectId, ...selector, answer: answerText });
242
+ if (!answerResult.answered) {
243
+ if (answerResult.reason === 'no-state') {
244
+ ctx.print('No project planning state exists yet. Seed it with /plan <goal>.');
245
+ } else if (answerResult.reason === 'question-not-found') {
246
+ const open = answerResult.openQuestions;
247
+ const listing = open.length > 0
248
+ ? open.map((question, index) => ` ${index + 1}. ${question.prompt} (${question.id})`).join('\n')
249
+ : ' (no open questions)';
250
+ ctx.print(`No open question matched "${ref}". Open questions:\n${listing}`);
251
+ } else {
252
+ ctx.print('Usage: /plan answer <question-number|question-id> <your answer>');
253
+ }
254
+ return;
255
+ }
256
+ openProjectPlanningPanel();
257
+ ctx.print(
258
+ `Recorded answer to: ${answerResult.question?.prompt ?? 'question'}\n` +
259
+ `Readiness: ${answerResult.evaluation.readiness}\n` +
260
+ formatNextQuestion(answerResult.evaluation.nextQuestion),
261
+ );
262
+ return;
263
+ }
264
+
265
+ // Defense (Wave 6 review): a single verb-looking token is almost never a
266
+ // real planning goal — it is a mistyped or removed subcommand. The
267
+ // Planning modal used to dispatch `/plan dismiss`, which has no
268
+ // subcommand and silently fell through to this free-form branch, seeding
269
+ // the goal with the literal "dismiss". Refuse to seed on a lone
270
+ // known-or-formerly-planned verb and point at the real usage instead of
271
+ // corrupting the goal.
272
+ if (args.length === 1 && PSEUDO_SUBCOMMAND_VERBS.has(args[0].toLowerCase())) {
273
+ ctx.print(
274
+ `Unknown /plan subcommand "${args[0]}" — did you mean panel, approve, list, show, or status? ` +
275
+ `To seed a planning goal, use /plan <a real sentence describing the change>.`,
276
+ );
277
+ return;
278
+ }
279
+
168
280
  const taskDescription = args.join(' ');
169
281
  if (!projectPlanningService || !projectId) {
170
282
  ctx.print('Project planning service is not available in this runtime.');
@@ -98,12 +98,7 @@ export function registerPlatformSandboxRuntimeCommands(registry: CommandRegistry
98
98
  const shellPaths = requireShellPaths(ctx);
99
99
  const sub = args[0] ?? 'open';
100
100
  if (sub === 'open' || sub === 'panel') {
101
- if (ctx.showPanel) ctx.showPanel('sandbox');
102
- else {
103
- const panelManager = requirePanelManager(ctx);
104
- panelManager.open('sandbox');
105
- panelManager.show();
106
- }
101
+ ctx.openModal?.('sandbox-modal'); // W6.1: sandbox panel -> config modal
107
102
  return;
108
103
  }
109
104
  if (sub === 'review') {