@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
@@ -8,18 +8,25 @@ import { formatDuration } from './modal-utils.ts';
8
8
  import { logger } from '@pellux/goodvibes-sdk/platform/utils';
9
9
  import { getOverlaySurfaceMetrics, getStableOverlayContentRows } from './overlay-viewport.ts';
10
10
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
11
+ import { handleConfirmInput, type ConfirmState } from '../panels/confirm-state.ts';
12
+ import { AGENT_TERMINAL_STATUSES as MODAL_TERMINAL_STATUSES, AGENT_STALL_THRESHOLD_MS as MODAL_STALL_THRESHOLD_MS } from '../panels/agent-inspector-shared.ts';
11
13
 
12
14
  // ─── Constants ────────────────────────────────────────────────────────────────
13
15
 
14
16
  const MAX_LOG_ENTRIES = 10;
15
17
  const AGENT_ID_DISPLAY_LENGTH = 16;
16
18
 
19
+ // MODAL_TERMINAL_STATUSES and MODAL_STALL_THRESHOLD_MS are re-exported aliases
20
+ // from agent-inspector-shared.ts (imported above alongside ConfirmState).
21
+
17
22
  export interface AgentDetailModalDeps {
18
- readonly agentManager: Pick<AgentManager, 'getStatus'>;
23
+ readonly agentManager: Pick<AgentManager, 'getStatus' | 'list'>;
19
24
  readonly agentMessageBus: Pick<AgentMessageBus, 'getMessages'>;
20
25
  readonly sessionLogPathResolver: (agentId: string) => string;
21
26
  /** Optional — when supplied, constraint data from the agent's WRFC chain is shown (SDK 0.23.0). */
22
27
  readonly wrfcController?: Pick<WrfcController, 'getChain'>;
28
+ /** Cancel the agent by id using the same orphan-free path as WRFC. Returns true if cancelled. */
29
+ readonly cancelAgent: (agentId: string) => boolean;
23
30
  }
24
31
 
25
32
  // ─── AgentDetailModal ─────────────────────────────────────────────────────────
