@pellux/goodvibes-tui 0.21.0 → 0.22.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 (54) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +1 -1
  3. package/package.json +2 -1
  4. package/src/cli/completions/generate.ts +4 -8
  5. package/src/cli/entrypoint.ts +6 -0
  6. package/src/cli/parser.ts +17 -0
  7. package/src/cli/types.ts +2 -0
  8. package/src/config/goodvibes-home-audit.ts +2 -0
  9. package/src/core/context-auto-compact.ts +77 -0
  10. package/src/core/turn-event-wiring.ts +124 -0
  11. package/src/daemon/cli.ts +5 -0
  12. package/src/input/command-registry.ts +1 -0
  13. package/src/input/commands/control-room-runtime.ts +5 -5
  14. package/src/input/commands/provider.ts +57 -3
  15. package/src/input/commands/session-workflow.ts +8 -16
  16. package/src/input/commands/session.ts +70 -20
  17. package/src/input/commands.ts +0 -2
  18. package/src/input/handler-modal-routes.ts +37 -0
  19. package/src/input/handler-modal-token-routes.ts +19 -5
  20. package/src/input/handler-onboarding.ts +18 -0
  21. package/src/input/handler.ts +1 -0
  22. package/src/input/onboarding/onboarding-wizard-apply.ts +10 -0
  23. package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +14 -0
  24. package/src/input/onboarding/onboarding-wizard-steps.ts +6 -0
  25. package/src/input/onboarding/onboarding-wizard-validation.ts +77 -0
  26. package/src/input/settings-modal-behavior.ts +5 -0
  27. package/src/input/settings-modal-data.ts +77 -3
  28. package/src/input/settings-modal-mutations.ts +3 -0
  29. package/src/input/settings-modal-reset.ts +154 -0
  30. package/src/input/settings-modal.ts +55 -13
  31. package/src/main.ts +36 -28
  32. package/src/panels/agent-inspector-panel.ts +120 -18
  33. package/src/panels/agent-inspector-shared.ts +29 -0
  34. package/src/panels/builtin/development.ts +1 -0
  35. package/src/panels/builtin/knowledge.ts +14 -13
  36. package/src/panels/builtin/operations.ts +22 -1
  37. package/src/panels/builtin/shared.ts +7 -0
  38. package/src/panels/cockpit-panel.ts +123 -3
  39. package/src/panels/cockpit-read-model.ts +232 -0
  40. package/src/panels/index.ts +1 -1
  41. package/src/panels/knowledge-graph-panel.ts +84 -0
  42. package/src/panels/memory-panel.ts +370 -40
  43. package/src/panels/session-maintenance.ts +66 -15
  44. package/src/renderer/agent-detail-modal.ts +107 -3
  45. package/src/renderer/context-status-hint.ts +54 -0
  46. package/src/renderer/settings-modal.ts +14 -3
  47. package/src/renderer/shell-surface.ts +10 -0
  48. package/src/runtime/bootstrap-command-parts.ts +4 -0
  49. package/src/runtime/bootstrap-core.ts +24 -0
  50. package/src/runtime/bootstrap-shell.ts +11 -0
  51. package/src/runtime/bootstrap.ts +7 -0
  52. package/src/runtime/services.ts +6 -1
  53. package/src/version.ts +1 -1
  54. package/src/panels/knowledge-panel.ts +0 -343
package/CHANGELOG.md CHANGED
@@ -4,6 +4,29 @@ All notable changes to GoodVibes TUI.
4
4
 
5
5
  ---
6
6
 
