@pellux/goodvibes-tui 1.16.1 → 1.18.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 (257) hide show
  1. package/CHANGELOG.md +138 -9
  2. package/README.md +1 -1
  3. package/docs/foundation-artifacts/operator-contract.json +5490 -1806
  4. package/package.json +7 -7
  5. package/src/cli/completions/generate.ts +5 -4
  6. package/src/cli/config-overrides.ts +51 -11
  7. package/src/cli/doctor.ts +4 -5
  8. package/src/cli/entrypoint.ts +13 -8
  9. package/src/cli/help.ts +7 -6
  10. package/src/cli/launch-auto-update.ts +214 -0
  11. package/src/cli/management-commands.ts +82 -15
  12. package/src/cli/management.ts +0 -2
  13. package/src/cli/parser.ts +5 -0
  14. package/src/cli/status.ts +13 -0
  15. package/src/cli/surface-command.ts +5 -5
  16. package/src/cli/tui-startup.ts +60 -97
  17. package/src/cli/types.ts +2 -0
  18. package/src/config/index.ts +1 -1
  19. package/src/config/tui-extension-settings.ts +33 -0
  20. package/src/core/composer-state.ts +1 -1
  21. package/src/core/consolidation-receipt.ts +41 -0
  22. package/src/core/conversation-line-cache.ts +3 -3
  23. package/src/core/conversation-rendering.ts +18 -9
  24. package/src/core/conversation.ts +2 -2
  25. package/src/core/long-task-notifier.ts +7 -7
  26. package/src/core/memory-provenance.ts +67 -0
  27. package/src/core/pairing-devices.ts +49 -0
  28. package/src/core/pairing-handoff.ts +101 -0
  29. package/src/core/pairing-offers.ts +83 -0
  30. package/src/core/pairing-origin.ts +78 -0
  31. package/src/core/pairing-tailscale-gateway.ts +56 -0
  32. package/src/core/power-chip-source.ts +91 -0
  33. package/src/core/power-status.ts +95 -0
  34. package/src/core/stable-host.ts +171 -0
  35. package/src/core/stream-event-wiring.ts +29 -6
  36. package/src/core/system-message-noise.ts +1 -1
  37. package/src/core/system-message-router.ts +6 -6
  38. package/src/core/turn-budget-outcome.ts +60 -0
  39. package/src/core/turn-cancellation.ts +40 -1
  40. package/src/core/turn-event-wiring.ts +14 -8
  41. package/src/daemon/cli.ts +64 -58
  42. package/src/daemon/lifecycle.ts +41 -0
  43. package/src/daemon/service-commands.ts +5 -5
  44. package/src/export/cost-utils.ts +128 -6
  45. package/src/input/autocomplete.ts +2 -2
  46. package/src/input/command-registry.ts +55 -9
  47. package/src/input/commands/acp-runtime.ts +72 -0
  48. package/src/input/commands/ci-runtime.ts +5 -1
  49. package/src/input/commands/codebase-runtime.ts +6 -6
  50. package/src/input/commands/cost-runtime.ts +1 -1
  51. package/src/input/commands/devices-runtime.ts +109 -0
  52. package/src/input/commands/git-runtime.ts +28 -4
  53. package/src/input/commands/graph-runtime.ts +45 -0
  54. package/src/input/commands/health-runtime.ts +2 -2
  55. package/src/input/commands/intelligence-runtime.ts +1 -1
  56. package/src/input/commands/local-auth-runtime.ts +1 -1
  57. package/src/input/commands/local-provider-runtime.ts +49 -10
  58. package/src/input/commands/local-runtime.ts +53 -5
  59. package/src/input/commands/memory.ts +2 -2
  60. package/src/input/commands/operator-panel-runtime.ts +5 -5
  61. package/src/input/commands/operator-rpc.ts +32 -11
  62. package/src/input/commands/permissions-provenance.ts +1 -1
  63. package/src/input/commands/permissions-runtime.ts +99 -8
  64. package/src/input/commands/planning-runtime.ts +3 -3
  65. package/src/input/commands/platform-sandbox-runtime.ts +1 -1
  66. package/src/input/commands/power-runtime.ts +56 -0
  67. package/src/input/commands/provider-accounts-runtime.ts +71 -6
  68. package/src/input/commands/provider.ts +7 -5
  69. package/src/input/commands/qrcode-runtime.ts +14 -19
  70. package/src/input/commands/queue-runtime.ts +70 -0
  71. package/src/input/commands/recall-files-git.ts +13 -51
  72. package/src/input/commands/recall-files.ts +8 -11
  73. package/src/input/commands/recall-review.ts +2 -2
  74. package/src/input/commands/relay-runtime.ts +1 -1
  75. package/src/input/commands/remote-runtime.ts +1 -1
  76. package/src/input/commands/runtime-services.ts +4 -4
  77. package/src/input/commands/schedule-runtime.ts +14 -7
  78. package/src/input/commands/services-runtime.ts +1 -1
  79. package/src/input/commands/session-workflow.ts +2 -2
  80. package/src/input/commands/settings-sync-runtime.ts +2 -2
  81. package/src/input/commands/share-runtime.ts +1 -1
  82. package/src/input/commands/shell-core.ts +46 -68
  83. package/src/input/commands/subscription-runtime.ts +1 -1
  84. package/src/input/commands/update-runtime.ts +153 -96
  85. package/src/input/commands/work-plan-runtime.ts +1 -1
  86. package/src/input/commands/workstream-attempts.ts +6 -1
  87. package/src/input/commands/workstream-runtime.ts +46 -27
  88. package/src/input/commands/worktree-runtime.ts +35 -8
  89. package/src/input/commands.ts +10 -2
  90. package/src/input/config-modal-types.ts +14 -3
  91. package/src/input/config-modal.ts +75 -13
  92. package/src/input/feature-unit-layout.ts +93 -82
  93. package/src/input/feed-context-factory.ts +2 -2
  94. package/src/input/handler-command-route.ts +2 -2
  95. package/src/input/handler-content-actions.ts +1 -1
  96. package/src/input/handler-feed-routes.ts +7 -7
  97. package/src/input/handler-feed.ts +4 -4
  98. package/src/input/handler-interactions.ts +3 -2
  99. package/src/input/handler-modal-routes.ts +12 -4
  100. package/src/input/handler-modal-token-routes.ts +1 -1
  101. package/src/input/handler-onboarding-daemon-adopt.ts +2 -2
  102. package/src/input/handler-onboarding.ts +3 -2
  103. package/src/input/handler-picker-routes.ts +26 -13
  104. package/src/input/handler-shortcuts.ts +29 -6
  105. package/src/input/handler.ts +2 -2
  106. package/src/input/keybindings.ts +21 -3
  107. package/src/input/onboarding/onboarding-feature-units.ts +34 -27
  108. package/src/input/onboarding/onboarding-provider-key-fields.ts +112 -0
  109. package/src/input/onboarding/onboarding-wizard-apply.ts +22 -21
  110. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +17 -0
  111. package/src/input/onboarding/onboarding-wizard-network-adopt.ts +2 -2
  112. package/src/input/onboarding/onboarding-wizard-steps.ts +30 -42
  113. package/src/input/panel-integration-actions.ts +2 -2
  114. package/src/input/panel-mouse-geometry.ts +1 -1
  115. package/src/input/panel-paste-flood-guard.ts +1 -1
  116. package/src/input/provider-key-intake.ts +167 -0
  117. package/src/input/sandbox-exec-config.ts +2 -2
  118. package/src/input/selection-modal.ts +1 -1
  119. package/src/input/settings-modal-activation.ts +5 -0
  120. package/src/input/settings-modal-adjustment.ts +4 -31
  121. package/src/input/settings-modal-data.ts +52 -57
  122. package/src/input/settings-modal-mutations.ts +12 -68
  123. package/src/input/settings-modal-types.ts +27 -17
  124. package/src/input/settings-modal.ts +74 -55
  125. package/src/main.ts +90 -90
  126. package/src/panels/agent-inspector-shared.ts +4 -4
  127. package/src/panels/base-panel.ts +2 -2
  128. package/src/panels/builtin/agent.ts +4 -4
  129. package/src/panels/builtin/development.ts +4 -4
  130. package/src/panels/builtin/knowledge.ts +1 -1
  131. package/src/panels/builtin/operations.ts +83 -11
  132. package/src/panels/builtin/session.ts +5 -5
  133. package/src/panels/builtin/shared.ts +16 -4
  134. package/src/panels/builtin-modals.ts +59 -42
  135. package/src/panels/builtin-panels.ts +1 -1
  136. package/src/panels/cost-tracker-panel.ts +124 -7
  137. package/src/panels/diff-panel.ts +39 -6
  138. package/src/panels/eval-registry.ts +2 -2
  139. package/src/panels/fleet-acts.ts +446 -0
  140. package/src/panels/fleet-deep-link.ts +1 -1
  141. package/src/panels/fleet-gateway.ts +117 -0
  142. package/src/panels/fleet-observed-render.ts +128 -0
  143. package/src/panels/fleet-panel-format.ts +163 -11
  144. package/src/panels/fleet-panel-worktree-detail.ts +14 -0
  145. package/src/panels/fleet-panel.ts +73 -57
  146. package/src/panels/fleet-read-model.ts +84 -14
  147. package/src/panels/fleet-spawn.ts +266 -0
  148. package/src/panels/fleet-steer.ts +2 -2
  149. package/src/panels/fleet-stop.ts +41 -5
  150. package/src/panels/fleet-tabs.ts +15 -16
  151. package/src/panels/fleet-transcript.ts +5 -5
  152. package/src/panels/git-panel.ts +2 -2
  153. package/src/panels/index.ts +4 -1
  154. package/src/panels/local-auth-panel.ts +1 -1
  155. package/src/panels/memory-consolidation-gateway.ts +94 -0
  156. package/src/panels/modals/devices-modal.ts +161 -0
  157. package/src/panels/modals/hooks-modal.ts +1 -1
  158. package/src/panels/modals/keybindings-modal.ts +2 -2
  159. package/src/panels/modals/knowledge-modal.ts +1 -1
  160. package/src/panels/modals/marketplace-modal.ts +4 -4
  161. package/src/panels/modals/memory-modal.ts +153 -9
  162. package/src/panels/modals/modal-surface-helpers.ts +1 -1
  163. package/src/panels/modals/modal-theme.ts +1 -1
  164. package/src/panels/modals/pairing-modal.ts +198 -68
  165. package/src/panels/modals/planning-modal.ts +2 -2
  166. package/src/panels/modals/plugins-modal.ts +1 -1
  167. package/src/panels/modals/policy-modal.ts +1 -1
  168. package/src/panels/modals/security-modal.ts +7 -1
  169. package/src/panels/modals/skills-modal.ts +1 -1
  170. package/src/panels/modals/work-plan-modal.ts +3 -3
  171. package/src/panels/notifications-feed.ts +127 -0
  172. package/src/panels/notifications-panel.ts +160 -0
  173. package/src/panels/panel-manager.ts +9 -9
  174. package/src/panels/plugins-panel.ts +4 -4
  175. package/src/panels/project-planning-answer-actions.ts +1 -1
  176. package/src/panels/provider-health-routes.ts +2 -2
  177. package/src/panels/skills-panel.ts +4 -5
  178. package/src/panels/token-budget-panel.ts +11 -3
  179. package/src/panels/types.ts +5 -5
  180. package/src/panels/workstream-graph-render.ts +123 -0
  181. package/src/permissions/broker-approval-card.ts +201 -0
  182. package/src/permissions/hunk-selection.ts +28 -4
  183. package/src/permissions/prompt.ts +244 -29
  184. package/src/renderer/autocomplete-overlay.ts +107 -54
  185. package/src/renderer/code-block.ts +2 -2
  186. package/src/renderer/compaction-history-modal.ts +1 -1
  187. package/src/renderer/compaction-preview.ts +1 -1
  188. package/src/renderer/compaction-quality.ts +1 -1
  189. package/src/renderer/composer-fragments.ts +69 -0
  190. package/src/renderer/compositor.ts +2 -2
  191. package/src/renderer/config-modal.ts +1 -1
  192. package/src/renderer/conversation-overlays.ts +1 -1
  193. package/src/renderer/footer-tips.ts +3 -3
  194. package/src/renderer/hint-grammar.ts +1 -1
  195. package/src/renderer/modal-factory.ts +1 -1
  196. package/src/renderer/model-picker-overlay.ts +1 -1
  197. package/src/renderer/model-workspace.ts +7 -2
  198. package/src/renderer/panel-composite.ts +2 -2
  199. package/src/renderer/panel-workspace-bar.ts +1 -1
  200. package/src/renderer/process-indicator.ts +1 -1
  201. package/src/renderer/selection-modal-overlay.ts +1 -1
  202. package/src/renderer/session-picker-modal.ts +2 -2
  203. package/src/renderer/settings-modal-helpers.ts +23 -5
  204. package/src/renderer/settings-modal.ts +129 -39
  205. package/src/renderer/shell-surface.ts +6 -0
  206. package/src/renderer/terminal-bg-probe.ts +1 -1
  207. package/src/renderer/theme.ts +3 -3
  208. package/src/renderer/tool-call.ts +13 -7
  209. package/src/renderer/tool-result-summary.ts +1 -1
  210. package/src/renderer/turn-injection.ts +7 -6
  211. package/src/renderer/ui-factory.ts +38 -1
  212. package/src/runtime/bootstrap-command-context.ts +7 -2
  213. package/src/runtime/bootstrap-command-parts.ts +9 -5
  214. package/src/runtime/bootstrap-core.ts +41 -41
  215. package/src/runtime/bootstrap-hook-bridge.ts +1 -1
  216. package/src/runtime/bootstrap-shell.ts +13 -5
  217. package/src/runtime/bootstrap.ts +34 -32
  218. package/src/runtime/channel-composition.ts +46 -0
  219. package/src/runtime/code-index-services.ts +3 -4
  220. package/src/runtime/daemon-attach-notices.ts +120 -0
  221. package/src/runtime/durability-services.ts +90 -0
  222. package/src/runtime/embedded-service-factories.ts +40 -0
  223. package/src/runtime/feature-settings.ts +147 -0
  224. package/src/runtime/fleet-services.ts +41 -0
  225. package/src/runtime/idle-power-services.ts +85 -0
  226. package/src/runtime/index.ts +6 -21
  227. package/src/runtime/interaction-seams.ts +109 -0
  228. package/src/runtime/legacy-daemon-migration.ts +29 -19
  229. package/src/runtime/memory-fold.ts +1 -1
  230. package/src/runtime/notification-dispatch.ts +110 -0
  231. package/src/runtime/onboarding/apply.ts +3 -29
  232. package/src/runtime/onboarding/fast-path.ts +39 -6
  233. package/src/runtime/onboarding/provider-key-capture.ts +126 -0
  234. package/src/runtime/onboarding/snapshot.ts +0 -1
  235. package/src/runtime/onboarding/types.ts +9 -3
  236. package/src/runtime/power-keepawake-remote.ts +75 -0
  237. package/src/runtime/process-lifecycle.ts +3 -1
  238. package/src/runtime/provider-fallback.ts +55 -0
  239. package/src/runtime/provider-optimizer-wiring.ts +55 -0
  240. package/src/runtime/release-artifacts.ts +19 -103
  241. package/src/runtime/resume-notice.ts +1 -1
  242. package/src/runtime/services.ts +113 -113
  243. package/src/runtime/session-continuity-hints.ts +38 -0
  244. package/src/runtime/surface-feature-flags.ts +40 -9
  245. package/src/runtime/terminal-output-guard.ts +2 -2
  246. package/src/runtime/trust/workspace-registration.ts +10 -2
  247. package/src/runtime/trust/workspace-trust.ts +58 -31
  248. package/src/runtime/ui-services.ts +6 -1
  249. package/src/runtime/update-check.ts +15 -69
  250. package/src/runtime/workstream-services.ts +2 -2
  251. package/src/shell/blocking-input.ts +108 -132
  252. package/src/shell/recovery-input-helpers.ts +53 -166
  253. package/src/shell/ui-openers.ts +38 -6
  254. package/src/utils/format-duration.ts +1 -1
  255. package/src/verification/verification-ledger.ts +21 -4
  256. package/src/version.ts +1 -1
  257. package/src/input/commands/flags-runtime.ts +0 -328