@@ -39,6 +46,9 @@ export class AgentDetailModal {
39
46
  public logEntries: Record<string, unknown>[] = [];
40
47
  public logTotal = 0;
41
48
 
49
+ /** Pending cancel confirmation. Subject is the agent id to cancel. */
50
+ public confirmCancel: ConfirmState<string> | null = null;
51
+
42
52
  private refreshTimer: ReturnType<typeof setInterval> | null = null;
43
53
  private onRefresh: (() => void) | null = null;
44
54
 
@@ -67,12 +77,88 @@ export class AgentDetailModal {
67
77
  this.agentId = null;
68
78
  this.logEntries = [];
69
79
  this.logTotal = 0;
80
+ this.confirmCancel = null;
70
81
  if (this.refreshTimer) {
71
82
  clearInterval(this.refreshTimer);
72
83
  this.refreshTimer = null;
73
84
  }
74
85
  }
75
86
 
87
+ /**
88
+ * Handle a key press while the modal is active.
89
+ * Must be called BEFORE the Esc handler closes the modal.
90
+ *
91
+ * Routes:
92
+ * - 'c' initiates cancel confirm (if agent is non-terminal)
93
+ * - confirm keys (Enter/y/n/Esc) are forwarded to handleConfirmInput
94
+ *
95
+ * Returns true when the key was consumed (caller should NOT propagate).
96
+ */
97
+ handleKey(key: string): boolean {
98
+ if (!this.active) return false;
99
+
100
+ if (this.confirmCancel) {
101
+ const result = handleConfirmInput(this.confirmCancel, key);
102
+ if (result === 'confirmed') {
103
+ if (this.agentId) {
104
+ const rec = this.deps.agentManager.getStatus(this.agentId);
105
+ if (rec && !MODAL_TERMINAL_STATUSES.has(rec.status)) {
106
+ this.deps.cancelAgent(rec.id);
107
+ }
108
+ }
109
+ this.confirmCancel = null;
110
+ this.onRefresh?.();
111
+ return true;
112
+ }
113
+ if (result === 'cancelled') {
114
+ this.confirmCancel = null;
115
+ this.onRefresh?.();
116
+ return true;
117
+ }
118
+ // absorbed — key swallowed while confirm is pending
119
+ return true;
120
+ }
121
+
122
+ if (key === 'c') {
123
+ if (this.agentId) {
124
+ const rec = this.deps.agentManager.getStatus(this.agentId);
125
+ if (rec && !MODAL_TERMINAL_STATUSES.has(rec.status)) {
126
+ const label = rec.task.split('\n')[0]?.slice(0, 40) ?? rec.id.slice(-8);
127
+ this.confirmCancel = { subject: rec.id, label };
128
+ this.onRefresh?.();
129
+ return true;
130
+ }
131
+ }
132
+ // Non-cancellable — absorb key silently
133
+ return true;
134
+ }
135
+
136
+ return false;
137
+ }
138
+
139
+ /**
140
+ * Returns whether the current agent is considered stalled.
141
+ * Non-terminal agent with elapsed time exceeding MODAL_STALL_THRESHOLD_MS.
142
+ */
143
+ isCurrentAgentStalled(): boolean {
144
+ if (!this.agentId) return false;
145
+ const rec = this.deps.agentManager.getStatus(this.agentId);
146
+ if (!rec || MODAL_TERMINAL_STATUSES.has(rec.status)) return false;
147
+ return (Date.now() - rec.startedAt) >= MODAL_STALL_THRESHOLD_MS;
148
+ }
149
+
150
+ /**
151
+ * Count of all stalled agents across the agentManager list.
152
+ * Non-terminal agents with elapsed time >= MODAL_STALL_THRESHOLD_MS.
153
+ */
154
+ getStalledAgentCount(): number {
155
+ const now = Date.now();
156
+ return this.deps.agentManager.list().filter(rec => {
157
+ if (MODAL_TERMINAL_STATUSES.has(rec.status)) return false;
158
+ return (now - rec.startedAt) >= MODAL_STALL_THRESHOLD_MS;
159
+ }).length;
160
+ }
161
+
76
162
  async loadLog(): Promise<void> {
77
163
  if (!this.agentId) { this.logEntries = []; this.logTotal = 0; return; }
78
164
  try {
@@ -161,7 +247,8 @@ export function renderAgentDetailModal(
161
247
  const modelStr = rec.model ? `${rec.provider ?? ''}/${rec.model}` : (rec.provider ?? '(default)');
162
248
  sections.push({ type: 'text', content: `Template : ${rec.template}` });
163
249
  sections.push({ type: 'text', content: `Model : ${modelStr}` });
164
- sections.push({ type: 'text', content: `Status : ${rec.status}` });
250
+ const isStalled = !MODAL_TERMINAL_STATUSES.has(rec.status) && (now - rec.startedAt) >= MODAL_STALL_THRESHOLD_MS;
251
+ sections.push({ type: 'text', content: `Status : ${rec.status}${isStalled ? ' [STALLED — 5+ min no activity]' : ''}` });
165
252
  sections.push({ type: 'text', content: `Duration : ${formatDuration(elapsedMs)}` });
166
253
  sections.push({ type: 'separator' });
167
254
 
@@ -321,12 +408,29 @@ export function renderAgentDetailModal(
321
408
  }
322
409
  }
323
410
 
411
+ // Cancel confirm overlay (when pending)
412
+ const cancellable = !MODAL_TERMINAL_STATUSES.has(rec.status);
413
+ if (modal.confirmCancel) {
414
+ sections.push({ type: 'separator' });
415
+ sections.push({
416
+ type: 'text',
417
+ content: `Cancel agent "${modal.confirmCancel.label}"?`,
418
+ style: { fg: '#f59e0b' },
419
+ });
420
+ sections.push({
421
+ type: 'text',
422
+ content: 'y / Enter confirm n / Esc cancel',
423
+ style: { dim: true },
424
+ });
425
+ }
426
+
427
+ const cancelHint = cancellable ? '[c] Cancel ' : '';
324
428
  return ModalFactory.createModal({
325
429
  title: `Agent: ${rec.id.slice(0, AGENT_ID_DISPLAY_LENGTH)}`,
326
430
  width: metrics.boxWidth,
327
431
  margin: metrics.margin,
328
432
  targetContentRows,
329
433
  sections,
330
- hints: ['[Esc] Close'],
434
+ hints: [cancelHint + '[Esc] Close'],
331
435
  }, width);
332
436
  }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Context status hint — TASK-056.
3
+ *
4
+ * Produces a short, dismissible status-line hint when the session maintenance
5
+ * level indicates compaction is recommended or repair is needed. The hint is
6
+ * passive and non-blocking: it appears in the footer status row and disappears
7
+ * once the pressure signal clears.
8
+ *
9
+ * Honest wording policy:
10
+ * - suggest-compact → describes the situation and offers /compact
11
+ * - needs-repair → names the failure state honestly without alarming
12
+ * - compacting → shows in-progress text so the user knows something is running
13
+ * - watch → no hint (not yet actionable)
14
+ * - stable/unknown → no hint
15
+ */
16
+
17
+ import type { PanelSessionMaintenanceLevel } from '../panels/session-maintenance.ts';
18
+
19
+ export interface ContextStatusHintOptions {
20
+ /** Maintenance level from evaluateSessionMaintenance. */
21
+ readonly level: PanelSessionMaintenanceLevel;
22
+ /** Whether auto-compaction is active (threshold > 0 in config). */
23
+ readonly autoCompactEnabled: boolean;
24
+ /** Current usage percent 0–100. */
25
+ readonly usagePct: number;
26
+ }
27
+
28
+ /**
29
+ * Build the passive status-line hint text for context pressure.
30
+ *
31
+ * Returns null when no hint is warranted (stable / watch / unknown).
32
+ * The caller renders this as a dim informational line — no prompts, no
33
+ * blocking, no confirmation required.
34
+ */
35
+ export function buildContextStatusHint(options: ContextStatusHintOptions): string | null {
36
+ const { level, autoCompactEnabled, usagePct } = options;
37
+
38
+ switch (level) {
39
+ case 'needs-repair':
40
+ return ` Context pressure critical (${usagePct}% used) — compaction needs attention. Run /compact or /health review.`;
41
+
42
+ case 'suggest-compact':
43
+ if (autoCompactEnabled) {
44
+ return ` Context high (${usagePct}% used) — auto-compact will run before the next turn.`;
45
+ }
46
+ return ` Context high (${usagePct}% used) — run /compact to recover headroom.`;
47
+
48
+ case 'compacting':
49
+ return ` Compacting context — freeing headroom...`;
50
+
51
+ default:
52
+ return null;
53
+ }
54
+ }
@@ -539,14 +539,25 @@ function rowColorForSetting(modal: SettingsModal, rowText: string): string {
539
539
  return valueColor(selected);
540
540
  }
541
541
 
542
- function footerText(modal: SettingsModal): string {
542
+ function footerText(modal: SettingsModal, width: number): string {
543
+ // Armed reset gate takes priority over all other footer states.
544
+ if (modal.resetCategoryConfirm !== null || modal.resetAllConfirm !== null)
545
+ return 'Reset armed · Enter/y confirm · Esc/n cancel';
543
546
  if (modal.searchFocused) return 'Search · type to filter · Up/Down navigate results · Enter select · Esc exit search';
544
547
  if (modal.editingMode) return 'Enter Confirm edit · Esc Cancel edit · text keys edit the selected field';
545
548
  if (modal.focusPane === 'categories') return 'Focus categories · Up/Down choose · Right/Enter settings · Tab pane · / search · Esc close';
546
549
  if (modal.currentCategory === 'subscriptions') return 'Focus settings · Up/Down provider · Left categories · Tab pane · / search · Enter review/sign out · Esc close';
547
550
  if (modal.currentCategory === 'mcp') return 'Focus settings · Up/Down server · Left categories · Tab pane · / search · Enter edit trust · Esc close';
548
551
  if (modal.currentCategory === 'flags') return 'Focus feature flags · Up/Down flag · Left categories · Tab pane · / search · Enter/Space toggle · Esc close';
549
- return 'Focus settings · Up/Down setting · Left categories · Tab pane · / search · Enter/Space edit/toggle · R reset · Esc close';
552
+ // Default settings pane: tier the reset affordances by available width.
553
+ // W<80: minimal — only the most critical action survives.
554
+ // W<160: compact but still shows both reset affordances.
555
+ // W≥160: standard with all navigation tokens.
556
+ if (width < 80)
557
+ return 'R reset · Esc';
558
+ if (width < 160)
559
+ return 'Up/Down · Enter/Space edit · ⇧R reset cat · ^⇧R reset all · Esc';
560
+ return 'Focus settings · Up/Down setting · Left · Enter/Space edit/toggle · ⇧R reset cat · ^⇧R reset all · Esc close';
550
561
  }
551
562
 
552
563
  export function renderSettingsModal(
@@ -601,6 +612,6 @@ export function renderSettingsModal(
601
612
  })),
602
613
  contextRows,
603
614
  controlRows,
604
- footer: footerText(modal),
615
+ footer: footerText(modal, width),
605
616
  });