7
+ ## [0.22.0] — 2026-06-12
8
+
9
+ Second best-in-class program release: the backlog tail, the providers/failover track opened, and the release pipeline made fully honest. Every change passed independent review at 10/10 before commit.
10
+
11
+ ### Features
12
+ - Added a Cockpit control room: live agent roster (status, stalled flag, real cost/tokens), inspect and confirm-gated cancel action keys on the shared orphan-free cancel path.
13
+ - Added /session as the single front-door for session work: lifecycle subcommands (list, rename, resume, fork, save, info, export, search, delete, events, groups, hotspots) and orchestration (link-task with cycle detection, handoff, graph, cancel with task/subtree/session scopes); alias /sess.
14
+ - Added a merged memory panel (records + review queue via Tab) with the knowledge-graph view on its own panel; /project-memory routes to records; session scratch surface renamed to notes.
15
+ - Added settings reset-category and reset-all (Shift+R / Ctrl+Shift+R, confirm-gated, runtime-synced), schema-derived unknown-key warnings at load, restart hints from a typed key table, and width-honest footers proven at 80 and 120 columns.
16
+ - Added wizard required-field gating: apply blocks with per-field messages and a focus jump to the first offender.
17
+ - Added the provider optimizer enable switch: /provider optimizer on|off drives the live instance and persists; /provider route auto says honestly when the optimizer is off.
18
+ - Added wizard security hardening: Cloudflare tunnel apply sets trustProxy with the residual risk named until SDK header validation ships; TLS plaintext hard-warn in the wizard and a startup banner; CORS guidance.
19
+ - Added context truth: auto-compact runs only when behavior.autoCompact is enabled (threshold from config, percent converted correctly), with a passive suggest-compact status hint and honest transcript notices.
20
+ - Added tts.speed to settings, bridged end-to-end today (modal to synthesis call); explicit tts.* defaults with truthful modified markers.
21
+
22
+ ### Fixes
23
+ - Fixed CLI flag truth end-to-end: a drift-proof parity test extracts flags from parser source and asserts equality across help, completions, and docs; ghost completion flags removed; --output-format deprecated with a stderr warning; session lifecycle flags are mutually exclusive with a clear error.
24
+ - Fixed onboarding marker timing residue and wizard copy; sessions, recovery, and WRFC chains read through version-gated, quarantining loaders everywhere.
25
+
26
+ ### Internal
27
+ - Performance gate is real: fail-closed in CI without a baseline, headless startup and frame benches sharing one methodology and one CI-safe budget source (p95 16ms, p99 110ms with runner headroom), committed ratchet baseline.
28
+ - Per-agent stall watchdog constants shared; agent cost/token surfaces fabricated-number-free; concurrency-safe test temp dirs; eval gate fail-closed with a committed baseline; 540 test files green.
29
+
7
30
  ## [0.21.0] — 2026-06-12
8
31
 
