@pellux/goodvibes-tui 1.0.0 → 1.7.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 (209) hide show
  1. package/CHANGELOG.md +130 -127
  2. package/README.md +32 -14
  3. package/docs/foundation-artifacts/operator-contract.json +1 -1
  4. package/package.json +2 -2
  5. package/src/cli/ensure-goodvibes-gitignore.ts +32 -0
  6. package/src/cli/entrypoint.ts +2 -0
  7. package/src/core/alert-gating.ts +67 -0
  8. package/src/core/approval-alert.ts +72 -0
  9. package/src/core/budget-breach-notifier.ts +106 -0
  10. package/src/core/conversation-rendering.ts +19 -0
  11. package/src/core/conversation.ts +58 -0
  12. package/src/core/focus-tracker.ts +41 -0
  13. package/src/core/long-task-notifier.ts +33 -6
  14. package/src/core/stream-event-wiring.ts +104 -2
  15. package/src/core/stream-stall-watchdog.ts +41 -17
  16. package/src/core/system-message-router.ts +37 -51
  17. package/src/core/turn-cancellation.ts +20 -0
  18. package/src/core/turn-event-wiring.ts +64 -3
  19. package/src/export/cost-utils.ts +126 -8
  20. package/src/input/command-registry.ts +54 -1
  21. package/src/input/commands/checkpoint-runtime.ts +280 -0
  22. package/src/input/commands/codebase-runtime.ts +192 -0
  23. package/src/input/commands/diff-runtime.ts +61 -30
  24. package/src/input/commands/eval.ts +1 -1
  25. package/src/input/commands/health-runtime.ts +1 -1
  26. package/src/input/commands/image-runtime.ts +112 -0
  27. package/src/input/commands/intelligence-runtime.ts +11 -1
  28. package/src/input/commands/local-auth-runtime.ts +4 -1
  29. package/src/input/commands/local-runtime.ts +9 -3
  30. package/src/input/commands/marketplace-runtime.ts +2 -2
  31. package/src/input/commands/memory.ts +6 -1
  32. package/src/input/commands/operator-panel-runtime.ts +40 -24
  33. package/src/input/commands/planning-runtime.ts +26 -3
  34. package/src/input/commands/platform-sandbox-runtime.ts +1 -6
  35. package/src/input/commands/plugin-runtime.ts +2 -2
  36. package/src/input/commands/policy-dispatch.ts +21 -0
  37. package/src/input/commands/provider-accounts-runtime.ts +1 -1
  38. package/src/input/commands/qrcode-runtime.ts +3 -3
  39. package/src/input/commands/recall-review.ts +35 -0
  40. package/src/input/commands/remote-runtime.ts +3 -3
  41. package/src/input/commands/runtime-services.ts +54 -13
  42. package/src/input/commands/services-runtime.ts +1 -1
  43. package/src/input/commands/session-content.ts +12 -0
  44. package/src/input/commands/session-workflow.ts +19 -1
  45. package/src/input/commands/settings-sync-runtime.ts +1 -1
  46. package/src/input/commands/share-runtime.ts +9 -2
  47. package/src/input/commands/shell-core.ts +15 -7
  48. package/src/input/commands/skills-runtime.ts +2 -8
  49. package/src/input/commands/subscription-runtime.ts +2 -2
  50. package/src/input/commands/test-runtime.ts +277 -0
  51. package/src/input/commands/websearch-runtime.ts +101 -0
  52. package/src/input/commands/work-plan-runtime.ts +36 -10
  53. package/src/input/commands/workstream-runtime.ts +338 -0
  54. package/src/input/commands.ts +12 -0
  55. package/src/input/config-modal-types.ts +161 -0
  56. package/src/input/config-modal.ts +377 -0
  57. package/src/input/feed-context-factory.ts +7 -8
  58. package/src/input/handler-command-route.ts +27 -17
  59. package/src/input/handler-content-actions.ts +22 -8
  60. package/src/input/handler-feed-routes.ts +107 -7
  61. package/src/input/handler-feed.ts +49 -13
  62. package/src/input/handler-interactions.ts +3 -3
  63. package/src/input/handler-modal-routes.ts +65 -0
  64. package/src/input/handler-modal-stack.ts +3 -5
  65. package/src/input/handler-modal-token-routes.ts +16 -49
  66. package/src/input/handler-picker-routes.ts +11 -94
  67. package/src/input/handler-shortcuts.ts +24 -14
  68. package/src/input/handler-types.ts +3 -6
  69. package/src/input/handler-ui-state.ts +10 -22
  70. package/src/input/handler.ts +10 -28
  71. package/src/input/keybindings.ts +22 -8
  72. package/src/input/model-picker-types.ts +24 -6
  73. package/src/input/model-picker.ts +58 -0
  74. package/src/input/panel-integration-actions.ts +9 -170
  75. package/src/input/settings-modal-data.ts +95 -0
  76. package/src/input/settings-modal-mutations.ts +6 -4
  77. package/src/main.ts +41 -44
  78. package/src/panels/agent-inspector-shared.ts +35 -11
  79. package/src/panels/base-panel.ts +22 -1
  80. package/src/panels/builtin/agent.ts +21 -107
  81. package/src/panels/builtin/development.ts +16 -62
  82. package/src/panels/builtin/knowledge.ts +8 -32
  83. package/src/panels/builtin/operations.ts +84 -428
  84. package/src/panels/builtin/session.ts +21 -112
  85. package/src/panels/builtin/shared.ts +9 -43
  86. package/src/panels/builtin-modals.ts +218 -0
  87. package/src/panels/builtin-panels.ts +5 -0
  88. package/src/panels/cost-tracker-panel.ts +63 -14
  89. package/src/panels/diff-panel.ts +123 -60
  90. package/src/panels/eval-registry.ts +60 -0
  91. package/src/panels/fleet-panel.ts +800 -0
  92. package/src/panels/fleet-read-model.ts +477 -0
  93. package/src/panels/fleet-steer.ts +92 -0
  94. package/src/panels/fleet-stop.ts +125 -0
  95. package/src/panels/fleet-tabs.ts +230 -0
  96. package/src/panels/fleet-transcript.ts +356 -0
  97. package/src/panels/git-panel.ts +9 -9
  98. package/src/panels/index.ts +7 -31
  99. package/src/panels/local-auth-panel.ts +10 -0
  100. package/src/panels/modals/hooks-modal.ts +187 -0
  101. package/src/panels/modals/keybindings-modal.ts +151 -0
  102. package/src/panels/modals/knowledge-modal.ts +158 -0
  103. package/src/panels/modals/local-auth-modal.ts +132 -0
  104. package/src/panels/modals/marketplace-modal.ts +218 -0
  105. package/src/panels/modals/memory-modal.ts +180 -0
  106. package/src/panels/modals/modal-surface-helpers.ts +54 -0
  107. package/src/panels/modals/modal-theme.ts +36 -0
  108. package/src/panels/modals/pairing-modal.ts +144 -0
  109. package/src/panels/modals/planning-modal.ts +282 -0
  110. package/src/panels/modals/plugins-modal.ts +174 -0
  111. package/src/panels/modals/policy-modal.ts +256 -0
  112. package/src/panels/modals/provider-health-modal.ts +136 -0
  113. package/src/panels/modals/remote-modal.ts +115 -0
  114. package/src/panels/modals/sandbox-modal.ts +150 -0
  115. package/src/panels/modals/security-modal.ts +217 -0
  116. package/src/panels/modals/services-modal.ts +179 -0
  117. package/src/panels/modals/settings-sync-modal.ts +142 -0
  118. package/src/panels/modals/skills-modal.ts +189 -0
  119. package/src/panels/modals/subscription-modal.ts +172 -0
  120. package/src/panels/modals/work-plan-modal.ts +149 -0
  121. package/src/panels/panel-confirm-overlay.ts +72 -0
  122. package/src/panels/panel-manager.ts +108 -5
  123. package/src/panels/project-planning-answer-actions.ts +4 -2
  124. package/src/panels/scrollable-list-panel.ts +9 -0
  125. package/src/panels/skills-panel.ts +7 -51
  126. package/src/panels/token-budget-panel.ts +5 -3
  127. package/src/panels/types.ts +26 -0
  128. package/src/permissions/hunk-selection.ts +179 -0
  129. package/src/permissions/prompt.ts +60 -4
  130. package/src/renderer/compaction-history-modal.ts +19 -3
  131. package/src/renderer/compaction-preview.ts +13 -2
  132. package/src/renderer/compaction-quality.ts +100 -0
  133. package/src/renderer/config-modal.ts +81 -0
  134. package/src/renderer/conversation-overlays.ts +10 -17
  135. package/src/renderer/fleet-tab-strip.ts +56 -0
  136. package/src/renderer/footer-tips.ts +10 -2
  137. package/src/renderer/git-status.ts +40 -0
  138. package/src/renderer/help-overlay.ts +2 -2
  139. package/src/renderer/model-workspace.ts +44 -1
  140. package/src/renderer/panel-workspace-bar.ts +8 -2
  141. package/src/renderer/shell-surface.ts +8 -1
  142. package/src/renderer/turn-injection.ts +114 -0
  143. package/src/renderer/ui-factory.ts +91 -7
  144. package/src/runtime/bootstrap-command-context.ts +24 -1
  145. package/src/runtime/bootstrap-command-parts.ts +36 -7
  146. package/src/runtime/bootstrap-core.ts +19 -4
  147. package/src/runtime/bootstrap-hook-bridge.ts +5 -0
  148. package/src/runtime/bootstrap-shell.ts +41 -17
  149. package/src/runtime/bootstrap.ts +11 -17
  150. package/src/runtime/code-index-services.ts +112 -0
  151. package/src/runtime/orchestrator-core-services.ts +49 -0
  152. package/src/runtime/process-lifecycle.ts +3 -1
  153. package/src/runtime/services.ts +91 -43
  154. package/src/runtime/ui-services.ts +8 -0
  155. package/src/runtime/workstream-services.ts +195 -0
  156. package/src/shell/blocking-input.ts +22 -1
  157. package/src/shell/ui-openers.ts +129 -17
  158. package/src/utils/format-duration.ts +8 -18
  159. package/src/utils/splash-lines.ts +1 -1
  160. package/src/version.ts +1 -1
  161. package/src/panels/agent-inspector-panel.ts +0 -786
  162. package/src/panels/approval-panel.ts +0 -252
  163. package/src/panels/automation-control-panel.ts +0 -479
  164. package/src/panels/cockpit-panel.ts +0 -481
  165. package/src/panels/cockpit-read-model.ts +0 -233
  166. package/src/panels/communication-panel.ts +0 -256
  167. package/src/panels/control-plane-panel.ts +0 -470
  168. package/src/panels/debug-panel.ts +0 -609
  169. package/src/panels/docs-panel.ts +0 -375
  170. package/src/panels/eval-panel.ts +0 -627
  171. package/src/panels/file-explorer-panel.ts +0 -664
  172. package/src/panels/file-preview-panel.ts +0 -517
  173. package/src/panels/hooks-panel.ts +0 -401
  174. package/src/panels/incident-review-panel.ts +0 -406
  175. package/src/panels/intelligence-panel.ts +0 -383
  176. package/src/panels/knowledge-graph-panel.ts +0 -506
  177. package/src/panels/marketplace-panel.ts +0 -399
  178. package/src/panels/memory-panel.ts +0 -558
  179. package/src/panels/ops-control-panel.ts +0 -329
  180. package/src/panels/ops-strategy-panel.ts +0 -321
  181. package/src/panels/orchestration-panel.ts +0 -465
  182. package/src/panels/panel-list-panel.ts +0 -557
  183. package/src/panels/plan-dashboard-panel.ts +0 -707
  184. package/src/panels/policy-panel.ts +0 -517
  185. package/src/panels/project-planning-panel.ts +0 -721
  186. package/src/panels/provider-health-panel.ts +0 -678
  187. package/src/panels/provider-health-tracker.ts +0 -306
  188. package/src/panels/provider-health-views.ts +0 -560
  189. package/src/panels/qr-panel.ts +0 -280
  190. package/src/panels/remote-panel.ts +0 -534
  191. package/src/panels/routes-panel.ts +0 -241
  192. package/src/panels/sandbox-panel.ts +0 -456
  193. package/src/panels/security-panel.ts +0 -447
  194. package/src/panels/services-panel.ts +0 -329
  195. package/src/panels/session-browser-panel.ts +0 -487
  196. package/src/panels/settings-sync-panel.ts +0 -398
  197. package/src/panels/subscription-panel.ts +0 -342
  198. package/src/panels/symbol-outline-panel.ts +0 -619
  199. package/src/panels/system-messages-panel.ts +0 -364
  200. package/src/panels/tasks-panel.ts +0 -608
  201. package/src/panels/thinking-panel.ts +0 -333
  202. package/src/panels/tool-inspector-panel.ts +0 -537
  203. package/src/panels/work-plan-panel.ts +0 -530
  204. package/src/panels/worktree-panel.ts +0 -360
  205. package/src/panels/wrfc-panel.ts +0 -790
  206. package/src/renderer/agent-detail-modal.ts +0 -465
  207. package/src/renderer/live-tail-modal.ts +0 -156
  208. package/src/renderer/process-modal.ts +0 -656
  209. 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,7 +17,7 @@ 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 ─────────────────────────────────────────────────────────