@@ -1,6 +1,7 @@
1
1
  import type { OnboardingAcknowledgementTarget, OnboardingApplyOperation, OnboardingApplyRequest } from '../../runtime/onboarding/index.ts';
2
2
  import { formatProviderModel } from '../../config/provider-model.ts';
3
3
  import { getServerSurfaceFeatureFlags } from '../../runtime/surface-feature-flags.ts';
4
+ import { featureEnablementWrite } from '../../runtime/feature-settings.ts';
4
5
  import {
5
6
  buildCloudflareApiTokenRef,
6
7
  buildCloudflareOperationalTokenRef,
@@ -17,13 +18,9 @@ import {
17
18
  } from './onboarding-wizard-external-surfaces.ts';
18
19
  import { applyFeatureUnitOperations } from './onboarding-feature-units.ts';
19
20
  import { buildGoodVibesSecretKey, buildGoodVibesSecretRef, isLoopbackAddress, isSecretReferenceValue } from './onboarding-wizard-helpers.ts';
21
+ import { deriveProviderKeyCaptureTargets, providerKeyFieldId } from '../../runtime/onboarding/provider-key-capture.ts';
20
22
  import type { OnboardingWizardControllerLike } from './onboarding-wizard-types.ts';
21
23
 
22
- /** SDK schema default for behavior.hitlMode (schema-domain-core DEFAULT_CONFIG). */
23
- const HITL_MODE_SCHEMA_DEFAULT = 'balanced';
24
- /** Feature flag that gates behavior.hitlMode preset application in ModeManager. */
25
- const HITL_UX_MODES_FEATURE_FLAG = 'hitl-ux-modes';
26
-
27
24
  export function buildOnboardingApplyRequest(controller: OnboardingWizardControllerLike): OnboardingApplyRequest {
28
25
  const operations: OnboardingApplyOperation[] = [];
29
26
  // Accumulates feature-flag state that onboarding selections require, so the
@@ -132,21 +129,21 @@ export function buildOnboardingApplyRequest(controller: OnboardingWizardControll
132
129
  setConfig('provider.reasoningEffort', controller.getStringFieldValue('default-model.reasoning', controller.runtimeSnapshot?.providerRouting.primaryReasoningEffort ?? 'medium'));
133
130
  const hitlMode = controller.getStringFieldValue('experience.hitl', controller.runtimeSnapshot?.runtimeDefaults.behavior.hitlMode ?? 'balanced');
134
131
  setConfig('behavior.hitlMode', hitlMode);
135
- // behavior.hitlMode is gated by the hitl-ux-modes feature flag: ModeManager
136
- // only applies the chosen preset to the notification router when the flag is
137
- // on. Writing hitlMode without enabling the flag leaves the user's choice
138
- // silently inert. When the user picks a non-default mode, enable the gating
139
- // flag in the same apply batch so the selection actually takes effect. The
140
- // SDK schema default is 'balanced' (schema-domain-core DEFAULT_CONFIG); a
141
- // choice equal to the default is left to the flag's own default-off state.
142
- if (hitlMode !== HITL_MODE_SCHEMA_DEFAULT) {
143
- featureFlagOverrides.set(HITL_UX_MODES_FEATURE_FLAG, 'enabled');
144
- }
132
+ // behavior.hitlMode IS the enablement key of the hitl-ux-modes capability
133
+ // (active for quiet/balanced/operator, off only at 'off'), so the write
134
+ // above already carries the enablement — no separate gating write exists,
135
+ // and adding one here would clobber a non-default choice with the stock
136
+ // mode when the batch flushes.
145
137
  setConfig('behavior.guidanceMode', controller.getStringFieldValue('experience.guidance', controller.runtimeSnapshot?.runtimeDefaults.behavior.guidanceMode ?? 'minimal'));
146
138
  setConfig('permissions.mode', controller.getStringFieldValue('experience.permissions', controller.runtimeSnapshot?.runtimeDefaults.permissionsMode ?? 'prompt'));
147
139
  setConfig('storage.secretPolicy', controller.getStringFieldValue('external-services.secret-policy', controller.runtimeSnapshot?.runtimeDefaults.secretStoragePolicy ?? 'preferred_secure'));
148
140
 
149
- setSecret('OPENAI_API_KEY', controller.getStringFieldValue('providers.openai-api-key', ''));
141
+ // Provider-agnostic key capture: store each entered key under the provider's
142
+ // own declared secret key. Empty fields are skipped (setSecret no-ops), so
143
+ // a blank field keeps any existing key untouched.
144
+ for (const target of deriveProviderKeyCaptureTargets(controller.runtimeSnapshot?.providerAccounts?.providers)) {
145
+ setSecret(target.apiKeyEnvVar, controller.getStringFieldValue(providerKeyFieldId(target.providerId), ''));
146
+ }
150
147
 
151
148
  if (shouldShowCloudflareStep(controller)) {
152
149
  addCloudflareOperations(controller, operations, setSecret);
@@ -190,12 +187,13 @@ export function buildOnboardingApplyRequest(controller: OnboardingWizardControll
190
187
  }
191
188
  }
192
189
  // Feature units from the guided feature steps (safety, memory & context,
193
- // telemetry, automation, provider, advanced): enable/disable each flag the
194
- // user changed from its default and write its chosen sub-options.
190
+ // telemetry, automation, provider, advanced): enable/disable each feature
191
+ // the user changed from its default and write its chosen sub-options.
195
192
  applyFeatureUnitOperations(controller, setConfig, featureFlagOverrides);
196
193
 
197
- // Surface/server flags are authoritative for the network-exposing flags they
198
- // own — apply them last so they win over any feature-step override.
194
+ // Surface/server capabilities are authoritative for the network-exposing
195
+ // features they own — apply them last so they win over any feature-step
196
+ // override.
199
197
  for (const surfaceFlag of getServerSurfaceFeatureFlags({
200
198
  serverBacked: hasServers,
201
199
  web: browserAccess,
@@ -203,8 +201,11 @@ export function buildOnboardingApplyRequest(controller: OnboardingWizardControll
203
201
  })) {
204
202
  featureFlagOverrides.set(surfaceFlag, 'enabled');
205
203
  }
204
+ // Each selection lands as a plain domain-settings write on the feature's
205
+ // real enablement key (e.g. sandbox.enabled, controlPlane.gateway).
206
206
  for (const flagId of [...featureFlagOverrides.keys()].sort((left, right) => left.localeCompare(right))) {
207
- setConfig(`featureFlags.${flagId}`, featureFlagOverrides.get(flagId)!);
207
+ const write = featureEnablementWrite(flagId, featureFlagOverrides.get(flagId)! === 'enabled');
208
+ if (write) setConfig(write.key, write.value);
208
209
  }
209
210
 
210
211
  acknowledge('providers', 'providers.reviewed');
@@ -282,6 +282,23 @@ const SURFACE_OVERLAYS: readonly SurfaceOverlay[] = [
282
282
  { configKey: 'surfaces.mattermost.defaultChannelId', label: 'Mattermost default channel ID', hint: 'Fallback Mattermost channel for outbound messages.', placeholder: 'channel id' },
283
283
  ],
284
284
  },
285
+ {
286
+ onboardingId: 'telephony',
287
+ sdkSurface: 'telephony',
288
+ label: 'Telephony surface',
289
+ hint: 'Enable SMS/voice notification delivery — direct Twilio, or a self-hosted bridge.',
290
+ fields: [
291
+ { configKey: 'surfaces.telephony.provider', label: 'Telephony provider', hint: 'twilio for direct Twilio delivery, bridge for a self-hosted telephony bridge.', placeholder: 'twilio' },
292
+ { configKey: 'surfaces.telephony.mode', label: 'Telephony mode', hint: 'sms, voice (call with spoken text), or bridge.', placeholder: 'sms' },
293
+ { configKey: 'surfaces.telephony.accountSid', label: 'Twilio account SID', hint: 'Account SID for direct Twilio delivery.', placeholder: 'AC...' },
294
+ { configKey: 'surfaces.telephony.authToken', label: 'Twilio auth token', hint: 'Auth token for direct Twilio delivery.', placeholder: 'auth token' },
295
+ { configKey: 'surfaces.telephony.fromNumber', label: 'From number', hint: 'The E.164 number outbound SMS/calls originate from.', placeholder: '+15551234567' },
296
+ { configKey: 'surfaces.telephony.defaultRecipient', label: 'Default recipient', hint: 'Fallback E.164 number for outbound delivery.', placeholder: '+15557654321' },
297
+ { configKey: 'surfaces.telephony.bridgeUrl', label: 'Bridge URL', hint: 'Base URL of the self-hosted telephony bridge (bridge provider only).', placeholder: 'https://telephony-bridge.example.test' },
298
+ { configKey: 'surfaces.telephony.token', label: 'Bridge token', hint: 'Token used to authenticate with the telephony bridge.', placeholder: 'bridge token' },
299
+ { configKey: 'surfaces.telephony.webhookSecret', label: 'Webhook secret', hint: 'Secret used to verify inbound telephony webhooks.', placeholder: 'webhook secret' },
300
+ ],
301
+ },
285
302
  {
286
303
  onboardingId: 'matrix',
287
304
  sdkSurface: 'matrix',
@@ -106,7 +106,7 @@ export function pushLegacyDaemonMigrationFields(
106
106
  fields.push({
107
107
  kind: 'status',
108
108
  id: 'network.migrate-legacy-daemon-detected',
109
- label: 'Legacy daemon service detected',
109
+ label: 'Separate install-script daemon service detected',
110
110
  // Follow-up: name the unit this host actually resolves (carried on the
111
111
  // snapshot from `resolveConfiguredServiceName` at collection time) — the
112
112
  // MANAGED_SERVICE_NAME fallback only covers snapshots built without it.
@@ -126,7 +126,7 @@ export function pushLegacyDaemonMigrationFields(
126
126
  fields.push({
127
127
  kind: 'action',
128
128
  id: 'network.migrate-legacy-daemon',
129
- label: confirmed ? 'Migrate legacy daemon service now' : 'Preview migration plan',
129
+ label: confirmed ? 'Migrate the install-script daemon service now' : 'Preview migration plan',
130
130
  hint: confirmed
131
131
  ? 'Executes the migration now: new-up-then-old-down, never touching the legacy unit until the new one is verified healthy. Result prints to the feed below.'
132
132
  : 'Prints the migration plan without changing anything. Check "Confirm migration execution" above to arm the real migration.',
@@ -10,6 +10,8 @@ import {
10
10
  } from './onboarding-wizard-external-surfaces.ts';
11
11
  import { buildFeatureUnitSteps } from './onboarding-feature-units.ts';
12
12
  import { countSelected, modelSelectionLabel, normalizeText } from './onboarding-wizard-helpers.ts';
13
+ import { deriveProviderKeyCaptureTargets } from '../../runtime/onboarding/provider-key-capture.ts';
14
+ import { buildProviderKeyInventoryField, buildProviderKeyMaskedFields, buildSubscriptionPosture } from './onboarding-provider-key-fields.ts';
13
15
  import { getDaemonSource, pushDaemonAdoptionFields, pushLegacyDaemonMigrationFields } from './onboarding-wizard-network-adopt.ts';
14
16
  import type { OnboardingWizardControllerLike } from './onboarding-wizard-types.ts';
15
17
  import type { OnboardingWizardAcknowledgementFieldDefinition, OnboardingWizardActionFieldDefinition, OnboardingWizardChecklistFieldDefinition, OnboardingWizardExternalSurfaceStepId, OnboardingWizardFieldDefinition, OnboardingWizardModelPickerFieldDefinition, OnboardingWizardRadioFieldDefinition, OnboardingWizardRadioOption, OnboardingWizardStepDefinition } from './onboarding-wizard-types.ts';
@@ -150,10 +152,20 @@ export function buildProvidersStep(controller: OnboardingWizardControllerLike):
150
152
  const providerAck = controller.runtimeDerived.reopenEditAcknowledgements.providers;
151
153
  const activeSubscriptions = controller.runtimeSnapshot?.subscriptions.active ?? [];
152
154
  const pendingSubscriptions = controller.runtimeSnapshot?.subscriptions.pending ?? [];
153
- const openAiActive = activeSubscriptions.some((subscription) => subscription.provider === 'openai');
154
- const openAiPending = pendingSubscriptions.some((subscription) => subscription.provider === 'openai');
155
- const providerSecretCount = controller.runtimeSnapshot?.secrets.records.filter((record) => record.key.endsWith('_API_KEY') || record.key.endsWith('_TOKEN')).length ?? 0;
156
- const openAiApiKeyConfigured = controller.runtimeSnapshot?.secrets.records.some((record) => record.key === 'OPENAI_API_KEY') ?? false;
155
+ // Provider-agnostic subscription posture: one status row per
156
+ // subscription-capable provider (registration truth), plus any provider with
157
+ // a live/pending session, and which provider's in-wizard sign-in applies now.
158
+ const subscription = buildSubscriptionPosture(
159
+ activeSubscriptions.map((entry) => entry.provider),
160
+ pendingSubscriptions.map((entry) => entry.provider),
161
+ controller.runtimeSnapshot?.providerAccounts?.providers,
162
+ );
163
+ const signInPending = subscription.signInPending;
164
+ // Provider-agnostic key capture: one field per provider the registry knows
165
+ // accepts an API key (derived from registration truth — never a hardcoded
166
+ // vendor list). No provider is named as the only option.
167
+ const keyTargets = deriveProviderKeyCaptureTargets(controller.runtimeSnapshot?.providerAccounts?.providers);
168
+ const configuredKeyCount = keyTargets.filter((target) => target.configured).length;
157
169
  const providerReviewField: OnboardingWizardAcknowledgementFieldDefinition = {
158
170
  kind: 'acknowledgement',
159
171
  id: 'providers.reviewed',
@@ -166,46 +178,21 @@ export function buildProvidersStep(controller: OnboardingWizardControllerLike):
166
178
  };
167
179
 
168
180
  const fields: OnboardingWizardFieldDefinition[] = [
169
- {
170
- kind: 'status',
171
- id: 'providers.openai-subscription',
172
- label: 'OpenAI subscription status',
173
- hint: openAiActive
174
- ? 'An OpenAI subscription session is already available.'
175
- : openAiPending
176
- ? 'An OpenAI subscription login is pending.'
177
- : 'No OpenAI subscription session was found in the current runtime state.',
178
- defaultValue: openAiActive ? 'Active' : openAiPending ? 'Pending' : 'Not detected',
179
- },
180
- {
181
- kind: 'status',
182
- id: 'providers.api-key-inventory',
183
- label: 'Provider API key inventory',
184
- hint: providerSecretCount > 0
185
- ? `${providerSecretCount} provider credential reference(s) were found. Values stay masked.`
186
- : 'No provider API key references were detected in the current runtime state.',
187
- defaultValue: providerSecretCount > 0 ? `${providerSecretCount} configured` : 'None detected',
188
- },
189
- {
190
- kind: 'masked',
191
- id: 'providers.openai-api-key',
192
- label: 'OpenAI API key',
193
- hint: openAiApiKeyConfigured
194
- ? 'An OpenAI API key is already stored. Leave blank to keep it; enter a new key to replace it through the secret manager.'
195
- : 'Optional: enter an OpenAI API key now. The value is stored through the secret manager, not in config.',
196
- placeholder: openAiApiKeyConfigured ? 'already configured' : 'sk-...',
197
- defaultValue: '',
198
- },
199
- ...(openAiActive ? [] : [
181
+ ...subscription.statusFields,
182
+ buildProviderKeyInventoryField(keyTargets),
183
+ // One masked field per key-accepting provider. Entered values are stored
184
+ // through the secret manager at apply and the provider re-registers live.
185
+ ...buildProviderKeyMaskedFields(keyTargets),
186
+ ...(!subscription.signInProviderId ? [] : [
200
187
  {
201
188
  kind: 'action' as const,
202
189
  id: 'providers.openai-subscription-start',
203
190
  action: 'start-openai-subscription' as const,
204
- label: openAiPending ? 'Restart OpenAI subscription sign-in' : 'Start OpenAI subscription sign-in',
191
+ label: signInPending ? 'Restart OpenAI subscription sign-in' : 'Start OpenAI subscription sign-in',
205
192
  hint: 'Opens the OpenAI sign-in flow from the wizard and records pending login state here.',
206
- defaultValue: openAiPending ? 'Restart' : 'Start',
193
+ defaultValue: signInPending ? 'Restart' : 'Start',
207
194
  },
208
- ...(openAiPending ? [
195
+ ...(signInPending ? [
209
196
  {
210
197
  kind: 'text' as const,
211
198
  id: 'providers.openai-authorization-url',
@@ -239,12 +226,13 @@ export function buildProvidersStep(controller: OnboardingWizardControllerLike):
239
226
  id: 'provider-access',
240
227
  title: 'AI provider access',
241
228
  shortLabel: 'Providers',
242
- description: 'Review subscription posture and optionally add an OpenAI API key directly through the wizard.',
229
+ description: 'Review subscription posture and optionally add an API key for any provider that accepts one, directly through the wizard.',
243
230
  summaryTitle: 'Provider access summary',
244
231
  summaryLines: [
245
- `OpenAI subscription: ${openAiActive ? 'active' : openAiPending ? 'pending' : 'not detected'}`,
246
- `OpenAI API key: ${openAiApiKeyConfigured ? 'configured' : 'not detected'}`,
247
- `Provider credential references: ${providerSecretCount}`,
232
+ subscription.summaryLine,
233
+ keyTargets.length === 0
234
+ ? 'Provider API keys: no key-accepting providers registered'
235
+ : `Provider API keys: ${configuredKeyCount}/${keyTargets.length} configured`,
248
236
  `Review: ${controller.getFieldValueLabel(providerReviewField)}`,
249
237
  ],
250
238
  fields,
@@ -2,14 +2,14 @@ import type { CommandContext } from './command-registry.ts';
2
2
  import type { Panel } from '../panels/types.ts';
3
3
  import type { PanelManager } from '../panels/panel-manager.ts';
4
4
 
5
- // W6.1 (the purge): every instanceof-routed branch this file used to carry
5
+ // (the purge): every instanceof-routed branch this file used to carry
6
6
  // (FileExplorerPanel/FilePreviewPanel/SymbolOutlinePanel — DELETE;
7
7
  // ApprovalPanel/TasksPanel/OrchestrationPanel/AgentInspectorPanel —
8
8
  // RETIRE-INTO-FLEET) targeted a panel class that no longer exists. Panels
9
9
  // migrated onto the formal `Panel.handlePanelIntegrationAction` hook (the
10
10
  // preferred seam — see the comment below) are unaffected; this function now
11
11
  // only provides that passthrough. See
12
- // .goodvibes/audit/2026-07-04-wave6-briefs.json (W6.1) for the disposition
12
+ // .goodvibes/audit/2026-07-04-wave6-briefs.json for the disposition
13
13
  // map.
14
14
  export function handlePanelIntegrationAction(
15
15
  panelManager: PanelManager,
@@ -3,7 +3,7 @@
3
3
  //
4
4
  // Pure mouse-geometry helpers for the panel workspace, extracted from
5
5
  // handler-feed-routes.ts so that file stays under the 800-line architecture
6
- // cap once the UX-C focus verbs and the DEBT-5 paste-flood guard both live in
6
+ // cap once the focus verbs and the paste-flood guard both live in
7
7
  // it. These map a terminal (row, col) onto the panel/pane/workspace-tab under
8
8
  // the cursor. No dependency on the feed-route state types, so no import cycle.
9
9
  // ---------------------------------------------------------------------------
@@ -1,5 +1,5 @@
1
1
  // ---------------------------------------------------------------------------
2
- // panel-paste-flood-guard.ts — DEBT-5 item 5.
2
+ // panel-paste-flood-guard.ts — item 5.
3
3
  //
4
4
  // A terminal WITHOUT bracketed paste delivers a pasted block as a burst of
5
5
  // discrete 1-char 'text' tokens (isPasteToken stays false for every one of
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Provider key intake — capture a provider's API key at the moment the user
3
+ * selects an unconfigured provider or model, store it through the secrets
4
+ * manager (never env-only, never cleartext into config or provider JSON), let
5
+ * the provider re-register live, then complete the user's original selection.
6
+ *
7
+ * The decision of whether a key is needed is read from the provider's own
8
+ * registration-time auth state (describeAuthState / isConfigured) — never a
9
+ * hardcoded vendor list — so this works for any provider the registry knows
10
+ * accepts a key. The secrets-store key name is the provider's declared auth env
11
+ * var; it is used only as the storage key and is never shown to the user.
12
+ */
13
+
14
+ import type { LLMProvider } from '@pellux/goodvibes-sdk/platform/providers';
15
+ import type { ConcealedInputRequest } from './concealed-input.ts';
16
+ import type { CommandContext } from './command-registry.ts';
17
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
18
+
19
+ /** The intake decision derived purely from a provider's registration truth. */
20
+ export interface ProviderKeyIntakeDecision {
21
+ /** True when the provider accepts a key and does not yet have one. */
22
+ readonly needsKey: boolean;
23
+ /** Secrets-store key to write the value under (the provider's auth env var). */
24
+ readonly secretKey?: string;
25
+ /** True when the provider already has a credential (no prompt needed). */
26
+ readonly alreadyConfigured: boolean;
27
+ }
28
+
29
+ /**
30
+ * Decide whether to prompt for a key, reading only the provider's own auth
31
+ * state. A provider that is already configured, works anonymously, or declares
32
+ * no accepted env var is left alone (proceed straight through). A provider that
33
+ * does not declare an auth state at all is treated per isConfigured(), and an
34
+ * unknown state is assumed configured so a working selection is never nagged.
35
+ */
36
+ export function decideProviderKeyIntake(provider: LLMProvider | undefined): ProviderKeyIntakeDecision {
37
+ if (!provider) return { needsKey: false, alreadyConfigured: false };
38
+ const state = provider.describeAuthState?.();
39
+ if (state) {
40
+ if (state.configured) return { needsKey: false, alreadyConfigured: true };
41
+ if (state.anonymousReady) return { needsKey: false, alreadyConfigured: false };
42
+ const secretKey = state.authEnvVars[0];
43
+ if (secretKey) return { needsKey: true, secretKey, alreadyConfigured: false };
44
+ return { needsKey: false, alreadyConfigured: false };
45
+ }
46
+ const configured = provider.isConfigured?.() ?? true;
47
+ return { needsKey: false, alreadyConfigured: configured };
48
+ }
49
+
50
+ /** Human-facing name for a provider — plain, vendor-neutral, never an env var. */
51
+ export function providerIntakeLabel(providerId: string): string {
52
+ return `the ${providerId} provider`;
53
+ }
54
+
55
+ /** Dependencies the intake orchestrator needs, decoupled from the command context for testing. */
56
+ export interface ProviderKeyIntakeDeps {
57
+ /** The provider instance whose registration-time auth state drives the decision. */
58
+ readonly provider: LLMProvider | undefined;
59
+ /** Canonical secret store — the same path /secrets set writes through. */
60
+ readonly secretsManager?: {
61
+ set(key: string, value: string, options?: { scope?: 'project' | 'user'; medium?: 'secure' | 'plaintext' }): Promise<void>;
62
+ } | undefined;
63
+ /** Re-resolves credentials and re-registers providers live (no restart). */
64
+ readonly refreshProviderCredentials?: (() => Promise<void>) | undefined;
65
+ /** Begins one line of concealed composer input for the key. */
66
+ readonly beginConcealedInput?: ((request: ConcealedInputRequest) => void) | undefined;
67
+ /** Surfaces short status lines to the transcript. */
68
+ readonly print: (text: string) => void;
69
+ }
70
+
71
+ /**
72
+ * If the selected provider needs a key, open the concealed prompt, store the
73
+ * pasted value through the secrets manager, re-register the provider live, then
74
+ * run `proceed` (the user's original selection). If no key is needed — or the
75
+ * concealed surface is unavailable — `proceed` runs immediately. `proceed`
76
+ * always runs exactly once, including when the user skips (empty/Escape), so a
77
+ * selection is never stranded.
78
+ */
79
+ export function runProviderKeyIntake(
80
+ providerId: string,
81
+ deps: ProviderKeyIntakeDeps,
82
+ proceed: () => void,
83
+ ): void {
84
+ const decision = decideProviderKeyIntake(deps.provider);
85
+ if (!decision.needsKey || !decision.secretKey) {
86
+ proceed();
87
+ return;
88
+ }
89
+ const { secretsManager, beginConcealedInput } = deps;
90
+ if (!secretsManager || !beginConcealedInput) {
91
+ // No canonical store or no concealed surface here — keep the selection
92
+ // rather than blocking it; the user can add the key via /secrets set later.
93
+ proceed();
94
+ return;
95
+ }
96
+ const secretKey = decision.secretKey;
97
+ const label = providerIntakeLabel(providerId);
98
+ let completed = false;
99
+ const finishOnce = (): void => {
100
+ if (completed) return;
101
+ completed = true;
102
+ proceed();
103
+ };
104
+ deps.print(`Enter the API key for ${label} (input is masked; press Enter to store, Esc to skip).`);
105
+ beginConcealedInput({
106
+ label: providerId,
107
+ onSubmit: (value) => {
108
+ if (value.length === 0) {
109
+ deps.print(`No key entered for ${label}; selection kept, no key stored.`);
110
+ finishOnce();
111
+ return;
112
+ }
113
+ void (async () => {
114
+ try {
115
+ await secretsManager.set(secretKey, value, { scope: 'user', medium: 'secure' });
116
+ await deps.refreshProviderCredentials?.();
117
+ deps.print(`Stored the API key for ${label} and registered it live (value hidden). No restart needed.`);
118
+ } catch (error) {
119
+ deps.print(`Could not store the key for ${label}: ${summarizeError(error)}`);
120
+ }
121
+ finishOnce();
122
+ })();
123
+ },
124
+ onCancel: () => {
125
+ deps.print(`Key entry skipped for ${label}; selection kept, no key stored.`);
126
+ finishOnce();
127
+ },
128
+ });
129
+ }
130
+
131
+ /**
132
+ * Command-context adapter for {@link runProviderKeyIntake}: resolves the live
133
+ * provider, secrets manager, live re-registration hook, and concealed-input
134
+ * surface from the shell command context, then runs the intake before
135
+ * completing the user's original selection (`proceed`).
136
+ */
137
+ export function ensureProviderKeyThenSelect(
138
+ ctx: CommandContext,
139
+ providerId: string,
140
+ proceed: () => void,
141
+ ): void {
142
+ // Resolve the live provider defensively: a command context that routes only
143
+ // through the provider API (no raw registry access) leaves the intake a no-op
144
+ // and the selection completes unchanged.
145
+ let registry: CommandContext['provider']['providerRegistry'] | undefined;
146
+ let provider: ReturnType<CommandContext['provider']['providerRegistry']['tryGet']> | undefined;
147
+ try {
148
+ registry = ctx.provider?.providerRegistry;
149
+ provider = registry?.tryGet?.(providerId);
150
+ } catch {
151
+ registry = undefined;
152
+ provider = undefined;
153
+ }
154
+ runProviderKeyIntake(
155
+ providerId,
156
+ {
157
+ provider,
158
+ secretsManager: ctx.platform?.secretsManager,
159
+ refreshProviderCredentials: registry
160
+ ? () => registry!.refreshProviderCredentials()
161
+ : undefined,
162
+ beginConcealedInput: ctx.beginConcealedInput,
163
+ print: (text) => ctx.print(text),
164
+ },
165
+ proceed,
166
+ );
167
+ }
@@ -59,14 +59,14 @@ export const SANDBOX_EGRESS_ALLOWLIST_SYNTHETIC_SETTING: ConfigSetting = {
59
59
  key: SANDBOX_EGRESS_ALLOWLIST_CONFIG_KEY,
60
60
  type: 'string',
61
61
  default: [],
62
- description: 'Comma-separated command base names (or "*" for all) whose network access is re-enabled inside the per-command exec sandbox boundary, as a named escalation the approval flow surfaces ("wants network"). Empty = the sandbox disables network for every command. Only takes effect when sandbox.enabled is on, the graduation-tracked exec-sandbox feature flag is on, and bubblewrap is available (Linux only — reports unavailable elsewhere).',
62
+ description: 'Comma-separated command base names (or "*" for all) whose network access is re-enabled inside the per-command exec sandbox boundary, as a named escalation the approval flow surfaces ("wants network"). Empty = the sandbox disables network for every command. Only takes effect when sandbox.enabled is on, the exec-sandbox setting is on, and bubblewrap is available (Linux only — reports unavailable elsewhere).',
63
63
  };
64
64
 
65
65
  export const SANDBOX_WORKSPACE_WRITABLE_SYNTHETIC_SETTING: ConfigSetting = {
66
66
  key: SANDBOX_WORKSPACE_WRITABLE_CONFIG_KEY,
67
67
  type: 'string',
68
68
  default: [],
69
- description: 'Comma-separated absolute paths outside the workspace bound writable into the per-command exec sandbox boundary, as a named escalation the approval flow surfaces ("wants path outside workspace"). Empty = only the workspace (and an isolated /tmp) is writable. Only takes effect when sandbox.enabled is on, the graduation-tracked exec-sandbox feature flag is on, and bubblewrap is available (Linux only — reports unavailable elsewhere).',
69
+ description: 'Comma-separated absolute paths outside the workspace bound writable into the per-command exec sandbox boundary, as a named escalation the approval flow surfaces ("wants path outside workspace"). Empty = only the workspace (and an isolated /tmp) is writable. Only takes effect when sandbox.enabled is on, the exec-sandbox setting is on, and bubblewrap is available (Linux only — reports unavailable elsewhere).',
70
70
  };
71
71
 
72
72
  function buildSandboxExecListEntry(configManager: Pick<ConfigManager, 'get'>, setting: ConfigSetting): SettingEntry {
@@ -37,7 +37,7 @@ export class SelectionModal {
37
37
  public allowSearch = true;
38
38
  public customActions: Map<string, SelectionAction> = new Map();
39
39
  /**
40
- * UX-C vocab unification: overrides the default per-primaryAction Enter
40
+ * vocab unification: overrides the default per-primaryAction Enter
41
41
  * verb ("Select"/"Toggle"/"Edit"/"Delete") shown in the footer hint. Used by
42
42
  * callers whose items are really commands (e.g. /help) so the hint reads
43
43
  * "[Enter] Run", matching the slash-command palette's own verb, instead of
@@ -92,6 +92,11 @@ export function activateSelected(ctx: ActivateSelectedContext): void {
92
92
  const idx = setting.enumValues.indexOf(entry.currentValue as string);
93
93
  const nextIdx = (idx + 1) % setting.enumValues.length;
94
94
  ctx.setValue(setting.key as ConfigKey, setting.enumValues[nextIdx]);
95
+ } else if (setting.type === 'object') {
96
+ // Object-typed keys (e.g. pricing.modelPrices) edit as JSON: seed the
97
+ // buffer with the current value's JSON so the edit starts from truth.
98
+ ctx.setEditingMode(true);
99
+ ctx.setEditBuffer(JSON.stringify(entry.currentValue ?? {}));
95
100
  } else if (setting.type === 'string' || setting.type === 'number') {
96
101
  // Enter inline edit mode
97
102
  ctx.setEditingMode(true);
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * settings-modal-adjustment — pure adjustment helpers for SettingsModal.
3
3
  *
4
- * These functions encapsulate the two directional-adjustment operations:
4
+ * These functions encapsulate the directional-adjustment operation:
5
5
  * - adjustSelected: cycle enum/boolean/number values via left/right arrow keys
6
- * - toggleSelectedFlag: toggle a feature flag between enabled and disabled
7
6
  *
8
7
  * Each function takes its dependencies as explicit arguments rather than
9
8
  * accessing class-level state directly, following the same pattern as
@@ -13,11 +12,9 @@
13
12
  import type { ConfigKey } from '@pellux/goodvibes-sdk/platform/config';
14
13
  import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
15
14
  import type { FeatureFlagManager } from '@/runtime/index.ts';
16
- import type { FlagState } from '@/runtime/index.ts';
17
15
  import type { FlagEntry, McpEntry, SettingEntry } from './settings-modal-types.ts';
18
16
  import { buildMcpEntries } from './settings-modal-data.ts';
19
17
  import { getNumericAdjustmentMeta, roundToPrecision } from './settings-modal-behavior.ts';
20
- import { applyFlagState } from './settings-modal-mutations.ts';
21
18
  import type { McpRegistry } from '@pellux/goodvibes-sdk/platform/mcp';
22
19
 
23
20
  // ---------------------------------------------------------------------------
@@ -45,10 +42,9 @@ export function adjustSelected(
45
42
  ): void {
46
43
  if (ctx.editingMode) return;
47
44
 
48
- // Feature-unit toggle headers (setting.type === 'boolean', key featureFlags.<id>)
49
- // now live across topical categories, not a single flags tab. They fall through
50
- // to the boolean branch below, which routes through setValue → the modal's
51
- // flag-toggle chokepoint. No category-special-case is needed here anymore.
45
+ // Feature-unit headers are the real config rows for their enablement keys
46
+ // (boolean or enum), so they fall through to the ordinary branches below —
47
+ // a plain config write the settings bridge forwards to the gate manager.
52
48
 
53
49
  if (ctx.currentCategory === 'mcp') {
54
50
  const entry = ctx.getSelectedMcp();
@@ -96,26 +92,3 @@ export function adjustSelected(
96
92
  ctx.setValue(setting.key as ConfigKey, nextValue);
97
93
  }
98
94
  }
99
-
100
- // ---------------------------------------------------------------------------
101
- // toggleSelectedFlag
102
- // ---------------------------------------------------------------------------
103
-
104
- export interface ToggleSelectedFlagContext {
105
- readonly featureFlagManager: FeatureFlagManager | null;
106
- readonly configManager: ConfigManager | null;
107
- getSelectedFlag(): FlagEntry | null;
108
- }
109
-
110
- export function toggleSelectedFlag(ctx: ToggleSelectedFlagContext): void {
111
- const flagEntry = ctx.getSelectedFlag();
112
- if (!flagEntry || !ctx.featureFlagManager || !ctx.configManager) return;
113
-
114
- const { state } = flagEntry;
115
-
116
- // Killed flags are blocked
117
- if (state === 'killed') return;
118
-
119
- const newState: FlagState = state === 'enabled' ? 'disabled' : 'enabled';
120
- applyFlagState(flagEntry, newState, ctx.featureFlagManager, ctx.configManager);
121
- }