606
617
  }
@@ -29,6 +29,11 @@ export interface ShellFooterBuildOptions {
29
29
  readonly composerStatus?: string;
30
30
  readonly composerFlags?: readonly string[];
31
31
  readonly composerPendingRisk?: 'none' | 'approval-wait' | 'shell' | 'command' | 'remote';
32
+ /**
33
+ * Passive context pressure hint from buildContextStatusHint.
34
+ * Rendered as a dim informational line above the prompt when non-null.
35
+ */
36
+ readonly contextStatusHint?: string | null;
32
37
  }
33
38
 
34
39
  export interface ShellFooterBuildResult {
@@ -84,5 +89,10 @@ export function buildShellFooter(
84
89
  );
85
90
  const inputBoxRows = Math.max(1, options.promptLineCount) + 2;
86
91
  lines.splice(inputBoxRows, 0, ...processIndicator);
92
+ // Passive context status hint — rendered as a dim informational line before the prompt.
93
+ if (options.contextStatusHint) {
94
+ const hintLine = UIFactory.stringToLine(options.contextStatusHint, options.width, { fg: '#64748b' });
95
+ lines.unshift(hintLine);
96
+ }
87
97
  return { lines, height: lines.length };
88
98
  }
@@ -160,6 +160,7 @@ export function createBootstrapCommandActions(
160
160
  | 'openMcpWorkspace'
161
161
  | 'openSecurityPanel'
162
162
  | 'openKnowledgePanel'
163
+ | 'openMemoryPanel'
163
164
  | 'openRemotePanel'
164
165
  | 'openSubscriptionPanel'
165
166
  | 'openLocalAuthMaskedEntry'
@@ -273,6 +274,9 @@ export function createBootstrapCommandActions(
273
274
  openKnowledgePanel: () => {
274
275
  showPanel('knowledge');
275
276
  },
277
+ openMemoryPanel: () => {
278
+ showPanel('memory');
279
+ },
276
280
  openRemotePanel: () => {
277
281
  showPanel('remote');
278
282
  },
@@ -426,6 +426,30 @@ export async function initializeBootstrapCore(
426
426
  }
427
427
  };
428
428
 
429
+ // Startup TLS banner — emitted via wrfcBuffer.push() because the
430
+ // SystemMessageRouter is not attached yet at this point in bootstrap. The
431
+ // smart-ref setter on systemMessageRouterRef auto-flushes the buffer when
432
+ // the router attaches, so the message will appear in the WRFC panel on startup.
433
+ {
434
+ const cpEnabled = Boolean(configManager.get('controlPlane.enabled'));
435
+ const cpHostMode = String(configManager.get('controlPlane.hostMode') ?? 'local');
436
+ const cpTlsMode = String(configManager.get('controlPlane.tls.mode') ?? 'off');
437
+ const hlEnabled = Boolean(configManager.get('danger.httpListener'));
438
+ const hlHostMode = String(configManager.get('httpListener.hostMode') ?? 'local');
439
+ const hlTlsMode = String(configManager.get('httpListener.tls.mode') ?? 'off');
440
+ const cpNetworkPlaintext = cpEnabled && cpHostMode !== 'local' && cpTlsMode === 'off';
441
+ const hlNetworkPlaintext = hlEnabled && hlHostMode !== 'local' && hlTlsMode === 'off';
442
+ if (cpNetworkPlaintext || hlNetworkPlaintext) {
443
+ const affected: string[] = [];
444
+ if (cpNetworkPlaintext) affected.push('control plane');
445
+ if (hlNetworkPlaintext) affected.push('HTTP listener');
446
+ wrfcBuffer.push(
447
+ `[SECURITY] TLS is off for the ${affected.join(' and ')} but it is network-reachable. All traffic (credentials, tokens, conversation content) travels in plaintext. Enable TLS (controlPlane.tls.mode / httpListener.tls.mode) or restrict to loopback before exposing to untrusted networks.`,
448
+ 'high',
449
+ );
450
+ }
451
+ }
452
+
429
453
  runtimeUnsubs.push(
430
454
  runtimeBus.on<Extract<import('@/runtime/index.ts').WorkflowEvent, { type: 'WORKFLOW_CONSTRAINTS_ENUMERATED' }>>(
431
455
  'WORKFLOW_CONSTRAINTS_ENUMERATED',
@@ -40,6 +40,11 @@ export interface BootstrapShellState {
40
40
  readonly lastGitInfoRef: { value: GitHeaderInfo | undefined };
41
41
  readonly inputHistory: InputHistory;
42
42
  readonly systemMessageRouter: SystemMessageRouter;
43
+ /**
44
+ * Wire the agent detail modal opener after InputHandler is constructed.
45
+ * Call with `(id) => input.agentDetailModal.open(id)` from main.ts.
46
+ */
47
+ readonly setOpenAgentDetail: (fn: (agentId: string) => void) => void;
43
48
  }
44
49
 
45
50
  export interface BootstrapShellOptions {
@@ -103,6 +108,8 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
103
108
  providerRegistry: services.providerRegistry,
104
109
  });
105
110
 
111
+ const openAgentDetailRef: { fn: (agentId: string) => void } = { fn: (_agentId: string) => {} };
112
+
106
113
  let commandContextRef: CommandContext | null = null;
107
114
  registerBuiltinPanels(services.panelManager, {
108
115
  configManager,
@@ -143,6 +150,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
143
150
  hookActivityTracker: services.hookActivityTracker,
144
151
  hookWorkbench: services.hookWorkbench,
145
152
  mcpRegistry: services.mcpRegistry,
153
+ openAgentDetail: (agentId: string) => openAgentDetailRef.fn(agentId),
146
154
  daemonHomeDir: join(services.homeDirectory, '.goodvibes', 'daemon'),
147
155
  });
148
156
  services.panelManager.prewarmRegistered();
@@ -278,5 +286,8 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
278
286
  lastGitInfoRef,
279
287
  inputHistory,
280
288
  systemMessageRouter,
289
+ setOpenAgentDetail: (fn: (agentId: string) => void) => {
290
+ openAgentDetailRef.fn = fn;
291
+ },
281
292
  };
282
293
  }