@@ -76,6 +76,10 @@ export const recallCommand: SlashCommand = {
76
76
  handleRecallExplain(rest, context);
77
77
  break;
78
78
 
79
+ case 'injections':
80
+ handleRecallInjections(rest, context);
81
+ break;
82
+
79
83
  case 'promote':
80
84
  handleRecallPromote(rest, context);
81
85
  break;
@@ -134,6 +138,7 @@ export const recallCommand: SlashCommand = {
134
138
  ' stale <id> [reason...] — Mark a record stale with an operator reason',
135
139
  ' contradict <id> [reason...] — Mark a record contradicted with an operator reason',
136
140
  ' explain <task...> [--scope <path> ...] — Show the knowledge records that would be injected for a task',
141
+ ' injections [agentId] — Show per-turn passive knowledge injection records (Wave-5); no id shows the main session, an id shows that spawned agent',
137
142
  ' promote <id> <scope> — Promote a memory record into session|project|team scope',
138
143
  ' export <path> [--scope <scope>] [--cls <class>] — Export a durable knowledge bundle',
139
144
  ' import <path> — Import a durable knowledge bundle',
@@ -13,31 +13,29 @@ export function registerOperatorPanelCommand(registry: CommandRegistry): void {
13
13
  const pm = requirePanelManager(ctx);
14
14
  const sub = args[0]?.toLowerCase() ?? '';
15
15
  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
- }
16
+ // W6.1 (the purge): 'panel-list' (the browse-all-panels picker) was
17
+ // DELETE-disposition — a picker over a handful of panels is dead
18
+ // weight now (see the Ctrl+P selectionModal repoint in
19
+ // shell/ui-openers.ts for its interactive replacement). Bare
20
+ // `/panel` and `/panel toggle` now do what "toggle" actually means:
21
+ // toggle the workspace, which opens 'fleet' by default when nothing
22
+ // is open (PanelManager.toggle()) rather than force-opening a
23
+ // specific browse panel.
24
+ pm.toggle();
25
+ ctx.renderRequest();
28
26
  } 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();
27
+ // Print a text listing instead of opening a picker panel — grouped
28
+ // by category from the live registry, so this can never drift from
29
+ // what's actually registered.
30
+ const byCategory = pm.getTypesByCategory();
31
+ const lines: string[] = [];
32
+ for (const [category, entries] of byCategory) {
33
+ lines.push(`${category}:`);
34
+ for (const entry of entries) {
35
+ lines.push(` ${entry.icon} ${entry.id} — ${entry.name}`);
36
36
  }
37
- } catch {
38
- pm.show();
39
- ctx.renderRequest();
40
37
  }
38
+ ctx.print(lines.length > 0 ? lines.join('\n') : 'No panels registered.');
41
39
  } else if (sub === 'open') {
42
40
  const id = args[1];
43
41
  const pane = args[2]?.toLowerCase();
@@ -47,6 +45,11 @@ export function registerOperatorPanelCommand(registry: CommandRegistry): void {
47
45
  return;
48
46
  }
49
47
  try {
48
+ // A MIGRATE-TO-MODAL id (e.g. 'sessions' -> the session-picker modal)
49
+ // resolves to a modal, not a panel: opening it fires the redirect and
50
+ // no panel lands in the workspace. Report that honestly rather than
51
+ // claiming "Panel opened: <id>".
52
+ const redirectTarget = pm.getModalRedirect(id);
50
53
  if (ctx.showPanel) ctx.showPanel(id, pane as 'top' | 'bottom' | undefined);
51
54
  else {
52
55
  pm.open(id, pane as 'top' | 'bottom' | undefined);
@@ -54,9 +57,22 @@ export function registerOperatorPanelCommand(registry: CommandRegistry): void {
54
57
  ctx.focusPanels?.();
55
58
  ctx.renderRequest();
56
59
  }
57
- ctx.print(`Panel opened: ${id}${pane ? ` (${pane} pane)` : ''}`);
60
+ if (redirectTarget) {
61
+ ctx.print(`"${id}" moved to the ${redirectTarget} modal — opening it.`);
62
+ } else {
63
+ ctx.print(`Panel opened: ${id}${pane ? ` (${pane} pane)` : ''}`);
64
+ }
58
65
  } catch (e) {
59
- ctx.print(`Error: ${summarizeError(e)}`);
66
+ // A deleted/unknown panel id throws "No panel type registered with
67
+ // id: <id>" from PanelManager.open — surface the same friendly line
68
+ // the bare-/panel path uses instead of leaking the raw error. Any
69
+ // genuinely unexpected failure still shows its real message.
70
+ const message = summarizeError(e);
71
+ if (message.includes('No panel type registered with id')) {
72
+ ctx.print(`Unknown panel "${id}". Use /panel list to see available panels.`);
73
+ } else {
74
+ ctx.print(`Error: ${message}`);
75
+ }
60
76
  }
61
77
  } else if (sub === 'close') {
62
78
  const id = args[1];
@@ -5,7 +5,15 @@ 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
+ * (some, like `dismiss`, were dispatched by UI that assumed a subcommand that
13
+ * never existed). A lone one of these is refused rather than seeded as a goal,
14
+ * so a stray verb can never overwrite the project goal with itself.
15
+ */
16
+ const PSEUDO_SUBCOMMAND_VERBS = new Set(['dismiss', 'answer', 'pause', 'stop', 'cancel']);
9
17
 
10
18
  function recordNextQuestion(
11
19
  state: Partial<ProjectPlanningState>,
@@ -43,7 +51,7 @@ function formatNextQuestion(question: ProjectPlanningQuestion | undefined): stri
43
51
  if (!question) return 'No next question recorded.';
44
52
  const lines = [`Next question: ${question.prompt}`];
45
53
  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.');
54
+ lines.push('Answer in the prompt, or open the Planning modal to choose/type an answer.');
47
55
  return lines.join('\n');
48
56
  }
49
57
 
@@ -67,7 +75,7 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
67
75
 
68
76
  const projectPlanningService = ctx.workspace.projectPlanningService;
69
77
  const projectId = ctx.workspace.projectPlanningProjectId;
70
- const openProjectPlanningPanel = () => ctx.showPanel?.('project-planning');
78
+ const openProjectPlanningPanel = () => openModalCommand(ctx, 'planning-modal');
71
79
 
72
80
  if (args.length === 0) {
73
81
  if (projectPlanningService && projectId) {
@@ -165,6 +173,21 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
165
173
  return;
166
174
  }
167
175
 
176
+ // Defense (Wave 6 review): a single verb-looking token is almost never a
177
+ // real planning goal — it is a mistyped or removed subcommand. The
178
+ // Planning modal used to dispatch `/plan dismiss`, which has no
179
+ // subcommand and silently fell through to this free-form branch, seeding
180
+ // the goal with the literal "dismiss". Refuse to seed on a lone
181
+ // known-or-formerly-planned verb and point at the real usage instead of
182
+ // corrupting the goal.
183
+ if (args.length === 1 && PSEUDO_SUBCOMMAND_VERBS.has(args[0].toLowerCase())) {
184
+ ctx.print(
185
+ `Unknown /plan subcommand "${args[0]}" — did you mean panel, approve, list, show, or status? ` +
186
+ `To seed a planning goal, use /plan <a real sentence describing the change>.`,
187
+ );
188
+ return;
189
+ }
190
+
168
191
  const taskDescription = args.join(' ');
169
192
  if (!projectPlanningService || !projectId) {
170
193
  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') {
@@ -13,7 +13,7 @@ import {
13
13
  upsertEcosystemCatalogEntry,
14
14
  uninstallEcosystemCatalogEntry,
15
15
  } from '@/runtime/index.ts';
16
- import { requireEcosystemCatalogPaths, requirePluginPathOptions } from './runtime-services.ts';
16
+ import { openModalCommand, requireEcosystemCatalogPaths, requirePluginPathOptions } from './runtime-services.ts';
17
17
 
18
18
  export function registerPluginRuntimeCommands(registry: CommandRegistry): void {
19
19
  registry.register({
@@ -33,7 +33,7 @@ export function registerPluginRuntimeCommands(registry: CommandRegistry): void {
33
33
  const sub = args[0];
34
34
 
35
35
  if (!sub || sub === 'open' || sub === 'panel') {
36
- if (ctx.showPanel) ctx.showPanel('plugins');
36
+ openModalCommand(ctx, 'plugins-modal');
37
37
  return;
38
38
  }
39
39
 
@@ -301,6 +301,23 @@ export function renderPolicyUsage(): string {
301
301
  ].join('\n');
302
302
  }
303
303
 
304
+ // W6 command-path parity: the policy modal dropped its 'r' (record a
305
+ // divergence-dashboard trend sample) action because the panel used to call
306
+ // this._state.recordTrendEntry() directly and /policy had no equivalent verb.
307
+ // This exposes it as a thin wrapper: recordTrendEntry() forwards to the attached
308
+ // DivergencePanel, so it only captures a sample while a simulation dashboard is
309
+ // active — reported honestly here rather than silently no-op'ing.
310
+ function handleRecordTrend(_args: string[], context: CommandContext): void {
311
+ const policyState = getPolicyState(context);
312
+ if (!policyState.getDashboard()) {
313
+ context.print('[policy] No active simulation dashboard — nothing to sample. Run `/policy simulate` first, then `/policy record-trend`.');
314
+ return;
315
+ }
316
+ policyState.recordTrendEntry();
317
+ const snap = policyState.getSnapshot();
318
+ context.print(`[policy] Recorded a divergence trend sample (${snap.divergence?.trend.length ?? 0} point(s) total).`);
319
+ }
320
+
304
321
  export async function dispatchPolicyCommand(args: string[], context: CommandContext): Promise<void> {
305
322
  const [sub, ...rest] = args;
306
323
  switch (sub) {
@@ -332,6 +349,10 @@ export async function dispatchPolicyCommand(args: string[], context: CommandCont
332
349
  case 'st':
333
350
  await handleStatus(rest, context);
334
351
  break;
352
+ case 'record-trend':
353
+ case 'trend':
354
+ handleRecordTrend(rest, context);
355
+ break;
335
356
  default:
336
357
  context.print(renderPolicyUsage());
337
358
  break;
@@ -29,7 +29,7 @@ export function registerProviderAccountsRuntimeCommands(registry: CommandRegistr
29
29
  async handler(args, ctx) {
30
30
  const sub = (args[0] ?? 'review').toLowerCase();
31
31
  if (sub === 'panel' || sub === 'open') {
32
- openCommandPanel(ctx, 'accounts');
32
+ ctx.openModal?.('providers-modal'); // W6.1: accounts alias -> provider-health modal
33
33
  return;
34
34
  }
35
35
  const snapshot = await loadProviderAccountSnapshot(ctx);
@@ -1,5 +1,5 @@
1
1
  import type { CommandRegistry } from '../command-registry.ts';
2
- import { openCommandPanel } from './runtime-services.ts';
2
+ import { openModalCommand } from './runtime-services.ts';
3
3
 
4
4
  /**
5
5
  * Register the /qrcode command.
@@ -11,9 +11,9 @@ export function registerQrcodeRuntimeCommands(registry: CommandRegistry): void {
11
11
  registry.register({
12
12
  name: 'qrcode',
13
13
  aliases: ['qr', 'pair'],
14
- description: 'Open the QR code panel for companion app pairing',
14
+ description: 'Open the companion-app pairing modal (QR code)',
15
15
  handler(_args, ctx) {
16
- openCommandPanel(ctx, 'qr-code');
16
+ openModalCommand(ctx, 'pairing-modal');
17
17
  },
18
18
  });
19
19
  }
@@ -1,6 +1,7 @@
1
1
  import type { CommandContext } from '../command-registry.ts';
2
2
  import { VALID_REVIEW_STATES, VALID_SCOPES, isValidReviewState, isValidScope } from './recall-shared.ts';
3
3
  import { getMemoryApi } from './recall-query.ts';
4
+ import { buildMainSessionTurnInjectionsText, buildTurnInjectionsText } from '../../renderer/turn-injection.ts';
4
5
 
5
6
  export function handleRecallQueue(args: string[], context: CommandContext): void {
6
7
  const memory = getMemoryApi(context);
@@ -102,6 +103,40 @@ export function handleRecallExplain(args: string[], context: CommandContext): vo
102
103
  context.print(explanation.prompt ?? '[recall] No explainable project knowledge was selected.');
103
104
  }
104
105
 
106
+ /**
107
+ * W5.2 (wo803) — per-turn passive knowledge injection inspectability.
108
+ *
109
+ * With an explicit agent id, reads `AgentRecord.turnInjections` (Wave-5 wo801)
110
+ * via `context.ops.agentManager.exportState()` — the CommandContext-exposed
111
+ * `ShellAgentManagerService` doesn't have a `getStatus`/`list` pair, but
112
+ * `exportState()` returns the same full `AgentRecord[]` (it's what
113
+ * compactConversation() already uses for the same reason).
114
+ *
115
+ * With NO agent id (wo805 wiring), renders the MAIN interactive session's own
116
+ * per-turn injection ring — `Orchestrator.getTurnInjections()`, threaded onto
117
+ * the command context as `session.getMainSessionTurnInjections`. As of wo805 the
118
+ * main session DOES route through the passive-injection engine, so this is the
119
+ * honest main-session default (previously there was no record source, so the
120
+ * no-id path could only print a usage hint). When the accessor isn't wired
121
+ * (headless/test contexts built without an orchestrator), the main-session
122
+ * renderer's honest empty state stands in.
123
+ */
124
+ export function handleRecallInjections(args: string[], context: CommandContext): void {
125
+ const agentId = args[0];
126
+ if (!agentId) {
127
+ const mainSessionInjections = context.session.getMainSessionTurnInjections?.() ?? [];
128
+ context.print(buildMainSessionTurnInjectionsText(mainSessionInjections));
129
+ return;
130
+ }
131
+ const records = context.ops.agentManager?.exportState() ?? [];
132
+ const record = records.find((candidate) => candidate.id === agentId);
133
+ if (!record) {
134
+ context.print(`[recall] Agent not found: ${agentId}`);
135
+ return;
136
+ }
137
+ context.print(buildTurnInjectionsText(agentId, record.turnInjections ?? []));
138
+ }
139
+
105
140
  export function handleRecallPromote(args: string[], context: CommandContext): void {
106
141
  const memory = getMemoryApi(context);
107
142
  if (!memory) {
@@ -47,11 +47,11 @@ export function registerRemoteRuntimeCommands(registry: CommandRegistry): void {
47
47
  usage: '[list | show [agentId] | supervisor [runnerId] | capabilities [runnerId] | recover [runnerId] | setup [export <path>] | env [export <path>] | tunnel [review|export <path>] | bootstrap [export <path>|inspect <path>] | session <export|inspect|import> <path> | pool <list|show|create|assign|unassign> ... | dispatch [template] <description> | dispatch-pool <pool> [template] <description> | contract [agentId] | cancel <agentId> | export <agentId> [path] | artifact list | artifact show <id> | artifact export <id> [path] | review <id> | rerun-local <id> | import <path>]',
48
48
  async handler(args, ctx) {
49
49
  if (args.length === 0) {
50
- if (ctx.openRemotePanel) {
51
- ctx.openRemotePanel();
50
+ if (ctx.openModal) {
51
+ ctx.openModal('remote-modal'); // W6.1: remote panel -> config modal
52
52
  return;
53
53
  }
54
- ctx.print('Remote panel is not available in this runtime.');
54
+ ctx.print('Remote surface is not available in this runtime.');
55
55
  return;
56
56
  }
57
57