@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
@@ -95,7 +95,7 @@ export function handleSelectionModalToken(state: SelectionRouteState, token: Inp
95
95
  dispatchSelectionAction(action, selected);
96
96
  }
97
97
  } else if (state.selectionModal.allowSearch) {
98
- // UX-C instant filter: an unclaimed keystroke arms search AND starts
98
+ // instant filter: an unclaimed keystroke arms search AND starts
99
99
  // the query immediately, instead of silently doing nothing until the
100
100
  // user discovers '/' first (the "help search needs '/' arming while
101
101
  // the palette filters instantly" evaluator finding — /help has no
@@ -108,7 +108,7 @@ export function handleSelectionModalToken(state: SelectionRouteState, token: Inp
108
108
  }
109
109
  } else if (token.type === 'key') {
110
110
  if (token.logicalName === 'escape') {
111
- // UX-C: ONE Escape always closes the modal, regardless of search state.
111
+ // ONE Escape always closes the modal, regardless of search state.
112
112
  // Clearing an in-progress query is Backspace's job, not Esc's — the old
113
113
  // two-stage contract (1st Esc clears query, 2nd blurs search, 3rd
114
114
  // finally closes) was the "help modal took 3 Escapes after searching"
@@ -234,6 +234,8 @@ type SettingsRouteState = {
234
234
  searchQuery: string;
235
235
  commitEdit: () => void;
236
236
  toggleSelectedFlag: () => void;
237
+ /** Scroll the documentation pane (PgUp/PgDn); the renderer clamps and shows honest markers. */
238
+ scrollContext?: (delta: number) => void;
237
239
  activateSelected: () => void;
238
240
  handleSubscriptionLogoutKey?: (key: string) => 'confirmed' | 'cancelled' | 'absorbed' | 'inactive';
239
241
  adjustSelected: (direction: 'left' | 'right', step?: number) => void;
@@ -415,6 +417,12 @@ export function handleSettingsModalToken(state: SettingsRouteState, token: Input
415
417
  const reset = state.settingsModal.resetSelected?.();
416
418
  if (reset) syncRuntimeAfterSettingReset(state.commandContext, reset.key, reset.value);
417
419
  }
420
+ else if (token.logicalName === 'pageup' && !state.settingsModal.editingMode) {
421
+ state.settingsModal.scrollContext?.(-3);
422
+ }
423
+ else if (token.logicalName === 'pagedown' && !state.settingsModal.editingMode) {
424
+ state.settingsModal.scrollContext?.(3);
425
+ }
418
426
  else if (token.logicalName === 'tab' && !state.settingsModal.searchFocused) {
419
427
  if (state.settingsModal.toggleFocusPane) state.settingsModal.toggleFocusPane();
420
428
  else if (focusPane === 'categories') state.settingsModal.focusSettings?.();
@@ -556,9 +564,9 @@ type ConfigModalRouteState = {
556
564
  };
557
565
 
558
566
  /**
559
- * Route a key to the generic config-modal host (W6.1 MIGRATE-TO-MODAL
567
+ * Route a key to the generic config-modal host (MIGRATE-TO-MODAL
560
568
  * surfaces). The host owns the reserved navigation keys (Esc, up/down/j/k tab
561
- * left/right, and now '/' — DEBT-5 item 1's type-to-filter, armed the same
569
+ * left/right, and now '/' — item 1's type-to-filter, armed the same
562
570
  * way as scrollable-list-panel.ts's opt-in filter and SettingsModal's own
563
571
  * '/'-armed search); every other key is offered to the active surface's
564
572
  * declarative action table (fireAction handles the two-press confirm for
@@ -227,7 +227,7 @@ export function handleModalTokenRoutes(state: ModalTokenRouteState, token: Input
227
227
  return withState(state, true);
228
228
  }
229
229
 
230
- // W6.1 retirement: the live-tail, agent-detail, and process-modal token
230
+ // retirement: the live-tail, agent-detail, and process-modal token
231
231
  // routes were removed with those modals (F2 now opens Fleet, which subsumes
232
232
  // the live process tree; the three modals were structurally unreachable after
233
233
  // the F2 repoint and were deleted).
@@ -138,10 +138,10 @@ export async function handleMigrateLegacyDaemonServiceForHandler(handler: InputH
138
138
  legacy,
139
139
  );
140
140
  handler.commandContext?.print?.(
141
- ['Migrate legacy daemon service:', ...result.lines.map((line: string) => ` ${line}`)].join('\n'),
141
+ ['Migrate install-script daemon service:', ...result.lines.map((line: string) => ` ${line}`)].join('\n'),
142
142
  );
143
143
  } catch (error) {
144
- handler.commandContext?.print?.(`Migrate legacy daemon service failed: ${summarizeError(error)}`);
144
+ handler.commandContext?.print?.(`Migrate install-script daemon service failed: ${summarizeError(error)}`);
145
145
  } finally {
146
146
  handler.onboardingApplyPending = false;
147
147
  handler.requestRender();
@@ -3,6 +3,7 @@ import { beginOpenAICodexLogin, exchangeOpenAICodexCode } from '@pellux/goodvibe
3
3
  import { summarizeError, openExternalUrl } from '@pellux/goodvibes-sdk/platform/utils';
4
4
  import { getProviderIdFromModel } from '../config/provider-model.ts';
5
5
  import { buildProviderAccountSnapshot } from '@/runtime/index.ts';
6
+ import { enrichProviderAccountsSnapshot } from '../runtime/onboarding/provider-key-capture.ts';
6
7
  import { handleConnectExistingDaemonForHandler, handleMigrateLegacyDaemonServiceForHandler } from './handler-onboarding-daemon-adopt.ts';
7
8
  import { detectLegacyUnit, resolveConfiguredServiceName } from '../runtime/legacy-daemon-migration.ts';
8
9
  import { OnboardingWizardController, type OnboardingWizardAction, type OnboardingWizardApplyFeedback } from './onboarding/onboarding-wizard.ts';
@@ -376,12 +377,12 @@ export async function refreshOnboardingHydrationForHandler(handler: InputHandler
376
377
  list: () => handler.uiServices.platform.surfaceRegistry.syncConfiguredSurfaces(),
377
378
  },
378
379
  providerAccounts: {
379
- loadSnapshot: () => buildProviderAccountSnapshot({
380
+ loadSnapshot: async () => enrichProviderAccountsSnapshot(await buildProviderAccountSnapshot({
380
381
  providerRegistry: handler.uiServices.providers.providerRegistry,
381
382
  serviceRegistry: handler.uiServices.platform.serviceRegistry,
382
383
  subscriptionManager: handler.uiServices.platform.subscriptionManager,
383
384
  secretsManager: handler.uiServices.platform.secretsManager,
384
- }),
385
+ }), handler.uiServices.providers.providerRegistry),
385
386
  },
386
387
  legacyDaemon: {
387
388
  detect: () => ({ ...detectLegacyUnit({ homeDir: handler.uiServices.environment.homeDirectory }), trackedServiceName: resolveConfiguredServiceName(handler.uiServices.platform.configManager) }),
@@ -3,6 +3,7 @@ import type { InputToken } from '@pellux/goodvibes-sdk/platform/core';
3
3
  import type { CommandContext } from './command-registry.ts';
4
4
  import type { CapabilityFilter, CategoryFilter, ModelPickerModal } from './model-picker.ts';
5
5
  import { MODEL_PICKER_CHROME_LINES } from '../renderer/model-picker-overlay.ts';
6
+ import { ensureProviderKeyThenSelect } from './provider-key-intake.ts';
6
7
  import { resolveAndValidatePath } from '@pellux/goodvibes-sdk/platform/utils';
7
8
  import { logger } from '@pellux/goodvibes-sdk/platform/utils';
8
9
  import type { BlockActionId } from '../renderer/block-actions.ts';
@@ -66,15 +67,17 @@ export function handleModelPickerToken(state: ModelPickerRouteState, token: Inpu
66
67
  } else {
67
68
  const target = state.modelPicker.target;
68
69
  const handled = state.onModelPickerCommit?.() ?? false;
69
- if (!handled) {
70
- state.commandContext?.completeModelSelection?.({
71
- model: selected,
72
- effort: currentEffort,
73
- target,
74
- });
75
- }
76
70
  state.modelPicker.close();
77
71
  if (state.modalStack[state.modalStack.length - 1] === 'modelPicker') state.modalStack.pop();
72
+ const ctx = state.commandContext;
73
+ if (!handled && ctx) {
74
+ // Selecting an unconfigured provider captures its key inline
75
+ // (concealed prompt → secrets manager → live re-register) and
76
+ // only then completes the original selection.
77
+ ensureProviderKeyThenSelect(ctx, selected.provider, () => {
78
+ ctx.completeModelSelection?.({ model: selected, effort: currentEffort, target });
79
+ });
80
+ }
78
81
  }
79
82
  }
80
83
  } else if (mode === 'provider') {
@@ -88,12 +91,16 @@ export function handleModelPickerToken(state: ModelPickerRouteState, token: Inpu
88
91
  } else if (mode === 'effort') {
89
92
  const model = state.modelPicker.pendingModel;
90
93
  const effort = state.modelPicker.effortLevels[idx];
91
- if (model && effort) {
92
- const handled = state.onModelPickerCommit?.() ?? false;
93
- if (!handled) state.commandContext?.completeModelSelection?.({ model, effort, target: state.modelPicker.target });
94
- }
94
+ const target = state.modelPicker.target;
95
+ const handled = model && effort ? (state.onModelPickerCommit?.() ?? false) : false;
95
96
  state.modelPicker.close();
96
97
  if (state.modalStack[state.modalStack.length - 1] === 'modelPicker') state.modalStack.pop();
98
+ const ctx = state.commandContext;
99
+ if (model && effort && !handled && ctx) {
100
+ ensureProviderKeyThenSelect(ctx, model.provider, () => {
101
+ ctx.completeModelSelection?.({ model, effort, target });
102
+ });
103
+ }
97
104
  } else if (mode === 'embeddingProvider') {
98
105
  const selectedProvider = state.modelPicker.embeddingProviders[idx];
99
106
  if (selectedProvider) {
@@ -113,10 +120,16 @@ export function handleModelPickerToken(state: ModelPickerRouteState, token: Inpu
113
120
  state.modelPicker.contextCapError = null;
114
121
  const validCap = parsedCap !== null && parsedCap > 0 && parsedCap <= 10_000_000 ? parsedCap : null;
115
122
  const effort = state.commandContext?.session.runtime.reasoningEffort ?? 'medium';
123
+ const target = state.modelPicker.target;
116
124
  const handled = state.onModelPickerCommit?.() ?? false;
117
- if (!handled) state.commandContext?.completeModelSelection?.({ model: capModel, effort, contextCap: validCap, target: state.modelPicker.target });
118
125
  state.modelPicker.close();
119
126
  if (state.modalStack[state.modalStack.length - 1] === 'modelPicker') state.modalStack.pop();
127
+ const ctx = state.commandContext;
128
+ if (!handled && ctx) {
129
+ ensureProviderKeyThenSelect(ctx, capModel.provider, () => {
130
+ ctx.completeModelSelection?.({ model: capModel, effort, contextCap: validCap, target });
131
+ });
132
+ }
120
133
  }
121
134
  } else {
122
135
  state.modelPicker.close();
@@ -214,7 +227,7 @@ function cycleCapabilityFilter(modelPicker: ModelPickerModal): void {
214
227
  modelPicker.setCapabilityFilter(cycle[(cur + 1) % cycle.length]!);
215
228
  }
216
229
 
217
- // W6.2 e / W6.1 retirement: the process-modal and live-tail-modal token routes
230
+ // e / retirement: the process-modal and live-tail-modal token routes
218
231
  // were removed with those modals (F2 now opens Fleet, which subsumes the live
219
232
  // process tree). ProcessModal/AgentDetailModal/LiveTailModal were structurally
220
233
  // unreachable after the F2 repoint and were deleted.
@@ -84,7 +84,7 @@ export function handleGlobalShortcutToken(
84
84
  // unclaimed key once panelFocused is true and reports it handled). The
85
85
  // practical effect was that F2 could OPEN+focus the Fleet panel exactly
86
86
  // once; every subsequent press while already focused vanished silently —
87
- // "F2 pressed 4x never closed the panel" (UX-C evaluator finding). Routing
87
+ // "F2 pressed 4x never closed the panel" (evaluator finding). Routing
88
88
  // it here, before handlePanelFocusToken ever sees the token, gives F2 the
89
89
  // same toggle semantics as Ctrl+O below, matching how Ctrl+P/panel-picker
90
90
  // was already reachable regardless of panelFocused.
@@ -119,6 +119,29 @@ export function handleGlobalShortcutToken(
119
119
  state.handleCtrlC();
120
120
  return true;
121
121
 
122
+ case 'cancel-tool-call':
123
+ // Cancel JUST the currently-running tool call (the live transcript row).
124
+ // No-op (still consumed) when nothing is running — the composer keeps the
125
+ // keystroke rather than letting it fall through as text.
126
+ state.commandContext?.cancelToolCall?.();
127
+ return true;
128
+
129
+ case 'toggle-memory-provenance':
130
+ // Expand/collapse the provenance chip's drill-in. No-op when the chip is
131
+ // not showing; still consumed so the composer never sees a stray Alt+M.
132
+ state.commandContext?.toggleMemoryProvenance?.();
133
+ return true;
134
+
135
+ case 'toggle-keep-awake': {
136
+ // Flip the daemon-held keep-awake toggle; the always-visible "sleep
137
+ // disabled" chip is the confirmation. Fire-and-forget (the seam renders on
138
+ // completion); errors are swallowed so a keystroke never throws.
139
+ const power = state.commandContext;
140
+ const current = power?.getPowerState?.().keepAwake ?? false;
141
+ void power?.setKeepAwake?.(!current);
142
+ return true;
143
+ }
144
+
122
145
  case 'screen-clear':
123
146
  state.commandContext?.clearScreen?.();
124
147
  return true;
@@ -139,7 +162,7 @@ export function handleGlobalShortcutToken(
139
162
  // in-panel action (FleetPanel session-tab detach) before it closes the
140
163
  // panel outright — see Panel.interceptPanelClose's doc comment.
141
164
  if (active?.interceptPanelClose?.()) {
142
- // UX-C fix: a consumed Ctrl+X (the Fleet panel's session-tab detach)
165
+ // fix: a consumed Ctrl+X (the Fleet panel's session-tab detach)
143
166
  // used to leave panelFocused untouched, so focus stayed on the panel
144
167
  // — the evaluator's "Ctrl+X detach landed focus in the panel and a
145
168
  // typed question became nav keys". Detach is a leave-taking action:
@@ -203,7 +226,7 @@ export function handleGlobalShortcutToken(
203
226
  // Alt+1..9: jump directly to the Nth workspace tab. Routed globally (like
204
227
  // panel-tab-next/prev) so the jump works whether focus is on the prompt or
205
228
  // the workspace; gated on visibility, matching cyclePanelTab semantics.
206
- // UX-C: a chord jump is "I'm going panel-driving" (focus rule 1a) — the
229
+ // a chord jump is "I'm going panel-driving" (focus rule 1a) — the
207
230
  // jump now also grabs keyboard focus, matching F2/Ctrl+O/Ctrl+P, so j/k
208
231
  // land in the newly-active tab immediately instead of the composer.
209
232
  const pm = state.panelManager;
@@ -218,9 +241,9 @@ export function handleGlobalShortcutToken(
218
241
  }
219
242
 
220
243
  case 'panel-ops': {
221
- // Ctrl+O: TOGGLE the Fleet panel (UX-C — same semantics as F2 above; see
244
+ // Ctrl+O: TOGGLE the Fleet panel (— same semantics as F2 above; see
222
245
  // toggleFleetPanel's doc comment). The former Ops Control panel was
223
- // retired to an 'ops-control' -> 'fleet' alias (W6.1); rather than route
246
+ // retired to an 'ops-control' -> 'fleet' alias; rather than route
224
247
  // through the now-aliased openOpsPanel callback (which opens without
225
248
  // transferring focus), this operates on 'fleet' directly.
226
249
  toggleFleetPanel(state);
@@ -403,7 +426,7 @@ export function handleGlobalShortcutToken(
403
426
  }
404
427
 
405
428
  /**
406
- * toggleFleetPanel — the shared F2 / Ctrl+O TOGGLE (UX-C item 2): if the
429
+ * toggleFleetPanel — the shared F2 / Ctrl+O TOGGLE (item 2): if the
407
430
  * Fleet panel is open AND the panel workspace currently owns keyboard focus
408
431
  * with Fleet as the active tab, the chord CLOSES it and returns focus to the
409
432
  * composer; if Fleet is open but not the focused/active tab, the chord brings
@@ -194,7 +194,7 @@ export class InputHandler implements InputHandlerLike {
194
194
  public blockActionsMenu = new BlockActionsMenu();
195
195
  public settingsModal = new SettingsModal();
196
196
  public configModal = new ConfigModal();
197
- /** DEBT-5 item 5 — paste-flood guard state, mutated in place across tokens (never reallocated). */
197
+ /** item 5 — paste-flood guard state, mutated in place across tokens (never reallocated). */
198
198
  private panelBurstGuard: PanelBurstGuardState = { timestamps: [], suspended: false, hintShown: false };
199
199
  public mcpWorkspace = new McpWorkspace();
200
200
  public onboardingWizard = new OnboardingWizardController();
@@ -277,7 +277,7 @@ export class InputHandler implements InputHandlerLike {
277
277
  uiServices.providers.benchmarkStore,
278
278
  uiServices.providers.providerRegistry,
279
279
  );
280
- // W6.1 retirement: ProcessModal/LiveTailModal/AgentDetailModal were removed
280
+ // retirement: ProcessModal/LiveTailModal/AgentDetailModal were removed
281
281
  // — F2 now opens the Fleet panel, which subsumes the live process tree.
282
282
  this.bookmarkModal = new BookmarkModal(uiServices.shell.bookmarkManager);
283
283
  this.sessionPickerModal = new SessionPickerModal(uiServices.sessions.sessionManager, uiServices.sessions.sessionBroker);
@@ -68,7 +68,10 @@ export type KeyAction =
68
68
  | 'kill-to-start'
69
69
  | 'kill-word-forward'
70
70
  | 'yank'
71
- | 'yank-pop';
71
+ | 'yank-pop'
72
+ | 'cancel-tool-call'
73
+ | 'toggle-keep-awake'
74
+ | 'toggle-memory-provenance';
72
75
 
73
76
  /** Human-readable description for each action (used in /keybindings display). */
74
77
  export const ACTION_DESCRIPTIONS: Record<KeyAction, string> = {
@@ -111,6 +114,9 @@ export const ACTION_DESCRIPTIONS: Record<KeyAction, string> = {
111
114
  'kill-word-forward': 'Kill word forward into kill ring (Alt+D)',
112
115
  'yank': 'Yank (paste) from kill ring (Ctrl+Shift+Y)',
113
116
  'yank-pop': 'Rotate kill ring and yank next entry (Alt+Y)',
117
+ 'cancel-tool-call': 'Cancel the running tool call (the turn continues)',
118
+ 'toggle-keep-awake': 'Toggle keep-awake (the "sleep disabled" chip)',
119
+ 'toggle-memory-provenance': 'List/hide the memories a turn used (provenance chip drill-in)',
114
120
  };
115
121
 
116
122
  /** Default key bindings for all actions. */
@@ -147,8 +153,8 @@ export const DEFAULT_KEYBINDINGS: Record<KeyAction, KeyCombo[]> = {
147
153
  'panel-tab-8': [{ key: '8', alt: true }],
148
154
  'panel-tab-9': [{ key: '9', alt: true }],
149
155
  // Ctrl+O: TOGGLE the Fleet panel (open+focus / bring-to-front+focus / close
150
- // — see toggleFleetPanel in handler-shortcuts.ts, UX-C). The former Ops
151
- // Control panel was retired to an 'ops-control' -> 'fleet' alias (W6.1); the
156
+ // — see toggleFleetPanel in handler-shortcuts.ts). The former Ops
157
+ // Control panel was retired to an 'ops-control' -> 'fleet' alias; the
152
158
  // binding is KEPT (repointed, not removed) so the Ctrl+O muscle memory still
153
159
  // lands somewhere useful. Routed globally in handleGlobalShortcutToken.
154
160
  'panel-ops': [{ key: 'o', ctrl: true }],
@@ -189,6 +195,18 @@ export const DEFAULT_KEYBINDINGS: Record<KeyAction, KeyCombo[]> = {
189
195
  'yank': [{ key: 'y', ctrl: true, shift: true }],
190
196
  // Alt+Y: yank-pop (rotate ring after yank)
191
197
  'yank-pop': [{ key: 'y', alt: true }],
198
+ // Alt+C: cancel the currently-running tool call (the turn continues). Alt+C
199
+ // is unused by any other default and representable by the tokenizer's
200
+ // { key, alt } combo form. Ctrl+C stays whole-input clear/cancel; this is the
201
+ // narrower per-tool stop.
202
+ 'cancel-tool-call': [{ key: 'c', alt: true }],
203
+ // Alt+A: toggle keep-awake (the one-key affordance for the "sleep disabled"
204
+ // chip). Alt+A is unused by any other default. The always-visible chip is the
205
+ // confirmation of the toggle's state.
206
+ 'toggle-keep-awake': [{ key: 'a', alt: true }],
207
+ // Alt+M: expand/collapse the "used N memories" provenance chip's drill-in.
208
+ // Alt+M is unused by any other default. A no-op when the chip is not showing.
209
+ 'toggle-memory-provenance': [{ key: 'm', alt: true }],
192
210
  };
193
211
 
194
212
  /** Resolved overrides type: each key can be a single combo or array. */
@@ -15,7 +15,7 @@
15
15
  */
16
16
 
17
17
  import type { ConfigKey } from '../../config/index.ts';
18
- import { FEATURE_FLAG_MAP } from '@pellux/goodvibes-sdk/platform/runtime/state';
18
+ import { getFeatureSetting, isFeatureDefaultEnabled } from '../../runtime/feature-settings.ts';
19
19
  import type {
20
20
  OnboardingWizardControllerLike,
21
21
  OnboardingWizardFieldDefinition,
@@ -94,12 +94,13 @@ export const FEATURE_ONBOARDING_SECTIONS: readonly FeatureSection[] = [
94
94
  hint: 'When the exec sandbox leaves a command on ask, a model pass annotates the ask with stated reasons. Never converts allow to deny; never touches the frozen catastrophic block.',
95
95
  subOptions: [
96
96
  {
97
- key: 'auto-approve',
98
- configKey: 'sandbox.judgmentAutoApprove' as ConfigKey,
99
- label: 'Auto-approve looks-safe verdicts',
100
- hint: 'Yes lets a looks-safe judgment auto-approve the ask; No keeps annotate-only (default).',
101
- valueType: 'boolean',
102
- defaultValue: 'no',
97
+ key: 'mode',
98
+ configKey: 'sandbox.judgment' as ConfigKey,
99
+ label: 'Judgment mode',
100
+ hint: 'annotate (default) proposes a verdict with stated reasons and the human still decides; auto-approve additionally auto-approves looks-safe verdicts; off keeps plain asks.',
101
+ valueType: 'enum',
102
+ options: ['off', 'annotate', 'auto-approve'],
103
+ defaultValue: 'annotate',
103
104
  },
104
105
  ],
105
106
  },
@@ -283,23 +284,18 @@ export const FEATURE_ONBOARDING_SECTIONS: readonly FeatureSection[] = [
283
284
  {
284
285
  flagId: 'integration-delivery-slo',
285
286
  label: 'Integration delivery SLO',
286
- hint: 'Retry with backoff and a dead-letter queue for Slack/Discord/webhook delivery. Exposes /notify dlq and /notify replay.',
287
- subOptions: [
288
- {
289
- key: 'slo',
290
- configKey: 'integrations.delivery.sloEnforced' as ConfigKey,
291
- label: 'Enforce delivery SLO',
292
- hint: 'Yes logs dead-letter events at error level and surfaces them in diagnostics; No is warn-level only (default).',
293
- valueType: 'boolean',
294
- defaultValue: 'no',
295
- },
296
- ],
287
+ hint: 'Retry with backoff and a dead-letter queue for Slack/Discord/webhook delivery, with dead-letter events surfaced in diagnostics. On by default; exposes /notify dlq and /notify replay. Retry bounds are tuned in /settings.',
297
288
  },
298
289
  {
299
290
  flagId: 'adaptive-execution-planner',
300
291
  label: 'Adaptive execution planner',
301
292
  hint: 'Score single/cohort/background/remote strategies each turn and pick the best. Exposes /plan commands.',
302
293
  },
294
+ {
295
+ flagId: 'daemon-auto-update',
296
+ label: 'Daemon auto-update',
297
+ hint: 'The daemon checks for a new release hourly, checksum-verifies it, swaps binaries only at a no-active-work moment, and keeps the previous binary for one-command rollback. On by default; update.auto turns it off and update.intervalMinutes tunes the cadence in /settings.',
298
+ },
303
299
  {
304
300
  flagId: 'relay-connect',
305
301
  label: 'Outbound zero-knowledge relay',
@@ -355,10 +351,10 @@ export const FEATURE_ONBOARDING_SECTIONS: readonly FeatureSection[] = [
355
351
  key: 'backend',
356
352
  configKey: 'tools.overflowSpillBackend' as ConfigKey,
357
353
  label: 'Spill backend',
358
- hint: 'file (default), ledger, or diagnostics.',
354
+ hint: 'ledger (default when this feature is on) or diagnostics; file is the baseline used while the feature is off.',
359
355
  valueType: 'enum',
360
356
  options: ['file', 'ledger', 'diagnostics'],
361
- defaultValue: 'file',
357
+ defaultValue: 'ledger',
362
358
  },
363
359
  ],
364
360
  },
@@ -398,9 +394,9 @@ export function featureSubOptionFieldId(flagId: string, key: string): string {
398
394
  return `feature.${flagId}.${key}`;
399
395
  }
400
396
 
401
- /** Whether a flag defaults to enabled (drives the toggle's default-checked state). */
397
+ /** Whether a feature defaults to enabled (drives the toggle's default-checked state). */
402
398
  export function isFeatureDefaultOn(flagId: string): boolean {
403
- return FEATURE_FLAG_MAP.get(flagId)?.defaultState === 'enabled';
399
+ return isFeatureDefaultEnabled(flagId);
404
400
  }
405
401
 
406
402
  /** Every flag id reachable through the guided feature steps. */
@@ -498,10 +494,12 @@ export function buildFeatureUnitSteps(
498
494
 
499
495
  /**
500
496
  * Fold the guided feature selections into apply operations: for each unit, when
501
- * the user's choice differs from the flag's default, record the flag override;
502
- * when the feature is enabled, write its implied config, prerequisite flags, and
503
- * chosen sub-options. Flag overrides are collected into `overrides` (flushed by
504
- * the caller with the surface flags); config writes go straight to setConfig.
497
+ * the user's choice differs from the feature's default, record the desired
498
+ * state; when the feature is enabled, write its implied config, prerequisite
499
+ * features, and chosen sub-options. Desired states are collected into
500
+ * `overrides` (flushed by the caller as plain domain-settings writes on each
501
+ * feature's enablement key, together with the surface capabilities); other
502
+ * config writes go straight to setConfig.
505
503
  */
506
504
  export function applyFeatureUnitOperations(
507
505
  controller: OnboardingWizardControllerLike,
@@ -521,15 +519,24 @@ export function applyFeatureUnitOperations(
521
519
  for (const prerequisite of unit.requiresFlags ?? []) overrides.set(prerequisite, 'enabled');
522
520
  for (const implied of unit.impliedConfig ?? []) setConfig(implied.key, implied.value);
523
521
 
522
+ const enablementKey = getFeatureSetting(unit.flagId)?.enablement.key;
524
523
  for (const sub of unit.subOptions ?? []) {
525
524
  const raw = controller.getStringFieldValue(featureSubOptionFieldId(unit.flagId, sub.key), sub.defaultValue);
525
+ let wrote = true;
526
526
  if (sub.valueType === 'boolean') {
527
527
  setConfig(sub.configKey, raw === 'yes');
528
528
  } else if (sub.valueType === 'text') {
529
- if (raw.length > 0) setConfig(sub.configKey, raw);
529
+ wrote = raw.length > 0;
530
+ if (wrote) setConfig(sub.configKey, raw);
530
531
  } else {
531
532
  setConfig(sub.configKey, raw);
532
533
  }
534
+ // A sub-option that writes the unit's OWN enablement key (e.g. the
535
+ // provider-optimizer routing mode) is the authoritative enablement
536
+ // value — drop the pending override so the flush cannot clobber it.
537
+ if (wrote && enablementKey !== undefined && sub.configKey === enablementKey) {
538
+ overrides.delete(unit.flagId);
539
+ }
533
540
  }
534
541
  }
535
542
  }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Provider-agnostic API-key fields for the onboarding provider step.
3
+ *
4
+ * Kept out of onboarding-wizard-steps.ts so the step file stays focused on step
5
+ * assembly. Both pieces read from the same registration-truth targets so the
6
+ * inventory line and the per-provider masked fields never disagree.
7
+ */
8
+
9
+ import type { ProviderKeyCaptureTarget, SubscriptionStatusTarget } from '../../runtime/onboarding/provider-key-capture.ts';
10
+ import { deriveSubscriptionStatusTargets, providerKeyFieldId, subscriptionStatusFieldId } from '../../runtime/onboarding/provider-key-capture.ts';
11
+ import type { OnboardingProviderAccountRecord } from '../../runtime/onboarding/types.ts';
12
+ import type {
13
+ OnboardingWizardMaskedFieldDefinition,
14
+ OnboardingWizardStatusFieldDefinition,
15
+ } from './onboarding-wizard-types.ts';
16
+
17
+ /**
18
+ * Providers with a wired in-wizard subscription sign-in flow. This is DATA — the
19
+ * set of providers whose OAuth login the wizard can start/finish itself — not an
20
+ * assumption that any one vendor is the only subscription option.
21
+ */
22
+ const SUBSCRIPTION_SIGN_IN_PROVIDER_IDS = new Set<string>(['openai']);
23
+
24
+ /**
25
+ * One subscription-status row per subscription-capable provider. The row's copy
26
+ * names the provider from data — never a vendor hardcoded in the field set — so
27
+ * the status surface tracks whatever subscription-capable providers the runtime
28
+ * reports, not a single assumed one.
29
+ */
30
+ export function buildSubscriptionStatusFields(
31
+ targets: readonly SubscriptionStatusTarget[],
32
+ ): OnboardingWizardStatusFieldDefinition[] {
33
+ return targets.map((target) => ({
34
+ kind: 'status',
35
+ id: subscriptionStatusFieldId(target.providerId),
36
+ label: `${target.providerId} subscription status`,
37
+ hint: target.active
38
+ ? `A ${target.providerId} subscription session is already available.`
39
+ : target.pending
40
+ ? `A ${target.providerId} subscription login is pending.`
41
+ : `No ${target.providerId} subscription session was found in the current runtime state.`,
42
+ defaultValue: target.active ? 'Active' : target.pending ? 'Pending' : 'Not detected',
43
+ }));
44
+ }
45
+
46
+ /** The provider-agnostic subscription posture the provider step renders. */
47
+ export interface SubscriptionPosture {
48
+ readonly statusFields: OnboardingWizardStatusFieldDefinition[];
49
+ /** The provider whose in-wizard sign-in flow applies now, or null. */
50
+ readonly signInProviderId: string | null;
51
+ readonly signInPending: boolean;
52
+ readonly summaryLine: string;
53
+ }
54
+
55
+ /**
56
+ * Derive the subscription posture for the provider step: one status row per
57
+ * subscription-capable provider, plus which (if any) provider's in-wizard OAuth
58
+ * sign-in flow applies right now (a subscription-capable, sign-in-wired provider
59
+ * that is not already active).
60
+ */
61
+ export function buildSubscriptionPosture(
62
+ activeProviderIds: readonly string[],
63
+ pendingProviderIds: readonly string[],
64
+ providers: readonly OnboardingProviderAccountRecord[] | undefined,
65
+ ): SubscriptionPosture {
66
+ const targets = deriveSubscriptionStatusTargets(providers, activeProviderIds, pendingProviderIds);
67
+ const signIn = targets.find((target) => SUBSCRIPTION_SIGN_IN_PROVIDER_IDS.has(target.providerId) && !target.active);
68
+ const activeCount = targets.filter((target) => target.active).length;
69
+ const pendingCount = targets.filter((target) => target.pending).length;
70
+ return {
71
+ statusFields: buildSubscriptionStatusFields(targets),
72
+ signInProviderId: signIn?.providerId ?? null,
73
+ signInPending: signIn?.pending ?? false,
74
+ summaryLine: targets.length === 0
75
+ ? 'Subscriptions: none detected'
76
+ : `Subscriptions: ${activeCount} active, ${pendingCount} pending across ${targets.length} provider(s)`,
77
+ };
78
+ }
79
+
80
+ /** The status line summarizing how many key-accepting providers are configured. */
81
+ export function buildProviderKeyInventoryField(
82
+ targets: readonly ProviderKeyCaptureTarget[],
83
+ ): OnboardingWizardStatusFieldDefinition {
84
+ const configuredCount = targets.filter((target) => target.configured).length;
85
+ return {
86
+ kind: 'status',
87
+ id: 'providers.api-key-inventory',
88
+ label: 'Provider API keys',
89
+ hint: targets.length === 0
90
+ ? 'No key-accepting providers are registered yet in the current runtime state.'
91
+ : `${targets.length} provider(s) accept an API key; ${configuredCount} already configured. Values stay masked.`,
92
+ defaultValue: targets.length === 0
93
+ ? 'None detected'
94
+ : `${configuredCount}/${targets.length} configured`,
95
+ };
96
+ }
97
+
98
+ /** One masked field per key-accepting provider, keyed off registration truth. */
99
+ export function buildProviderKeyMaskedFields(
100
+ targets: readonly ProviderKeyCaptureTarget[],
101
+ ): OnboardingWizardMaskedFieldDefinition[] {
102
+ return targets.map((target) => ({
103
+ kind: 'masked',
104
+ id: providerKeyFieldId(target.providerId),
105
+ label: `${target.providerId} API key`,
106
+ hint: target.configured
107
+ ? `A key for ${target.providerId} is already stored. Leave blank to keep it; enter a new key to replace it through the secret manager.`
108
+ : `Optional: enter an API key for ${target.providerId} now. The value is stored through the secret manager, not in config.`,
109
+ placeholder: target.configured ? 'already configured' : 'paste key',
110
+ defaultValue: '',
111
+ }));
112
+ }