@@ -118,6 +118,11 @@ export type BootstrapContext = RuntimeContext & {
118
118
  * stay out of the main conversation and go to the SystemMessagesPanel instead.
119
119
  */
120
120
  systemMessageRouter: SystemMessageRouter;
121
+ /**
122
+ * Wire the agent detail modal opener after InputHandler is constructed in main.ts.
123
+ * Call with `(id) => input.agentDetailModal.open(id)` once the InputHandler is ready.
124
+ */
125
+ setOpenAgentDetail: (fn: (agentId: string) => void) => void;
121
126
  };
122
127
 
123
128
  // ── Bootstrap function ────────────────────────────────────────────────────
@@ -292,6 +297,7 @@ export async function bootstrapRuntime(
292
297
  const gitStatusProvider = shell.gitStatusProvider;
293
298
  const inputHistory = shell.inputHistory;
294
299
  const lastGitInfoRef = shell.lastGitInfoRef;
300
+ const setOpenAgentDetail = shell.setOpenAgentDetail;
295
301
  const pluginCommandRegistry = {
296
302
  register(command: {
297
303
  readonly name: string;
@@ -622,6 +628,7 @@ export async function bootstrapRuntime(
622
628
  _getConfiguredProviderIds: () => services.providerRegistry.getConfiguredProviderIds(),
623
629
  commandRegistry,
624
630
  systemMessageRouter,
631
+ setOpenAgentDetail,
625
632
  shutdown: async (sessionData) => {
626
633
  // Clear bootstrap-owned subscriptions
627
634
  bootstrapUnsubs.forEach(fn => fn());
@@ -78,6 +78,7 @@ import { ComponentHealthMonitor } from '@/runtime/index.ts';
78
78
  import { WorktreeRegistry } from '@/runtime/index.ts';
79
79
  import { SandboxSessionRegistry } from '@/runtime/index.ts';
80
80
  import { createShellPathService, type ShellPathService } from '@/runtime/index.ts';
81
+ import { isFeatureFlagEnabled } from './surface-feature-flags.ts';
81
82
  import type { FeatureFlagManager } from '@/runtime/index.ts';
82
83
  import { createFeatureFlagManager } from '@/runtime/index.ts';
83
84
  import { PolicyRuntimeState } from '@/runtime/index.ts';
@@ -530,7 +531,11 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
530
531
  const worktreeRegistry = new WorktreeRegistry(workingDirectory);
531
532
  const webhookNotifier = new WebhookNotifier();
532
533
  const replayEngine = new DeterministicReplayEngine(workingDirectory);
533
- const providerOptimizer = new ProviderOptimizer(providerRegistry, providerCapabilityRegistry, false);
534
+ const providerOptimizer = new ProviderOptimizer(
535
+ providerRegistry,
536
+ providerCapabilityRegistry,
537
+ isFeatureFlagEnabled(configManager, 'provider-optimizer'),
538
+ );
534
539
  const sessionMemoryStore = new SessionMemoryStore();
535
540
  const sessionLineageTracker = new SessionLineageTracker();
536
541
  const sessionChangeTracker = new SessionChangeTracker();
package/src/version.ts CHANGED
@@ -6,7 +6,7 @@ import { join } from 'node:path';
6
6
  // The prebuild script updates the fallback value before compilation.
7
7
  // Uses import.meta.dir (Bun) to locate package.json relative to this file,
8
8
  // which is correct regardless of the process working directory.
9
- let _version = '0.21.0';
9
+ let _version = '0.22.0';
10
10
  try {
11
11
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
12
12
  _version = pkg.version ?? _version;