9
32
  First release of the best-in-class program: a full UX-first review of the codebase followed by WRFC-gated remediation (every change passed independent review at 10/10 before commit).
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.21.0-blue.svg)](https://github.com/mgd34msu/goodvibes-tui)
5
+ [![Version](https://img.shields.io/badge/version-0.22.0-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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-tui",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
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",
@@ -57,6 +57,7 @@
57
57
  "build:prod": "bun run scripts/build.ts",
58
58
  "build:all": "bun run scripts/build.ts --all",
59
59
  "perf:check": "bun run scripts/perf-check.ts",
60
+ "perf:baseline": "GOODVIBES_PERF_SAVE_BASELINE=1 bun run scripts/perf-check.ts",
60
61
  "architecture:check": "bun run scripts/check-architecture.ts",
61
62
  "audit:home": "bun run scripts/audit-goodvibes-home.ts",
62
63
  "foundation:artifacts": "bun run scripts/export-foundation-artifacts.ts",
@@ -137,12 +137,8 @@ export const GLOBAL_FLAGS: readonly CompletionFlag[] = [
137
137
  },
138
138
  { name: '--continue', takesValue: false, description: 'Continue the latest session' },
139
139
  { name: '--fork', takesValue: false, description: 'Fork session when supported' },
140
- { name: '--raw-output', takesValue: false, description: 'Raw output mode' },
141
- {
142
- name: '--accept-raw-output-risk',
143
- takesValue: false,
144
- description: 'Acknowledge raw output risk',
145
- },
140
+ { name: '--yes', short: '-y', takesValue: false, description: 'Auto-confirm prompts (non-interactive)' },
141
+ { name: '--non-interactive', takesValue: false, description: 'Disable all interactive prompts (implies --yes)' },
146
142
  ] as const;
147
143
 
148
144
  // ---------------------------------------------------------------------------
@@ -512,8 +508,8 @@ export function generateZsh(surface: CompletionSurface): string {
512
508
  lines.push(` '(-s --session)'{-s,--session}'[Use a specific session]:id:' \\`);
513
509
  lines.push(` '--continue[Continue the latest session]' \\`);
514
510
  lines.push(` '--fork[Fork session when supported]' \\`);
515
- lines.push(` '--raw-output[Raw output mode]' \\`);
516
- lines.push(` '--accept-raw-output-risk[Acknowledge raw output risk]' \\`);
511
+ lines.push(` '(-y --yes)'{-y,--yes}'[Auto-confirm prompts]' \\`);
512
+ lines.push(` '--non-interactive[Disable all interactive prompts]' \\`);
517
513
  lines.push(` '1:command:->cmd' \\`);
518
514
  lines.push(` '*:args:->args';`);
519
515
  lines.push('');
@@ -61,6 +61,12 @@ export async function prepareShellCliRuntime(
61
61
  process.exit(2);
62
62
  }
63
63
 
64
+ if (cli.warnings.length > 0) {
65
+ for (const warning of cli.warnings) {
66
+ console.warn(`[goodvibes] warning: ${warning}`);
67
+ }
68
+ }
69
+
64
70
  if (cli.flags.help || cli.command === 'help') {
65
71
  const helpTopic = cli.command === 'help'
66
72
  ? cli.commandArgs[0]
package/src/cli/parser.ts CHANGED
@@ -174,6 +174,7 @@ export function parseGoodVibesCli(
174
174
  const commandArgs: string[] = [];
175
175
  const positionals: string[] = [];
176
176
  const errors: string[] = [];
177
+ const warnings: string[] = [];
177
178
  let sawCommand = false;
178
179
  let passthrough = false;
179
180
 
@@ -292,6 +293,9 @@ export function parseGoodVibesCli(
292
293
  const consumed = getValue(argv, index, inlineValue, name, errors);
293
294
  index = consumed.nextIndex;
294
295
  flags = withFlag(flags, 'outputFormat', normalizeOutputFormat(consumed.value, name, errors));
296
+ if (name === '--output-format') {
297
+ warnings.push('--output-format is deprecated; use --output (or -o) instead.');
298
+ }
295
299
  continue;
296
300
  }
297
301
  if (name === '--config' || name === '-c') {
@@ -353,6 +357,18 @@ export function parseGoodVibesCli(
353
357
  errors.push(`Unknown command: ${rawCommand}`);
354
358
  }
355
359
 
360
+ // Session lifecycle conflict detection — only one of --continue / --resume / --fork may be used.
361
+ const sessionLifecycleFlags = [
362
+ flags.continueLast ? '--continue' : undefined,
363
+ flags.resume !== undefined ? '--resume' : undefined,
364
+ flags.fork !== undefined ? '--fork' : undefined,
365
+ ].filter((f): f is string => f !== undefined);
366
+ if (sessionLifecycleFlags.length > 1) {
367
+ errors.push(
368
+ `Conflicting session lifecycle flags: ${sessionLifecycleFlags.join(' and ')}. Use only one of --continue, --resume, or --fork.`,
369
+ );
370
+ }
371
+
356
372
  return {
357
373
  binary,
358
374
  command,
@@ -361,5 +377,6 @@ export function parseGoodVibesCli(
361
377
  positionals,
362
378
  flags,
363
379
  errors,
380
+ warnings,
364
381
  };
365
382
  }
package/src/cli/types.ts CHANGED
@@ -68,6 +68,8 @@ export interface GoodVibesCliParseResult {
68
68
  readonly positionals: readonly string[];
69
69
  readonly flags: GoodVibesCliFlags;
70
70
  readonly errors: readonly string[];
71
+ /** Deprecation and soft-warning messages (non-fatal). Callers should surface these to users. */
72
+ readonly warnings: readonly string[];
71
73
  }
72
74
 
73
75
  export interface CliCommandRuntime {
@@ -117,6 +117,8 @@ const KNOWN_DYNAMIC_KEYS = [
117
117
  /^featureFlags(?:\.|$)/,
118
118
  /^notifications\.webhookUrls$/,
119
119
  /^wrfc\.gates$/,
120
+ // TUI-bridged setting awaiting SDK schema registration (handoff Item 5b)
121
+ /^tts\.speed$/,
120
122
  ];
121
123
 
122
124
  export const GOODVIBES_ALLOWED_WRITE_ROOTS = ['tui/', 'daemon/'] as const;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Auto-compaction helper — TASK-058.
3
+ *
4
+ * Evaluates whether auto-compact should run after a turn completes and, if so,
5
+ * triggers compaction and posts an honest transcript notice.
6
+ *
7
+ * behavior.autoCompactThreshold: SDK schema range [10, 100], default 80.
8
+ * Auto-compact is active whenever the threshold is in its valid range (>0).
9
+ * The display/suggestion path (hint + meter) is always active regardless.
10
+ */
11
+
12
+ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
13
+ import type { ConversationManager } from './conversation';
14
+ import type { ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
15
+ import type { SystemMessageRouter } from './system-message-router';
16
+ import { logger } from '@pellux/goodvibes-sdk/platform/utils';
17
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
18
+
19
+ export interface AutoCompactDeps {
20
+ readonly configManager: Pick<ConfigManager, 'get'>;
21
+ readonly conversation: ConversationManager;
22
+ readonly providerRegistry: ProviderRegistry;
23
+ readonly systemMessageRouter: SystemMessageRouter;
24
+ readonly model: string;
25
+ readonly provider: string;
26
+ readonly lastInputTokens: number;
27
+ readonly contextWindow: number;
28
+ }
29
+
30
+ /**
31
+ * Run after each TURN_COMPLETED event.
32
+ *
33
+ * Reads behavior.autoCompactThreshold from config (SDK default: 80, range [10, 100]).
34
+ * When usage is at or above the threshold, compacts the conversation and posts
35
+ * an honest transcript notice so the user understands any summary discontinuity.
36
+ *
37
+ * This function is intentionally non-throwing; failures are logged and
38
+ * surfaced via the system message router.
39
+ */
40
+ export async function maybeAutoCompact(deps: AutoCompactDeps): Promise<void> {
41
+ // SDK schema default is 80; valid range is [10, 100]. The ?? 0 fallback is a
42
+ // defensive guard for missing/null values only — not a normal operating state.
43
+ const rawThreshold = Number(deps.configManager.get('behavior.autoCompactThreshold') ?? 0);
44
+ const thresholdPct = Number.isFinite(rawThreshold) ? rawThreshold : 0;
45
+
46
+ // Defensive guard: skip only when threshold is missing/non-positive (real config defaults to 80).
47
+ if (thresholdPct <= 0 || deps.contextWindow <= 0) return;
48
+
49
+ const usagePct = Math.min(100, Math.round((Math.max(0, deps.lastInputTokens) / deps.contextWindow) * 100));
50
+ if (usagePct < thresholdPct) return;
51
+
52
+ try {
53
+ logger.debug('auto-compact triggered', { usagePct, thresholdPct });
54
+ // Honest transcript notice — the user should always know when compaction
55
+ // runs automatically so they can understand any summary discontinuity.
56
+ deps.systemMessageRouter.routeSystemMessage(
57
+ `[Context] Auto-compacting conversation — usage reached ${usagePct}% (threshold ${thresholdPct}%). A summary will replace older turns to recover headroom.`,
58
+ 'high',
59
+ );
60
+ await deps.conversation.compact(
61
+ deps.providerRegistry,
62
+ deps.model,
63
+ 'auto',
64
+ deps.provider,
65
+ );
66
+ deps.systemMessageRouter.routeSystemMessage(
67
+ `[Context] Auto-compact complete — older turns summarised. Use /compact to compact again manually.`,
68
+ 'low',
69
+ );
70
+ } catch (err) {
71
+ logger.error('auto-compact failed', { error: summarizeError(err) });
72
+ deps.systemMessageRouter.routeSystemMessage(
73
+ `[Context] Auto-compact failed: ${summarizeError(err)}. Use /compact to try manually.`,
74
+ 'high',
75
+ );
76
+ }
77
+ }
@@ -0,0 +1,124 @@
1
+ import type { UiRuntimeEvents } from '@/runtime/index.ts';
2
+ import { buildPersistedSessionContext, persistConversation } from '@/runtime/index.ts';
3
+ import { maybeAutoCompact } from './context-auto-compact.ts';
4
+ import { logger } from '@pellux/goodvibes-sdk/platform/utils';
5
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
6
+ import type { HookDispatcher, HookPhase, HookCategory, HookEventPath } from '@pellux/goodvibes-sdk/platform/hooks';
7
+ import type { ConversationManager } from './conversation.ts';
8
+
9
+ /** Infer the options param of persistConversation to pick up SessionManager correctly. */
10
+ type PersistOptions = NonNullable<Parameters<typeof persistConversation>[5]>;
11
+
12
+ /** Minimal orchestrator surface required by turn-event wiring. */
13
+ interface TurnOrchestrator {
14
+ readonly lastInputTokens: number;
15
+ }
16
+
17
+ /** Minimal provider registry surface required by turn-event wiring. */
18
+ interface TurnProviderRegistry {
19
+ getCurrentModel(): { readonly contextWindow: number };
20
+ }
21
+
22
+ /** Minimal config manager surface required by turn-event wiring. */
23
+ interface TurnConfigManager {
24
+ get(key: string): unknown;
25
+ }
26
+
27
+ /** Minimal system message router surface required by turn-event wiring. */
28
+ interface TurnSystemMessageRouter {
29
+ high(message: string): void;
30
+ low(message: string): void;
31
+ routeSystemMessage(message: string, level: string): void;
32
+ }
33
+
34
+ export interface WireTurnEventHandlersOptions {
35
+ readonly events: UiRuntimeEvents;
36
+ readonly conversation: ConversationManager;
37
+ readonly runtime: { sessionId: string; model: string; provider: string };
38
+ readonly orchestrator: TurnOrchestrator;
39
+ readonly configManager: TurnConfigManager;
40
+ readonly providerRegistry: TurnProviderRegistry;
41
+ readonly systemMessageRouter: TurnSystemMessageRouter;
42
+ readonly hookDispatcher: HookDispatcher;
43
+ readonly workingDir: string;
44
+ readonly homeDirectory: string;
45
+ readonly sessionManager: PersistOptions['sessionManager'];
46
+ readonly gitStatusProvider: { refresh(): Promise<unknown> };
47
+ readonly lastGitInfoRef: { value: unknown };
48
+ readonly buildSessionContinuityHints: () => Record<string, unknown>;
49
+ readonly render: () => void;
50
+ }
51
+
52
+ export interface WireTurnEventHandlersResult {
53
+ /** Trigger a git status refresh; may be called from external code after tool execution. */
54
+ readonly refreshGit: () => void;
55
+ /** Unsubscribe functions to push into the parent unsubs array. */
56
+ readonly unsubs: ReadonlyArray<() => void>;
57
+ }
58
+
59
+ /**
60
+ * Wire TURN_COMPLETED, TOOL_SUCCEEDED, and TOOL_FAILED runtime events.
61
+ *
62
+ * Responsibilities:
63
+ * - Auto-save conversation to persistent store after each LLM turn
64
+ * - Fire the Lifecycle:session:save hook
65
+ * - Trigger auto-compact when context usage exceeds the configured threshold
66
+ * - Refresh git status after turns and tool results
67
+ *
68
+ * Returns refreshGit (callable externally) and unsubs (push into parent unsubs).
69
+ */
70
+ export function wireTurnEventHandlers(
71
+ options: WireTurnEventHandlersOptions,
72
+ ): WireTurnEventHandlersResult {
73
+ const {
74
+ events, conversation, runtime, orchestrator, configManager,
75
+ providerRegistry, systemMessageRouter, hookDispatcher,
76
+ workingDir, homeDirectory, sessionManager, gitStatusProvider,
77
+ lastGitInfoRef, buildSessionContinuityHints, render,
78
+ } = options;
79
+
80
+ const unsubs: Array<() => void> = [];
81
+
82
+ const refreshGit = (): void => {
83
+ gitStatusProvider.refresh().then((info) => { lastGitInfoRef.value = info; render(); }).catch(() => { /* non-fatal */ });
84
+ };
85
+
86
+ unsubs.push(events.turns.on('TURN_COMPLETED', () => {
87
+ // Auto-save after every LLM turn so kills don't lose the session
88
+ try {
89
+ const snapshot = conversation.toJSON() as { messages: Array<import('./conversation.ts').ConversationMessageSnapshot>; timestamp?: number };
90
+ const persisted = buildPersistedSessionContext(snapshot.messages, conversation.getTitleSource(), buildSessionContinuityHints());
91
+ persistConversation(
92
+ runtime.sessionId,
93
+ { ...snapshot, ...persisted },
94
+ runtime.model,
95
+ runtime.provider,
96
+ conversation.title || '',
97
+ { workingDirectory: workingDir, homeDirectory, sessionManager },
98
+ );
99
+ hookDispatcher.fire({ path: 'Lifecycle:session:save' as HookEventPath, phase: 'Lifecycle' as HookPhase, category: 'session' as HookCategory, specific: 'save', sessionId: runtime.sessionId, timestamp: Date.now(), payload: { sessionId: runtime.sessionId } }).catch((err: unknown) => logger.debug('hook fire error', { error: summarizeError(err) }));
100
+ } catch (e) { logger.debug('auto-save on turn:complete failed', { error: summarizeError(e) }); }
101
+ // Auto-compact: check context usage and compact if threshold exceeded
102
+ const currentModelForCompact = providerRegistry.getCurrentModel();
103
+ maybeAutoCompact({
104
+ configManager: configManager as Parameters<typeof maybeAutoCompact>[0]['configManager'],
105
+ conversation,
106
+ providerRegistry: providerRegistry as Parameters<typeof maybeAutoCompact>[0]['providerRegistry'],
107
+ systemMessageRouter: systemMessageRouter as Parameters<typeof maybeAutoCompact>[0]['systemMessageRouter'],
108
+ model: runtime.model,
109
+ provider: runtime.provider,
110
+ lastInputTokens: orchestrator.lastInputTokens,
111
+ contextWindow: currentModelForCompact.contextWindow,
112
+ }).catch((err: unknown) => logger.debug('maybeAutoCompact error', { error: summarizeError(err) }));
113
+ refreshGit();
114
+ }));
115
+
116
+ unsubs.push(events.tools.on('TOOL_SUCCEEDED', () => {
117
+ refreshGit();
118
+ }));
119
+ unsubs.push(events.tools.on('TOOL_FAILED', () => {
120
+ refreshGit();
121
+ }));
122
+
123
+ return { refreshGit, unsubs };
124
+ }
package/src/daemon/cli.ts CHANGED
@@ -104,6 +104,11 @@ async function main(): Promise<void> {
104
104
  console.error(renderGoodVibesDaemonHelp('goodvibes-daemon'));
105
105
  process.exit(2);
106
106
  }
107
+ if (cli.warnings.length > 0) {
108
+ for (const warning of cli.warnings) {
109
+ console.warn(`[goodvibes-daemon] warning: ${warning}`);
110
+ }
111
+ }
107
112
  if (cli.flags.help || cli.command === 'help') {
108
113
  console.log(renderGoodVibesDaemonHelp('goodvibes-daemon'));
109
114
  process.exit(0);
@@ -118,6 +118,7 @@ export interface CommandShellUiOpeners {
118
118
  openMcpWorkspace?: () => void;
119
119
  openSecurityPanel?: () => void;
120
120
  openKnowledgePanel?: () => void;
121
+ openMemoryPanel?: () => void;
121
122
  openRemotePanel?: () => void;
122
123
  openSubscriptionPanel?: () => void;
123
124
  /**
@@ -203,11 +203,11 @@ export function registerControlRoomRuntimeCommands(registry: CommandRegistry): v
203
203
  handler(args, ctx) {
204
204
  const subcommand = (args[0] ?? 'open').toLowerCase();
205
205
  if (subcommand === 'open') {
206
- if (ctx.openKnowledgePanel) {
207
- ctx.openKnowledgePanel();
206
+ if (ctx.openMemoryPanel) {
207
+ ctx.openMemoryPanel();
208
208
  return;
209
209
  }
210
- ctx.print('Knowledge panel is not available in this runtime.');
210
+ ctx.print('Memory panel is not available in this runtime.');
211
211
  return;
212
212
  }
213
213
  const memory = getMemoryApi(ctx);
@@ -245,8 +245,8 @@ export function registerControlRoomRuntimeCommands(registry: CommandRegistry): v
245
245
  ctx.print(prompt ?? 'No reviewed project knowledge matched that task.');
246
246
  return;
247
247
  }
248
- if (ctx.openKnowledgePanel) {
249
- ctx.openKnowledgePanel();
248
+ if (ctx.openMemoryPanel) {
249
+ ctx.openMemoryPanel();
250
250
  return;
251
251
  }
252
252
  ctx.print(`Unknown project-memory subcommand: ${subcommand}`);
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Implements the Provider Optimizer panel commands:
5
5
  *
6
+ * /provider optimizer on|off — Enable or disable the provider optimizer
6
7
  * /provider route auto|manual — Set optimizer routing mode
7
8
  * /provider explain-route — Print current route explanation
8
9
  * /provider pin <provider:model> — Pin routing to a specific provider/model
@@ -10,14 +11,19 @@
10
11
  *
11
12
  * When the optimizer is disabled, commands report its status and
12
13
  * explain-route still works (reads current model capabilities).
14
+ * Enabling the optimizer persists the change to config so it survives restart.
13
15
  */
14
16
 
15
17
  import type { SlashCommand, CommandContext } from '../command-registry.ts';
18
+ import type { ConfigKey } from '../../config/index.ts';
16
19
  import type { RouteExplanation } from '@pellux/goodvibes-sdk/platform/providers';
17
20
  import type { FallbackTestResult, FallbackTransition } from '@pellux/goodvibes-sdk/platform/providers';
18
21
  import type { ProviderApiModelRecord } from '@pellux/goodvibes-sdk/platform/providers';
19
22
  import { requireProviderApi } from './runtime-services.ts';
20
23
 
24
+ const PROVIDER_OPTIMIZER_FLAG = 'provider-optimizer';
25
+ const PROVIDER_OPTIMIZER_CONFIG_KEY = `featureFlags.${PROVIDER_OPTIMIZER_FLAG}` as ConfigKey;
26
+
21
27
  // ---------------------------------------------------------------------------
22
28
  // Formatting helpers
23
29
  // ---------------------------------------------------------------------------
@@ -65,6 +71,49 @@ function fmtExplanation(expl: RouteExplanation, context: CommandContext): void {
65
71
  }
66
72
  }
67
73
 
74
+ // ---------------------------------------------------------------------------
75
+ // /provider optimizer on|off
76
+ // ---------------------------------------------------------------------------
77
+
78
+ function handleOptimizerToggle(
79
+ args: string[],
80
+ context: CommandContext,
81
+ ): void {
82
+ const optimizer = requireProviderOptimizer(context);
83
+ if (!optimizer) return;
84
+ const sub = args[0];
85
+
86
+ if (sub !== 'on' && sub !== 'off') {
87
+ context.print('[provider] Usage: /provider optimizer on|off');
88
+ context.print(` Current state: optimizer is ${optimizer.enabled ? 'enabled' : 'disabled'}`);
89
+ context.print(' "on" — activates intelligent failover and auto-routing');
90
+ context.print(' "off" — disables optimizer; provider selection is manual only');
91
+ return;
92
+ }
93
+
94
+ const enable = sub === 'on';
95
+ const wasEnabled = optimizer.enabled;
96
+ optimizer.setEnabled(enable);
97
+
98
+ // Persist to config so the setting survives restart.
99
+ const flagValue = enable ? 'enabled' : 'disabled';
100
+ context.platform.configManager.setDynamic(PROVIDER_OPTIMIZER_CONFIG_KEY, flagValue);
101
+
102
+ if (enable && !wasEnabled) {
103
+ context.print('[provider] Optimizer enabled.');
104
+ context.print(' Intelligent failover is now active: on a request error the optimizer');
105
+ context.print(' will attempt the next viable provider and surface a transcript notice');
106
+ context.print(' naming the from→to transition and reason before retrying.');
107
+ context.print(' Use "/provider route auto" to enable fully automatic routing.');
108
+ } else if (!enable && wasEnabled) {
109
+ context.print('[provider] Optimizer disabled.');
110
+ context.print(' Provider selection returns to manual-only mode. No automatic failover.');
111
+ context.print(' Pinned targets and fallback log are preserved; re-enable to resume.');
112
+ } else {
113
+ context.print(`[provider] Optimizer already ${enable ? 'enabled' : 'disabled'} — no change.`);
114
+ }
115
+ }
116
+
68
117
  // ---------------------------------------------------------------------------
69
118
  // /provider route auto|manual
70
119
  // ---------------------------------------------------------------------------
@@ -85,9 +134,9 @@ function handleRoute(
85
134
 
86
135
  if (!optimizer.enabled) {
87
136
  context.print(
88
- '[provider] Optimizer is currently disabled. Enable it with the provider-optimizer feature flag.',
137
+ '[provider] Optimizer is off routing mode recorded but failover will not fire until optimizer is enabled.',
89
138
  );
90
- context.print(` Routing mode set to: ${sub} (no-op until optimizer is enabled)`);
139
+ context.print(' Enable with: /provider optimizer on');
91
140
  }
92
141
 
93
142
  optimizer.setMode(sub);
@@ -318,11 +367,15 @@ export const providerCommand: SlashCommand = {
318
367
  aliases: ['prov-opt'],
319
368
  description: 'Manage provider routing optimizer (route, pin, explain, fallback).',
320
369
  usage: '<subcommand> [args]',
321
- argsHint: 'route|explain-route|pin|fallback',
370
+ argsHint: 'optimizer|route|explain-route|pin|fallback',
322
371
  handler: async (args: string[], context: CommandContext): Promise<void> => {
323
372
  const [sub, ...rest] = args;
324
373
 
325
374
  switch (sub) {
375
+ case 'optimizer':
376
+ handleOptimizerToggle(rest, context);
377
+ break;
378
+
326
379
  case 'route':
327
380
  handleRoute(rest, context);
328
381
  break;
@@ -345,6 +398,7 @@ export const providerCommand: SlashCommand = {
345
398
  if (!optimizer) return;
346
399
  const lines = [
347
400
  'Usage: /provider <subcommand>',
401
+ ' optimizer on|off — Enable or disable the provider optimizer',
348
402
  ' route auto|manual — Set optimizer routing mode',
349
403
  ' explain-route — Show current route explanation',
350
404
  ' pin <provider:model> — Pin routing to specific provider/model',
@@ -1,6 +1,6 @@
1
1
  import { randomBytes } from 'node:crypto';
2
2
 
3
- import type { CommandContext, CommandRegistry } from '../command-registry.ts';
3
+ import type { CommandContext } from '../command-registry.ts';
4
4
  import { type SessionMeta } from '@pellux/goodvibes-sdk/platform/sessions';
5
5
  import type { TranscriptEventKind } from '@pellux/goodvibes-sdk/platform/core';
6
6
  import type { ConversationTitleSource } from '../../core/conversation';
@@ -441,21 +441,13 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
441
441
  return false;
442
442
  }
443
443
 
444
- export function registerSessionWorkflowCommands(registry: CommandRegistry): void {
445
- registry.register({
446
- name: 'session-mgmt',
447
- aliases: ['smgmt'],
448
- description: 'Manage sessions, resume posture, and transcript structure',
449
- usage: '[list | rename <name> | resume <id|name> | fork | save | info <id> | events [kind] | groups [kind] | hotspots | export <id> [format] | search <query> | delete <id>]',
450
- argsHint: '<list|rename|resume|fork|save|info|events|groups|hotspots|export|search|delete>',
451
- async handler(args, ctx) {
452
- const handled = await handleSessionWorkflowCommand(args, ctx);
453
- if (!handled) {
454
- ctx.print('Unknown subcommand: ' + (args[0] ?? '') + '\nUsage: /session-mgmt [list | rename <name> | resume <id> | fork [name] | save [name] | info [id] | events [kind] | groups [kind] | hotspots | export <id> [format] | search <query> | delete <id>]');
455
- }
456
- },
457
- });
458
- }
444
+ // session-mgmt / smgmt was removed in TASK-032.
445
+ // All session lifecycle operations are now first-class subcommands of /session.
446
+ // Use /session list, /session resume, /session save, etc.
447
+ //
448
+ // CommandRegistry.register() throws on duplicate names/aliases, so this
449
+ // registration was intentionally deleted rather than left as dead code.
450
+
459
451
  interface SessionExportData {
460
452
  readonly messages: object[];
461
453
  readonly timestamp?: number;