@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
@@ -25,6 +25,7 @@ import {
25
25
  } from './worktree-setup-config.ts';
26
26
  import { BUDGET_ALERT_USD_CONFIG_KEY, BUDGET_ALERT_USD_DEFAULT, readBudgetAlertUsd } from '../export/cost-utils.ts';
27
27
  import { MEMORY_PROJECTION_DIR_CONFIG_KEY, buildMemoryProjectionDirSyntheticEntry } from './commands/recall-files-config.ts';
28
+ import { MEMORY_SHOW_PROVENANCE_CONFIG_KEY, buildMemoryProvenanceSyntheticEntry } from '../core/memory-provenance.ts';
28
29
  import {
29
30
  injectSandboxExecSyntheticEntries,
30
31
  isSandboxExecListConfigKey,
@@ -52,16 +53,15 @@ import {
52
53
  } from './settings-modal-types.ts';
53
54
  import { enrichRelaySettingDescriptions } from './relay-settings-descriptions.ts';
54
55
  import { applyFeatureUnitLayout } from './feature-unit-layout.ts';
56
+ import { FEATURE_SETTINGS_BY_ID } from '../runtime/feature-settings.ts';
55
57
 
56
58
  // ---------------------------------------------------------------------------
57
59
  // deepEqual — structural equality for isDefault comparisons
58
60
  // ---------------------------------------------------------------------------
59
61
 
60
62
  /**
61
- * Structural equality check for setting default comparisons.
62
- * Handles scalars, arrays, and plain objects. Does NOT support
63
- * circular references or non-plain prototypes — config defaults
64
- * are always JSON-safe primitives, arrays, or plain objects.
63
+ * Structural equality for setting-default comparisons: scalars, arrays, and
64
+ * plain objects (config defaults are always JSON-safe; no circular refs).
65
65
  */
66
66
  export function deepEqual(a: unknown, b: unknown): boolean {
67
67
  if (a === b) return true;
@@ -98,9 +98,8 @@ export function buildSettingGroups(
98
98
  featureFlagManager?: FeatureFlagManager | null,
99
99
  ): Map<SettingsCategory, SettingEntry[]> {
100
100
  const groups = new Map<SettingsCategory, SettingEntry[]>();
101
- // Every category starts empty (including 'flags', now the Advanced Features
102
- // bucket, and the new fetch/agents/security/integrations/policy/notifications
103
- // namespaces) and is filled from CONFIG_SCHEMA below.
101
+ // Every category starts empty and is filled from CONFIG_SCHEMA below; the
102
+ // feature-unit layout pass then folds each capability's rows into one unit.
104
103
  for (const cat of SETTINGS_CATEGORIES) groups.set(cat, []);
105
104
 
106
105
  for (const setting of CONFIG_SCHEMA) {
@@ -122,11 +121,11 @@ export function buildSettingGroups(
122
121
  if ((rawCat === 'controlPlane' || rawCat === 'httpListener' || rawCat === 'web') && groups.has('network')) {
123
122
  groups.get('network')!.push(entry);
124
123
  }
124
+ // fleet.maxSize (was orchestration.maxActiveAgents) surfaces under orchestration.
125
+ if (rawCat === 'fleet' && groups.has('orchestration')) groups.get('orchestration')!.push(entry);
125
126
  }
126
127
 
127
- // DEBT-2: inject the synthetic display.themeMode enum (auto|dark|light). TUI-local
128
- // key stored under the existing `display` section (see theme-mode-config.ts for why
129
- // not `appearance`), same rationale as the other synthetic settings below.
128
+ // Synthetic display.themeMode enum (auto|dark|light) — TUI-local, see theme-mode-config.ts.
130
129
  const displayEntries = groups.get('display');
131
130
  if (displayEntries && !displayEntries.some((e) => e.setting.key === (THEME_MODE_CONFIG_KEY as ConfigKey))) {
132
131
  displayEntries.push(buildThemeModeSyntheticEntry(configManager));
@@ -153,26 +152,19 @@ export function buildSettingGroups(
153
152
  }
154
153
  }
155
154
 
156
- // Inject the synthetic tts.speed entry into the tts category.
157
- // tts.speed is not yet a ConfigKey in the SDK schema (pending SDK addition).
158
- // The entry is surfaced here with an honest description caveat so users can
159
- // see and understand the setting before the SDK schema catches up.
155
+ // Synthetic tts.speed (not yet an SDK ConfigKey) — surfaced with an honest caveat.
160
156
  if (ttsEntries && !ttsEntries.some((e) => e.setting.key === ('tts.speed' as ConfigKey))) {
161
157
  ttsEntries.push(buildTtsSpeedSyntheticEntry(configManager));
162
158
  }
163
159
 
164
- // Inject the synthetic behavior.notifyAfterSeconds entry into the behavior
165
- // category. This key is TUI-local (not in the SDK ConfigKey union) and
166
- // controls the long-task push notification threshold.
160
+ // Synthetic behavior.notifyAfterSeconds (TUI-local): long-task push threshold.
167
161
  const behaviorEntries = groups.get('behavior');
168
162
  if (behaviorEntries && !behaviorEntries.some((e) => e.setting.key === ('behavior.notifyAfterSeconds' as ConfigKey))) {
169
163
  behaviorEntries.push(buildNotifyAfterSecondsSyntheticEntry(configManager));
170
164
  }
171
165
 
172
- // Inject the synthetic behavior.budgetAlertUsd entry into the behavior
173
- // category. This key is TUI-local (not in the SDK ConfigKey union), which
174
- // previously left it with no schema-driven inspection surface at all: it
175
- // never appeared in /config, and /settings-sync show rejects any key not
166
+ // Synthetic behavior.budgetAlertUsd (TUI-local): no schema surface previously,
167
+ // never in /config; /settings-sync show rejects any key not
176
168
  // in CONFIG_KEYS. The Cost panel's 'b' key and /cost budget <usd> remain
177
169
  // the primary way to change it; this entry makes the current effective
178
170
  // value (and whether it's still the "no budget configured" default)
@@ -181,7 +173,7 @@ export function buildSettingGroups(
181
173
  behaviorEntries.push(buildBudgetAlertUsdSyntheticEntry(configManager));
182
174
  }
183
175
 
184
- // Inject the W2.3 alert-class toggles + master focus gate. TUI-local
176
+ // Inject the alert-class toggles + master focus gate. TUI-local
185
177
  // synthetic settings, same rationale as notifyAfterSeconds above.
186
178
  if (behaviorEntries) {
187
179
  for (const entry of buildNotifyAlertSyntheticEntries(configManager)) {
@@ -214,6 +206,12 @@ export function buildSettingGroups(
214
206
  storageEntries.push(buildMemoryProjectionDirSyntheticEntry(configManager));
215
207
  }
216
208
 
209
+ // Synthetic memory.showProvenance (TUI-local, default OFF — see memory-provenance.ts).
210
+ const memoryEntries = groups.get('memory');
211
+ if (memoryEntries && !memoryEntries.some((e) => e.setting.key === (MEMORY_SHOW_PROVENANCE_CONFIG_KEY as ConfigKey))) {
212
+ memoryEntries.push(buildMemoryProvenanceSyntheticEntry(configManager) as unknown as SettingEntry);
213
+ }
214
+
217
215
  // Inject the worktree.setup.commands / worktree.setup.carryOverGlobs
218
216
  // synthetic entries into the orchestration category (worktree isolation is
219
217
  // an orchestration-engine concern — see WorkstreamIsolation 'worktree').
@@ -240,11 +238,10 @@ export function buildSettingGroups(
240
238
  // the SDK's own descriptions don't carry (see relay-settings-descriptions.ts).
241
239
  enrichRelaySettingDescriptions(groups);
242
240
 
243
- // Feature-unit presentation: turn each flag into ONE unit (toggle + the config
244
- // keys it tunes) hosted in its topical category, folding no-config flags into
245
- // the Advanced Features bucket. Sourced from FEATURE_FLAG_CONFIG. Runs AFTER
246
- // synthetic injection so non-flag-owned rows stay as orphans; skipped on the
247
- // flagless test path.
241
+ // Feature-unit presentation: each capability becomes ONE unit (its real
242
+ // enablement row + the settings keys it tunes) in its settings domain,
243
+ // sourced from FEATURE_SETTINGS. Runs AFTER synthetic injection so
244
+ // non-feature-owned rows stay orphans; skipped on the featureless path.
248
245
  if (featureFlagManager) applyFeatureUnitLayout(groups, buildFlagEntries(featureFlagManager));
249
246
 
250
247
  // learning.consolidation.* is a real SDK config domain now, so its keys arrive
@@ -301,7 +298,7 @@ export function buildTtsSpeedSyntheticEntry(configManager: Pick<ConfigManager, '
301
298
  }
302
299
 
303
300
  // ---------------------------------------------------------------------------
304
- // display.themeMode synthetic setting (DEBT-2 light theme)
301
+ // display.themeMode synthetic setting (light theme)
305
302
  // ---------------------------------------------------------------------------
306
303
 
307
304
  /**
@@ -415,11 +412,11 @@ export function buildBudgetAlertUsdSyntheticEntry(configManager: Pick<ConfigMana
415
412
  }
416
413
 
417
414
  // ---------------------------------------------------------------------------
418
- // W2.3 alert-class synthetic settings — behavior.notifyOn* + notifyOnlyWhenUnfocused
415
+ // alert-class synthetic settings — behavior.notifyOn* + notifyOnlyWhenUnfocused
419
416
  // ---------------------------------------------------------------------------
420
417
 
421
418
  /**
422
- * The five W2.3 alert-gating booleans, all TUI-local (not yet in the SDK
419
+ * The five alert-gating booleans, all TUI-local (not yet in the SDK
423
420
  * ConfigKey union), all defaulting to on. Read/written generically by
424
421
  * core/alert-gating.ts (readBooleanConfig) and the per-alert-class modules
425
422
  * (budget-breach-notifier.ts, approval-alert.ts, turn-event-wiring.ts,
@@ -475,7 +472,7 @@ export function buildNotifyAlertSyntheticEntries(configManager: Pick<ConfigManag
475
472
  /**
476
473
  * The OSC 9 in-terminal desktop-notification toggles + the audible bell.
477
474
  * TUI-local synthetic booleans (not in the SDK ConfigKey union), each carrying
478
- * its OWN default (unlike the always-on W2.3 alert toggles above): approval-wait
475
+ * its OWN default (unlike the always-on alert toggles above): approval-wait
479
476
  * on, turn-end/agent-blocked off, bell off. Read generically by
480
477
  * core/terminal-notifier.ts (readBooleanConfig); this is only the
481
478
  * settings-modal-visible surface. These are independent of the master
@@ -537,9 +534,13 @@ export function buildCodeIndexEnabledSyntheticEntry(configManager: Pick<ConfigMa
537
534
 
538
535
  export function buildFlagEntries(featureFlagManager: FeatureFlagManager | null): FlagEntry[] {
539
536
  if (!featureFlagManager) return [];
540
- return Array.from(featureFlagManager.getAll().values()).map(
541
- ({ flag, state, persistedState, pendingRestart }) => ({ flag, state, persistedState, pendingRestart }),
542
- );
537
+ const entries: FlagEntry[] = [];
538
+ for (const { flag, state, persistedState, pendingRestart } of featureFlagManager.getAll().values()) {
539
+ const feature = FEATURE_SETTINGS_BY_ID.get(flag.id);
540
+ if (!feature) continue; // gate with no settings surface (registry/binding drift is SDK test-guarded)
541
+ entries.push({ feature, flag, state, persistedState, pendingRestart });
542
+ }
543
+ return entries;
543
544
  }
544
545
 
545
546
  // ---------------------------------------------------------------------------
@@ -606,44 +607,34 @@ export function refreshEntryValues(
606
607
  ): void {
607
608
  for (const entries of groups.values()) {
608
609
  for (const entry of entries) {
609
- // The worktree.setup.* keys read through a defensive helper. The SDK now
610
- // registers the 'worktree' section in DEFAULT_CONFIG, so a plain
611
- // configManager.get returns the empty default rather than throwing; the
612
- // helper's guard (belt-and-suspenders for an older SDK) keeps this
613
- // refresh from crashing for EVERY setting change if the section is ever
614
- // absent (see worktree-setup-config.ts).
610
+ // worktree.setup.* read through a defensive array-normalizing helper (see worktree-setup-config.ts).
615
611
  if (isWorktreeSetupListConfigKey(entry.setting.key)) {
616
612
  entry.currentValue = readWorktreeSetupList(configManager, entry.setting.key);
617
613
  entry.isDefault = (entry.currentValue as string[]).length === 0;
618
614
  continue;
619
615
  }
620
- // sandbox.egressAllowlist / sandbox.workspaceWritable: 'sandbox' IS a real
621
- // DEFAULT_CONFIG section (has been since before this repack, backing the
622
- // VM/REPL isolation settings), so a plain get() would not throw — but an
623
- // unset leaf still reads back as `undefined`, not `[]`, so this still needs
624
- // the same array-normalizing read as the worktree keys above (see
625
- // sandbox-exec-config.ts).
616
+ // sandbox.* list leaves normalize an unset undefined to [] (see sandbox-exec-config.ts).
626
617
  if (isSandboxExecListConfigKey(entry.setting.key)) {
627
618
  entry.currentValue = readSandboxExecList(configManager, entry.setting.key);
628
619
  entry.isDefault = (entry.currentValue as string[]).length === 0;
629
620
  continue;
630
621
  }
631
- // permissions.execEnvScrubAllowlist: same normalizing-read trap as above —
632
- // 'permissions' is a real section but this leaf is TUI-local (see
633
- // exec-env-scrub-config.ts).
622
+ // permissions.execEnvScrubAllowlist: same array-normalizing read (see exec-env-scrub-config.ts).
634
623
  if (isExecEnvScrubAllowlistConfigKey(entry.setting.key)) {
635
624
  entry.currentValue = readExecEnvScrubAllowlist(configManager);
636
625
  entry.isDefault = (entry.currentValue as string[]).length === 0;
637
626
  continue;
638
627
  }
639
- // 'learning' is now a real DEFAULT_CONFIG section (schema-domain-learning.ts),
640
- // so learning.consolidation.* keys read through the plain path below like any
641
- // other schema-driven setting — no special case needed anymore.
642
- // Same trap still applies here: 'memory' has no DEFAULT_CONFIG entry.
628
+ // 'memory' has no DEFAULT_CONFIG entry — its synthetic leaves read defensively below.
643
629
  if (entry.setting.key === MEMORY_PROJECTION_DIR_CONFIG_KEY) {
644
630
  const refreshed = buildMemoryProjectionDirSyntheticEntry(configManager);
645
- entry.currentValue = refreshed.currentValue;
646
- entry.isDefault = refreshed.isDefault;
631
+ entry.currentValue = refreshed.currentValue; entry.isDefault = refreshed.isDefault;
632
+ continue;
633
+ }
634
+ // memory.showProvenance: same 'memory'-has-no-DEFAULT_CONFIG trap — read defensively.
635
+ if (entry.setting.key === (MEMORY_SHOW_PROVENANCE_CONFIG_KEY as ConfigKey)) {
636
+ const refreshed = buildMemoryProvenanceSyntheticEntry(configManager);
637
+ entry.currentValue = refreshed.currentValue; entry.isDefault = refreshed.isDefault;
647
638
  continue;
648
639
  }
649
640
  const raw = configManager.get(entry.setting.key as ConfigKey);
@@ -676,8 +667,12 @@ export function updateEntryForKey(
676
667
  if (entry) {
677
668
  if (key === MEMORY_PROJECTION_DIR_CONFIG_KEY) {
678
669
  const refreshed = buildMemoryProjectionDirSyntheticEntry(configManager);
679
- entry.currentValue = refreshed.currentValue;
680
- entry.isDefault = refreshed.isDefault;
670
+ entry.currentValue = refreshed.currentValue; entry.isDefault = refreshed.isDefault;
671
+ continue;
672
+ }
673
+ if (key === (MEMORY_SHOW_PROVENANCE_CONFIG_KEY as ConfigKey)) {
674
+ const refreshed = buildMemoryProvenanceSyntheticEntry(configManager);
675
+ entry.currentValue = refreshed.currentValue; entry.isDefault = refreshed.isDefault;
681
676
  continue;
682
677
  }
683
678
  if (isSandboxExecListConfigKey(key)) {
@@ -1,17 +1,18 @@
1
1
  /**
2
2
  * settings-modal-mutations — pure mutation helpers for SettingsModal.
3
3
  *
4
- * These functions encapsulate the side-effectful write operations:
5
- * applying config values, persisting feature flag state, and applying flag
6
- * runtime toggles. Each function takes its dependencies as explicit arguments
7
- * rather than accessing class-level state.
4
+ * These functions encapsulate the side-effectful write operations. Every
5
+ * write — feature-unit headers included — is a plain domain-settings config
6
+ * write; the SDK's settings bridge keeps the runtime gate manager in sync
7
+ * (flipping runtime-toggleable gates live and recording honest
8
+ * pending-restart markers for startup-gated ones). Each function takes its
9
+ * dependencies as explicit arguments rather than accessing class-level state.
8
10
  */
9
11
 
10
- import type { ConfigKey, PersistedFlagState } from '@pellux/goodvibes-sdk/platform/config';
12
+ import type { ConfigKey } from '@pellux/goodvibes-sdk/platform/config';
11
13
  import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
12
14
  import { logger, summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
13
15
  import type { FeatureFlagManager } from '@/runtime/index.ts';
14
- import type { FeatureFlag, FlagState } from '@/runtime/index.ts';
15
16
  import type { FlagEntry, SettingEntry } from './settings-modal-types.ts';
16
17
  import { deepEqual } from './settings-modal-data.ts';
17
18
 
@@ -83,10 +84,12 @@ export function applySettingValue({
83
84
  };
84
85
  }
85
86
 
86
- // Update the entry in the groups map
87
+ // Update every entry carrying this key: cross-listed rows (the network
88
+ // combined view) and feature-unit headers that share one enablement key
89
+ // (e.g. both compaction features read behavior.compactionStrategy).
87
90
  for (const entries of groups.values()) {
88
- const entry = entries.find((candidate) => candidate.setting.key === key);
89
- if (entry) {
91
+ for (const entry of entries) {
92
+ if (entry.setting.key !== key) continue;
90
93
  entry.currentValue = configManager.get(key);
91
94
  entry.isDefault = deepEqual(entry.currentValue, entry.setting.default);
92
95
  }
@@ -112,65 +115,6 @@ export function applySettingValue({
112
115
  return { restartDomain, effectMessage, changed: previousValue !== value };
113
116
  }
114
117
 
115
- // ---------------------------------------------------------------------------
116
- // persistFlagState — write a flag override to config
117
- // ---------------------------------------------------------------------------
118
-
119
- export function persistFlagState(
120
- configManager: ConfigManager,
121
- flagId: string,
122
- newState: FlagState,
123
- defaultState: FlagState,
124
- ): void {
125
- if (newState === 'killed') return; // never persist killed state
126
-
127
- try {
128
- if (newState === defaultState) {
129
- // Back at the default: the override must be REMOVED, not merged.
130
- // getCategory clones and mergeCategory only sets keys, so the old
131
- // delete-then-merge approach left the stale override on disk and the
132
- // flag silently reloaded in the overridden state on the next start.
133
- configManager.removeCategoryKey('featureFlags', flagId);
134
- } else {
135
- configManager.mergeCategory('featureFlags', { [flagId]: newState } as Record<string, PersistedFlagState>);
136
- }
137
- } catch (e) {
138
- logger.error('SettingsModal: failed to persist flag state', { flagId, error: summarizeError(e) });
139
- }
140
- }
141
-
142
- // ---------------------------------------------------------------------------
143
- // applyFlagState — toggle a feature flag (runtime + persist)
144
- // ---------------------------------------------------------------------------
145
-
146
- export function applyFlagState(
147
- flagEntry: FlagEntry,
148
- newState: FlagState,
149
- featureFlagManager: FeatureFlagManager,
150
- configManager: ConfigManager,
151
- ): void {
152
- const flag: FeatureFlag = flagEntry.flag;
153
-
154
- try {
155
- // Persist first so the on-disk override is the source of truth, then apply
156
- // the same value to the live manager through applyConfigState. That single
157
- // call keeps both paths consistent with the SDK's config→flag bridge:
158
- // - runtime-toggleable flags apply immediately (firing flag subscribers
159
- // exactly as a direct enable()/disable() would), and
160
- // - startup-gated flags are NOT faked live — the effective state is left
161
- // untouched and a pending-restart marker is recorded instead.
162
- persistFlagState(configManager, flag.id, newState, flag.defaultState as FlagState);
163
- featureFlagManager.applyConfigState(flag.id, newState);
164
- } catch (e) {
165
- logger.error('SettingsModal: failed to toggle feature flag', { flag: flag.id, error: summarizeError(e) });
166
- }
167
-
168
- // Reflect the manager's real state, never the requested target: for a
169
- // startup-gated flag `state` stays at the unchanged effective value and
170
- // `pendingRestart` reports that a restart is needed for `persistedState`.
171
- syncFlagEntryFromManager(flagEntry, featureFlagManager);
172
- }
173
-
174
118
  /**
175
119
  * Refresh a FlagEntry's live/persisted/pending fields from the manager's
176
120
  * authoritative snapshot so the settings UI never displays a guessed state.
@@ -1,11 +1,12 @@
1
1
  import type { ConfigSetting } from '@pellux/goodvibes-sdk/platform/config';
2
2
  import type { ProviderAuthFreshness, ProviderAuthRoute } from '@/runtime/index.ts';
3
- import type { FeatureFlag, FlagState } from '@/runtime/index.ts';
3
+ import type { FeatureFlag, FeatureSetting, FlagState } from '@/runtime/index.ts';
4
4
 
5
5
  export type SettingsCategory =
6
6
  | 'display'
7
7
  | 'ui'
8
8
  | 'provider'
9
+ | 'pricing'
9
10
  | 'subscriptions'
10
11
  | 'behavior'
11
12
  | 'storage'
@@ -17,6 +18,7 @@ export type SettingsCategory =
17
18
  | 'tools'
18
19
  | 'helper'
19
20
  | 'tts'
21
+ | 'voice'
20
22
  | 'service'
21
23
  | 'daemon'
22
24
  | 'controlPlane'
@@ -43,8 +45,10 @@ export type SettingsCategory =
43
45
  | 'integrations'
44
46
  | 'policy'
45
47
  | 'notifications'
46
- | 'flags'
47
48
  | 'release'
49
+ | 'update'
50
+ | 'power'
51
+ | 'memory'
48
52
  | 'danger';
49
53
 
50
54
  export type SettingsFocusPane = 'categories' | 'settings';
@@ -54,12 +58,12 @@ export const SETTINGS_CATEGORY_GROUPS: ReadonlyArray<{
54
58
  readonly categories: readonly SettingsCategory[];
55
59
  }> = [
56
60
  { label: 'Interface', categories: ['display', 'ui', 'behavior', 'notifications', 'permissions', 'policy', 'security'] },
57
- { label: 'AI Routing', categories: ['provider', 'subscriptions', 'helper', 'tools', 'tts'] },
61
+ { label: 'AI Routing', categories: ['provider', 'pricing', 'subscriptions', 'helper', 'tools', 'tts', 'voice'] },
58
62
  { label: 'Service & Network', categories: ['service', 'daemon', 'network', 'controlPlane', 'httpListener', 'web', 'relay'] },
59
63
  { label: 'Surfaces & Cloud', categories: ['surfaces', 'integrations', 'mcp', 'cloudflare'] },
60
64
  { label: 'Automation', categories: ['batch', 'automation', 'checkin', 'watchers', 'orchestration', 'planner', 'wrfc'] },
61
- { label: 'Runtime & Data', categories: ['storage', 'atRest', 'sandbox', 'fetch', 'agents', 'runtime', 'cache', 'telemetry', 'diagnostics', 'learning'] },
62
- { label: 'Advanced', categories: ['flags', 'release', 'danger'] },
65
+ { label: 'Runtime & Data', categories: ['storage', 'atRest', 'sandbox', 'fetch', 'agents', 'runtime', 'power', 'memory', 'cache', 'telemetry', 'diagnostics', 'learning'] },
66
+ { label: 'Advanced', categories: ['release', 'update', 'danger'] },
63
67
  ];
64
68
 
65
69
  export const SETTINGS_CATEGORIES: SettingsCategory[] = SETTINGS_CATEGORY_GROUPS.flatMap(group => group.categories);
@@ -74,34 +78,40 @@ export interface SettingEntry {
74
78
  sourceLabel?: string;
75
79
  lockReason?: string;
76
80
  /**
77
- * Present when this row is a feature-unit toggle header (a feature flag
78
- * rendered as one unit with its config keys beneath it). The row's
79
- * setting.key is `featureFlags.<id>` and setting.type is 'boolean'; toggling
80
- * routes to the feature-flag manager, not a plain config write.
81
+ * Present when this row is a feature-unit header (a platform capability
82
+ * rendered as one unit with the settings that tune it beneath). The row IS
83
+ * the real config row for the feature's enablement key — boolean headers
84
+ * toggle and enum headers cycle their mode choices through the ordinary
85
+ * settings write path (a plain config write; the settings bridge keeps the
86
+ * runtime gate in sync).
81
87
  */
82
88
  flag?: FlagEntry;
83
89
  /**
84
- * Present when this row is a config sub-option OWNED by a feature unit — the
85
- * id of the owning flag. Drives the indented rendering beneath its header and
86
- * marks the row as claimed so it does not double-list as an orphan.
90
+ * Present when this row is a settings sub-option OWNED by a feature unit —
91
+ * the id of the owning feature. Drives the indented rendering beneath its
92
+ * header and marks the row as claimed so it does not double-list as an
93
+ * orphan.
87
94
  */
88
95
  ownerFlagId?: string;
89
96
  }
90
97
 
91
98
  export interface FlagEntry {
99
+ /** The per-feature settings metadata (domain, enablement shape, settings keys, real description). */
100
+ feature: FeatureSetting;
101
+ /** The internal gate declaration (kill state/reason live here). */
92
102
  flag: FeatureFlag;
93
103
  /** Live effective state as the runtime currently sees it. */
94
104
  state: FlagState;
95
105
  /**
96
106
  * Last known config-layer (persisted) value. Equals `state` unless a
97
- * startup-gated flag was toggled this session — then it holds the value that
98
- * will take effect on the next launch.
107
+ * startup-gated feature was changed this session — then it holds the value
108
+ * that will take effect on the next launch.
99
109
  */
100
110
  persistedState: FlagState;
101
111
  /**
102
- * True when a startup-gated flag's persisted value differs from its effective
103
- * state, i.e. a restart is required before the change takes effect. Sourced
104
- * from FeatureFlagManager.getAll(), never guessed.
112
+ * True when a startup-gated feature's persisted value differs from its
113
+ * effective state, i.e. a restart is required before the change takes
114
+ * effect. Sourced from FeatureFlagManager.getAll(), never guessed.
105
115
  */
106
116
  pendingRestart: boolean;
107
117
  }