@pellux/goodvibes-tui 0.20.3 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +23 -2
  3. package/docs/foundation-artifacts/operator-contract.json +78 -1
  4. package/package.json +4 -2
  5. package/src/audio/spoken-turn-controller.ts +31 -1
  6. package/src/audio/spoken-turn-wiring.ts +26 -4
  7. package/src/cli/bundle-command.ts +1 -1
  8. package/src/cli/completions/generate.ts +658 -0
  9. package/src/cli/config-overrides.ts +68 -0
  10. package/src/cli/entrypoint.ts +6 -0
  11. package/src/cli/help.ts +4 -2
  12. package/src/cli/management-commands.ts +1 -1
  13. package/src/cli/management.ts +1 -8
  14. package/src/cli/parser.ts +31 -18
  15. package/src/cli/service-command.ts +1 -1
  16. package/src/cli/surface-command.ts +1 -1
  17. package/src/cli/tui-startup.ts +72 -10
  18. package/src/cli/types.ts +14 -3
  19. package/src/cli-flags.ts +1 -0
  20. package/src/config/atomic-write.ts +70 -0
  21. package/src/config/goodvibes-home-audit.ts +2 -0
  22. package/src/config/read-versioned.ts +115 -0
  23. package/src/core/context-auto-compact.ts +77 -0
  24. package/src/core/conversation-rendering.ts +49 -15
  25. package/src/core/conversation.ts +101 -16
  26. package/src/core/format-user-error.ts +192 -0
  27. package/src/core/stream-event-wiring.ts +144 -0
  28. package/src/core/stream-stall-watchdog.ts +103 -0
  29. package/src/core/system-message-router.ts +5 -1
  30. package/src/core/turn-event-wiring.ts +124 -0
  31. package/src/daemon/cli.ts +5 -0
  32. package/src/export/cost-utils.ts +71 -0
  33. package/src/export/gist-uploader.ts +136 -0
  34. package/src/input/command-registry.ts +32 -1
  35. package/src/input/commands/control-room-runtime.ts +10 -10
  36. package/src/input/commands/experience-runtime.ts +5 -4
  37. package/src/input/commands/knowledge.ts +1 -1
  38. package/src/input/commands/local-auth-runtime.ts +27 -5
  39. package/src/input/commands/local-setup.ts +4 -6
  40. package/src/input/commands/memory-product-runtime.ts +8 -6
  41. package/src/input/commands/operator-panel-runtime.ts +1 -1
  42. package/src/input/commands/operator-runtime.ts +3 -10
  43. package/src/input/commands/{integration-runtime.ts → plugin-runtime.ts} +1 -1
  44. package/src/input/commands/provider.ts +57 -3
  45. package/src/input/commands/recall-review.ts +26 -2
  46. package/src/input/commands/services-runtime.ts +2 -2
  47. package/src/input/commands/session-workflow.ts +8 -16
  48. package/src/input/commands/session.ts +70 -20
  49. package/src/input/commands/share-runtime.ts +99 -12
  50. package/src/input/commands/tts-runtime.ts +30 -4
  51. package/src/input/commands.ts +2 -4
  52. package/src/input/delete-key-policy.ts +46 -0
  53. package/src/input/feed-context-factory.ts +2 -0
  54. package/src/input/handler-feed.ts +3 -0
  55. package/src/input/handler-interactions.ts +2 -15
  56. package/src/input/handler-modal-routes.ts +128 -12
  57. package/src/input/handler-modal-token-routes.ts +22 -5
  58. package/src/input/handler-onboarding-cloudflare.ts +1 -1
  59. package/src/input/handler-onboarding.ts +73 -69
  60. package/src/input/handler-types.ts +163 -0
  61. package/src/input/handler.ts +6 -2
  62. package/src/input/input-history.ts +76 -6
  63. package/src/input/model-picker-filter.ts +265 -0
  64. package/src/input/model-picker-items.ts +208 -0
  65. package/src/input/model-picker.ts +92 -325
  66. package/src/input/onboarding/handler-onboarding-routes.ts +7 -2
  67. package/src/input/onboarding/onboarding-verification-helpers.ts +76 -0
  68. package/src/input/onboarding/onboarding-wizard-apply.ts +14 -4
  69. package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +16 -2
  70. package/src/input/onboarding/onboarding-wizard-cloudflare.ts +8 -8
  71. package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +1 -1
  72. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +2 -29
  73. package/src/input/onboarding/onboarding-wizard-rules.ts +28 -28
  74. package/src/input/onboarding/onboarding-wizard-state.ts +20 -20
  75. package/src/input/onboarding/onboarding-wizard-steps.ts +24 -25
  76. package/src/input/onboarding/onboarding-wizard-types.ts +145 -3
  77. package/src/input/onboarding/onboarding-wizard-validation.ts +77 -0
  78. package/src/input/onboarding/onboarding-wizard.ts +3 -3
  79. package/src/input/settings-modal-behavior.ts +5 -0
  80. package/src/input/settings-modal-data.ts +378 -0
  81. package/src/input/settings-modal-mutations.ts +157 -0
  82. package/src/input/settings-modal-reset.ts +154 -0
  83. package/src/input/settings-modal.ts +236 -232
  84. package/src/main.ts +93 -85
  85. package/src/panels/agent-inspector-panel.ts +120 -18
  86. package/src/panels/agent-inspector-shared.ts +29 -0
  87. package/src/panels/builtin/agent.ts +4 -1
  88. package/src/panels/builtin/development.ts +5 -1
  89. package/src/panels/builtin/knowledge.ts +14 -13
  90. package/src/panels/builtin/operations.ts +22 -1
  91. package/src/panels/builtin/shared.ts +7 -0
  92. package/src/panels/cockpit-panel.ts +123 -3
  93. package/src/panels/cockpit-read-model.ts +232 -0
  94. package/src/panels/confirm-state.ts +27 -12
  95. package/src/panels/cost-tracker-panel.ts +23 -67
  96. package/src/panels/eval-panel.ts +10 -9
  97. package/src/panels/index.ts +1 -1
  98. package/src/panels/knowledge-graph-panel.ts +84 -0
  99. package/src/panels/local-auth-panel.ts +124 -4
  100. package/src/panels/memory-panel.ts +370 -40
  101. package/src/panels/project-planning-panel.ts +42 -4
  102. package/src/panels/search-focus.ts +11 -5
  103. package/src/panels/session-maintenance.ts +66 -15
  104. package/src/panels/subscription-panel.ts +33 -25
  105. package/src/panels/types.ts +28 -1
  106. package/src/panels/wrfc-panel.ts +224 -41
  107. package/src/renderer/agent-detail-modal.ts +118 -13
  108. package/src/renderer/code-block.ts +10 -2
  109. package/src/renderer/compositor.ts +18 -4
  110. package/src/renderer/context-inspector.ts +1 -5
  111. package/src/renderer/context-status-hint.ts +54 -0
  112. package/src/renderer/diff.ts +94 -21
  113. package/src/renderer/markdown.ts +29 -13
  114. package/src/renderer/settings-modal-helpers.ts +1 -1
  115. package/src/renderer/settings-modal.ts +90 -10
  116. package/src/renderer/shell-surface.ts +10 -0
  117. package/src/renderer/syntax-highlighter.ts +10 -3
  118. package/src/renderer/term-caps.ts +318 -0
  119. package/src/renderer/theme.ts +158 -0
  120. package/src/renderer/tool-call.ts +12 -2
  121. package/src/renderer/ui-factory.ts +50 -6
  122. package/src/runtime/bootstrap-command-context.ts +1 -0
  123. package/src/runtime/bootstrap-command-parts.ts +18 -0
  124. package/src/runtime/bootstrap-core.ts +145 -13
  125. package/src/runtime/bootstrap-shell.ts +11 -0
  126. package/src/runtime/bootstrap.ts +9 -0
  127. package/src/runtime/onboarding/apply.ts +4 -6
  128. package/src/runtime/onboarding/index.ts +1 -0
  129. package/src/runtime/onboarding/markers.ts +42 -49
  130. package/src/runtime/onboarding/progress.ts +148 -0
  131. package/src/runtime/onboarding/state.ts +133 -55
  132. package/src/runtime/onboarding/types.ts +20 -0
  133. package/src/runtime/services.ts +27 -1
  134. package/src/runtime/wrfc-persistence.ts +237 -0
  135. package/src/shell/blocking-input.ts +20 -5
  136. package/src/tools/wrfc-agent-guard.ts +64 -3
  137. package/src/utils/format-elapsed.ts +30 -0
  138. package/src/utils/terminal-width.ts +45 -0
  139. package/src/version.ts +1 -1
  140. package/src/work-plans/work-plan-store.ts +4 -6
  141. package/src/panels/knowledge-panel.ts +0 -345
  142. package/src/planning/project-planning-coordinator.ts +0 -543
@@ -9,10 +9,10 @@ import {
9
9
  type ExternalSurfaceSpec,
10
10
  } from './onboarding-wizard-external-surfaces.ts';
11
11
  import { countSelected, modelSelectionLabel, normalizeText } from './onboarding-wizard-helpers.ts';
12
- import type { OnboardingWizardController } from './onboarding-wizard.ts';
12
+ import type { OnboardingWizardControllerLike } from './onboarding-wizard-types.ts';
13
13
  import type { OnboardingWizardAcknowledgementFieldDefinition, OnboardingWizardActionFieldDefinition, OnboardingWizardChecklistFieldDefinition, OnboardingWizardExternalSurfaceStepId, OnboardingWizardFieldDefinition, OnboardingWizardModelPickerFieldDefinition, OnboardingWizardRadioFieldDefinition, OnboardingWizardRadioOption, OnboardingWizardStepDefinition } from './onboarding-wizard-types.ts';
14
14
 
15
- export function buildOnboardingWizardSteps(controller: OnboardingWizardController): readonly OnboardingWizardStepDefinition[] {
15
+ export function buildOnboardingWizardSteps(controller: OnboardingWizardControllerLike): readonly OnboardingWizardStepDefinition[] {
16
16
  if (controller.hydrationPending || controller.hydrationError !== null) return [buildLoadingStep(controller)];
17
17
 
18
18
  const capabilities = controller.getCapabilitySelectionState();
@@ -48,8 +48,8 @@ function buildApplyAndContinueAction(step: OnboardingWizardStepDefinition): Onbo
48
48
  kind: 'action',
49
49
  id: `${step.id}.apply-and-continue`,
50
50
  action: 'apply-and-continue',
51
- label: 'Apply & Continue To Next Section',
52
- hint: 'Save the current wizard selections in this onboarding session and move to the next section. Settings are persisted on the final Review apply.',
51
+ label: 'Next section',
52
+ hint: 'Selections are kept in this wizard session and persisted on the final Review apply.',
53
53
  defaultValue: 'Apply & next',
54
54
  spacerBeforeRows: 2,
55
55
  };
@@ -66,7 +66,7 @@ function addApplyAndContinueAction(step: OnboardingWizardStepDefinition): Onboar
66
66
  };
67
67
  }
68
68
 
69
- export function buildLoadingStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
69
+ export function buildLoadingStep(controller: OnboardingWizardControllerLike): OnboardingWizardStepDefinition {
70
70
  const failed = controller.hydrationError !== null;
71
71
  return {
72
72
  id: 'loading',
@@ -98,7 +98,7 @@ export function buildLoadingStep(controller: OnboardingWizardController): Onboar
98
98
  };
99
99
  }
100
100
 
101
- export function buildCapabilitiesStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
101
+ export function buildCapabilitiesStep(controller: OnboardingWizardControllerLike): OnboardingWizardStepDefinition {
102
102
  const capabilities = controller.getCapabilitySelectionState();
103
103
  const selectedCount = countSelected(capabilities);
104
104
  const fields: OnboardingWizardFieldDefinition[] = [
@@ -145,7 +145,7 @@ export function buildCapabilitiesStep(controller: OnboardingWizardController): O
145
145
  };
146
146
  }
147
147
 
148
- export function buildProvidersStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
148
+ export function buildProvidersStep(controller: OnboardingWizardControllerLike): OnboardingWizardStepDefinition {
149
149
  const providerAck = controller.runtimeDerived.reopenEditAcknowledgements.providers;
150
150
  const activeSubscriptions = controller.runtimeSnapshot?.subscriptions.active ?? [];
151
151
  const pendingSubscriptions = controller.runtimeSnapshot?.subscriptions.pending ?? [];
@@ -250,11 +250,11 @@ export function buildProvidersStep(controller: OnboardingWizardController): Onbo
250
250
  };
251
251
  }
252
252
 
253
- export function buildProviderAccessStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
253
+ export function buildProviderAccessStep(controller: OnboardingWizardControllerLike): OnboardingWizardStepDefinition {
254
254
  return buildProvidersStep(controller);
255
255
  }
256
256
 
257
- export function buildDefaultModelStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
257
+ export function buildDefaultModelStep(controller: OnboardingWizardControllerLike): OnboardingWizardStepDefinition {
258
258
  const routing = controller.runtimeSnapshot?.providerRouting;
259
259
  const primarySelectionField: OnboardingWizardModelPickerFieldDefinition = {
260
260
  kind: 'modelPicker',
@@ -294,7 +294,7 @@ export function buildDefaultModelStep(controller: OnboardingWizardController): O
294
294
  };
295
295
  }
296
296
 
297
- export function buildExternalServicesStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
297
+ export function buildExternalServicesStep(controller: OnboardingWizardControllerLike): OnboardingWizardStepDefinition {
298
298
  const selectedCount = EXTERNAL_SURFACE_SPECS
299
299
  .filter((surface) => controller.getBooleanFieldValue(
300
300
  surface.enabledFieldId,
@@ -355,7 +355,7 @@ export function buildExternalServicesStep(controller: OnboardingWizardController
355
355
  };
356
356
  }
357
357
 
358
- function getSelectedExternalSurfaceSpecs(controller: OnboardingWizardController): readonly ExternalSurfaceSpec[] {
358
+ function getSelectedExternalSurfaceSpecs(controller: OnboardingWizardControllerLike): readonly ExternalSurfaceSpec[] {
359
359
  return EXTERNAL_SURFACE_SPECS.filter((surface) => (
360
360
  controller.getBooleanFieldValue(
361
361
  surface.enabledFieldId,
@@ -378,7 +378,7 @@ const SURFACE_AUTO_START_OPTIONS: readonly OnboardingWizardRadioOption[] = [
378
378
  ];
379
379
 
380
380
  function buildExternalSurfaceStep(
381
- controller: OnboardingWizardController,
381
+ controller: OnboardingWizardControllerLike,
382
382
  surface: ExternalSurfaceSpec,
383
383
  ): OnboardingWizardStepDefinition {
384
384
  let setupCount = 0;
@@ -461,7 +461,7 @@ function buildExternalSurfaceStep(
461
461
  };
462
462
  }
463
463
 
464
- export function buildAccessStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
464
+ export function buildAccessStep(controller: OnboardingWizardControllerLike): OnboardingWizardStepDefinition {
465
465
  const step = buildAccountsStep(controller);
466
466
  return {
467
467
  ...step,
@@ -471,7 +471,7 @@ export function buildAccessStep(controller: OnboardingWizardController): Onboard
471
471
  };
472
472
  }
473
473
 
474
- export function buildExperienceStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
474
+ export function buildExperienceStep(controller: OnboardingWizardControllerLike): OnboardingWizardStepDefinition {
475
475
  return {
476
476
  id: 'experience',
477
477
  title: 'Shell experience',
@@ -512,7 +512,7 @@ export function buildExperienceStep(controller: OnboardingWizardController): Onb
512
512
  };
513
513
  }
514
514
 
515
- export function buildNetworkStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
515
+ export function buildNetworkStep(controller: OnboardingWizardControllerLike): OnboardingWizardStepDefinition {
516
516
  const bindSettings = controller.runtimeSnapshot?.bindSettings;
517
517
  const browserEnabled = controller.shouldEnableBrowserSurface();
518
518
  const listenerEnabled = controller.shouldExposeHttpListenerNetworkFields();
@@ -621,6 +621,12 @@ export function buildNetworkStep(controller: OnboardingWizardController): Onboar
621
621
  }
622
622
  }
623
623
 
624
+ if (controlPlaneRemote || listenerEnabled || browserEnabled) { // TLS warn + CORS notice.
625
+ const cpOff = controlPlaneRemote && String(controller.runtimeSnapshot?.config.controlPlane?.tls?.mode ?? 'off') === 'off';
626
+ const hlOff = listenerEnabled && String(controller.runtimeSnapshot?.config.httpListener?.tls?.mode ?? 'off') === 'off';
627
+ if (cpOff || hlOff) { const a = [cpOff ? 'control plane' : '', hlOff ? 'HTTP listener' : ''].filter(Boolean).join(' and '); fields.push({ kind: 'status', id: 'network.tls-warn', label: `TLS off — ${a} transmits plaintext`, defaultValue: 'Warning', hint: `The ${a} is network-reachable but TLS is off. Traffic travels in plaintext. Enable TLS or use a terminating reverse proxy.` }); }
628
+ if (listenerEnabled) { fields.push({ kind: 'status', id: 'network.cors-note', label: 'CORS must be configured manually', defaultValue: 'Info', hint: 'httpListener.enforceCors and httpListener.allowedOrigins are not in ConfigKey union (SDK handoff Item 5). Edit ~/.goodvibes/tui/settings.json to set them, then restart the daemon.' }); }
629
+ }
624
630
  return {
625
631
  id: 'network',
626
632
  title: 'Network setup',
@@ -636,7 +642,7 @@ export function buildNetworkStep(controller: OnboardingWizardController): Onboar
636
642
  };
637
643
  }
638
644
 
639
- export function buildAccountsStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
645
+ export function buildAccountsStep(controller: OnboardingWizardControllerLike): OnboardingWizardStepDefinition {
640
646
  const subscriptionsAck = controller.runtimeDerived.reopenEditAcknowledgements.subscriptions;
641
647
  const authAck = controller.runtimeDerived.reopenEditAcknowledgements.auth;
642
648
  const auth = controller.runtimeSnapshot?.auth.snapshot;
@@ -740,7 +746,7 @@ export function buildAccountsStep(controller: OnboardingWizardController): Onboa
740
746
  };
741
747
  }
742
748
 
743
- export function buildReviewStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
749
+ export function buildReviewStep(controller: OnboardingWizardControllerLike): OnboardingWizardStepDefinition {
744
750
  const feedback = controller.applyFeedback;
745
751
  const feedbackFields: OnboardingWizardFieldDefinition[] = feedback
746
752
  ? [
@@ -778,19 +784,12 @@ export function buildReviewStep(controller: OnboardingWizardController): Onboard
778
784
  ],
779
785
  fields: [
780
786
  ...feedbackFields,
781
- {
782
- kind: 'status',
783
- id: 'review.global-marker',
784
- label: 'Global onboarding check',
785
- hint: 'Opening this wizard marks onboarding as shown for this user account, so new projects do not reopen it automatically.',
786
- defaultValue: 'Already marked as shown',
787
- },
788
787
  {
789
788
  kind: 'action',
790
789
  id: 'review.apply',
791
790
  action: 'apply',
792
791
  label: 'Apply settings and verify',
793
- hint: 'Persist the wizard settings and verify the resulting runtime state. The global onboarding check was already recorded when the wizard opened.',
792
+ hint: 'Persist the wizard settings and verify the resulting runtime state. The global onboarding check marker is recorded when apply succeeds.',
794
793
  defaultValue: 'Ready',
795
794
  },
796
795
  ],
@@ -1,10 +1,7 @@
1
- import { isIP } from 'node:net';
2
1
  import type { ModelPickerTarget } from '../model-picker.ts';
3
2
  import {
4
- deriveOnboardingStepState,
5
3
  type OnboardingAcknowledgementReason,
6
4
  type OnboardingAcknowledgementTarget,
7
- type OnboardingApplyOperation,
8
5
  type OnboardingApplyRequest,
9
6
  type OnboardingMode,
10
7
  type OnboardingSnapshotState,
@@ -193,3 +190,148 @@ export interface OnboardingWizardRuntimeHydration {
193
190
  }
194
191
 
195
192
  export type MutableModelSelectionMap = Map<ModelPickerTarget, OnboardingWizardModelSelection>;
193
+
194
+ // ---------------------------------------------------------------------------
195
+ // External surface types (moved here to break the surfaces ↔ extra-specs cycle)
196
+ // ---------------------------------------------------------------------------
197
+
198
+ export interface ExternalSurfaceSetupFieldSpec {
199
+ readonly id: string;
200
+ readonly configKey: ConfigKey;
201
+ readonly kind: 'text' | 'masked' | 'radio';
202
+ readonly valueType?: 'string' | 'number';
203
+ readonly label: string;
204
+ readonly hint: string;
205
+ readonly placeholder: string;
206
+ readonly options?: readonly OnboardingWizardRadioOption[];
207
+ readonly defaultNumber?: number;
208
+ readonly min?: number;
209
+ readonly max?: number;
210
+ readonly defaultValue: (snapshot: OnboardingSnapshotState | null) => string;
211
+ }
212
+
213
+ export interface ExternalSurfaceSpec {
214
+ readonly id: string;
215
+ readonly enabledFieldId: string;
216
+ readonly enabledConfigKey: ConfigKey;
217
+ readonly label: string;
218
+ readonly hint: string;
219
+ /**
220
+ * Existing SDK config key. In onboarding this maps to the per-surface
221
+ * auto-start choice, not to whether setup fields are shown.
222
+ */
223
+ readonly defaultEnabled: (snapshot: OnboardingSnapshotState | null) => boolean;
224
+ readonly fields: readonly ExternalSurfaceSetupFieldSpec[];
225
+ }
226
+
227
+ // ---------------------------------------------------------------------------
228
+ // Controller interface (moved here to break the wizard ↔ satellites cycle)
229
+ // ---------------------------------------------------------------------------
230
+
231
+ /**
232
+ * Public interface of OnboardingWizardController. Satellite modules
233
+ * (apply, steps, rules, state, cloudflare, cloudflare-step) receive the
234
+ * controller through this interface so they do not need to import from
235
+ * onboarding-wizard.ts and the circular dependency is broken.
236
+ */
237
+ export interface OnboardingWizardControllerLike {
238
+ // State fields
239
+ active: boolean;
240
+ mode: OnboardingWizardMode;
241
+ stepIndex: number;
242
+ hydrationPending: boolean;
243
+ hydrationError: string | null;
244
+ pendingModelPickerTarget: ModelPickerTarget | null;
245
+ pendingAction: OnboardingWizardAction | null;
246
+ editingFieldId: string | null;
247
+ editBuffer: string;
248
+ applyFeedback: OnboardingWizardApplyFeedback | null;
249
+
250
+ readonly scrollOffsets: number[];
251
+ readonly selectedFieldIndices: number[];
252
+ readonly dirtyStepIds: Set<OnboardingWizardStepId>;
253
+ readonly toggleState: Map<string, boolean>;
254
+ readonly touchedActionFields: Set<string>;
255
+ readonly radioState: Map<string, string>;
256
+ readonly textState: Map<string, string>;
257
+ readonly modelSelectionState: MutableModelSelectionMap;
258
+
259
+ readonly baselineToggleState: Map<string, boolean>;
260
+ readonly baselineRadioState: Map<string, string>;
261
+ readonly baselineTextState: Map<string, string>;
262
+ readonly baselineModelSelectionState: MutableModelSelectionMap;
263
+
264
+ runtimeSnapshot: OnboardingSnapshotState | null;
265
+ runtimeDerived: OnboardingStepDerivationState;
266
+
267
+ // Computed
268
+ readonly steps: readonly OnboardingWizardStepDefinition[];
269
+ readonly currentStep: OnboardingWizardStepDefinition;
270
+ readonly dirty: boolean;
271
+ readonly dirtyStepCount: number;
272
+
273
+ // Field value accessors
274
+ getFieldValue(field: OnboardingWizardFieldDefinition): boolean | string | OnboardingWizardModelSelection;
275
+ getFieldById(fieldId: string): OnboardingWizardFieldDefinition | null;
276
+ getStringFieldValue(fieldId: string, fallback: string): string;
277
+ getBooleanFieldValue(fieldId: string, fallback: boolean): boolean;
278
+ parseIntegerFieldValue(fieldId: string, fallback: number): number | null;
279
+ getPortFieldValue(fieldId: string, fallback: number): number;
280
+ getNumberFieldValue(fieldId: string, fallback: number, min?: number, max?: number): number;
281
+
282
+ // Capability / rules accessors
283
+ isCapabilitySelected(capabilityId: OnboardingStep1CapabilityId): boolean;
284
+ hasServerCapabilitiesSelected(): boolean;
285
+ shouldEnableBrowserSurface(): boolean;
286
+ hasSelectedInboundExternalSurface(): boolean;
287
+ isRequiredExternalSetupField(fieldId: string): boolean;
288
+ getSelectedSecretMedium(): 'secure' | 'plaintext';
289
+ shouldEnableHttpListener(): boolean;
290
+ shouldExposeHttpListenerNetworkFields(): boolean;
291
+ shouldExposeControlPlaneNetwork(): boolean;
292
+ requiresAuthBootstrap(): boolean;
293
+ hasAdminAuthUser(): boolean;
294
+ hasLocalAuthUser(): boolean;
295
+ hasBootstrapCredentialPresent(): boolean;
296
+ getDefaultAdminUsername(): string;
297
+ getSharedIpDefault(enabled: { readonly controlPlane: boolean; readonly httpListener: boolean; readonly web: boolean }): boolean;
298
+ getSharedIpHostDefault(enabled: { readonly controlPlane: boolean; readonly httpListener: boolean; readonly web: boolean }): string;
299
+
300
+ // Capability mutations
301
+ toggleCapability(capabilityId: OnboardingStep1CapabilityId): void;
302
+ selectAllServerCapabilities(): void;
303
+ selectLocalTuiOnly(): void;
304
+ selectAllExternalSurfaces(): void;
305
+ clearExternalSurfaces(): void;
306
+ setCapabilityValue(capabilityId: OnboardingStep1CapabilityId, selected: boolean): void;
307
+ getCurrentCapabilities(): readonly OnboardingStep1CapabilityItem[];
308
+ getCapabilitySelectionState(): readonly OnboardingStep1CapabilityItem[];
309
+
310
+ // State operations
311
+ recalculateDirtyState(): void;
312
+ reconcileStateWithCurrentDefinitions(): void;
313
+ reconcileStepCursor(stepIndex: number): void;
314
+ resetValuesFromCurrentDefinitions(): void;
315
+ ensureSelectionVisible(visibleFields: number): void;
316
+ isFieldDirty(fieldId: string): boolean;
317
+ isFieldDirtyByDefinition(field: OnboardingWizardFieldDefinition): boolean;
318
+ isFieldSatisfied(field: OnboardingWizardFieldDefinition): boolean;
319
+ isStepDirty(stepIndex: number): boolean;
320
+ hasExistingAccessState(): boolean;
321
+ getBlockingFieldLabels(): readonly string[];
322
+ getFieldValidationError(step: OnboardingWizardStepDefinition, field: OnboardingWizardFieldDefinition): string | null;
323
+ getToggleFieldCount(stepIndex: number): number;
324
+ getCompletedToggleCount(stepIndex: number): number;
325
+ getStepFieldCount(stepIndex: number): number;
326
+ getCompletedFieldCount(stepIndex: number): number;
327
+
328
+ // Additional operations used by steps/apply
329
+ buildApplyRequest(): OnboardingApplyRequest;
330
+ isEditingTextField(): boolean;
331
+ getTextFieldValue(fieldId: string, fallback?: string): string;
332
+ getFieldValueLabel(field: OnboardingWizardFieldDefinition): string;
333
+ getSelectedFieldIndex(): number;
334
+ getSelectedField(): OnboardingWizardFieldDefinition | null;
335
+ getFieldWindow(visibleFields: number): OnboardingWizardFieldWindow;
336
+ }
337
+
@@ -0,0 +1,77 @@
1
+ import { normalizeText } from './onboarding-wizard-helpers.ts';
2
+ import type { OnboardingWizardControllerLike } from './onboarding-wizard-types.ts';
3
+ import type { OnboardingWizardFieldDefinition, OnboardingWizardStepDefinition } from './onboarding-wizard-types.ts';
4
+
5
+ export interface WizardStepValidationResult {
6
+ /** Human-readable error strings for each violating field. */
7
+ readonly errors: readonly string[];
8
+ /** ID of the first field that has an error, or null when all pass. */
9
+ readonly firstOffendingFieldId: string | null;
10
+ }
11
+
12
+ /**
13
+ * Validate all fields on a single wizard step, checking:
14
+ * - required text / masked fields that are empty
15
+ * - required acknowledgement fields that are unchecked
16
+ * - any general field-level validation errors (via getFieldValidationError)
17
+ *
18
+ * Returns per-field error messages and the first offending field id so the
19
+ * caller can block navigation and jump focus to the first problem.
20
+ */
21
+ export function getStepValidationErrors(
22
+ controller: OnboardingWizardControllerLike,
23
+ step: OnboardingWizardStepDefinition,
24
+ ): WizardStepValidationResult {
25
+ const errors: string[] = [];
26
+ let firstOffendingFieldId: string | null = null;
27
+
28
+ for (const field of step.fields) {
29
+ const error = getFieldError(controller, step, field);
30
+ if (error !== null) {
31
+ errors.push(error);
32
+ if (firstOffendingFieldId === null) firstOffendingFieldId = field.id;
33
+ }
34
+ }
35
+
36
+ return { errors, firstOffendingFieldId };
37
+ }
38
+
39
+ function getFieldError(
40
+ controller: OnboardingWizardControllerLike,
41
+ step: OnboardingWizardStepDefinition,
42
+ field: OnboardingWizardFieldDefinition,
43
+ ): string | null {
44
+ // Required acknowledgement not checked
45
+ if (field.kind === 'acknowledgement' && field.required) {
46
+ if (!controller.isFieldSatisfied(field)) {
47
+ return `${step.shortLabel}: ${field.label} must be acknowledged before continuing.`;
48
+ }
49
+ return null;
50
+ }
51
+
52
+ // Required text / masked field that is empty
53
+ if ((field.kind === 'text' || field.kind === 'masked') && field.required === true) {
54
+ const value = normalizeText(controller.getFieldValue(field) as string);
55
+ if (value.length === 0) {
56
+ return `${step.shortLabel}: ${field.label} is required.`;
57
+ }
58
+ }
59
+
60
+ // Delegate all other field-level validation (format errors, port range, etc.)
61
+ return controller.getFieldValidationError(step, field);
62
+ }
63
+
64
+ /**
65
+ * Focus the first offending field on the current step by mutating the
66
+ * controller's selectedFieldIndices. The renderer will pick up the change on
67
+ * the next paint cycle.
68
+ */
69
+ export function focusFirstOffendingField(
70
+ controller: OnboardingWizardControllerLike,
71
+ fieldId: string,
72
+ ): void {
73
+ const fields = controller.currentStep.fields;
74
+ const index = fields.findIndex((f) => f.id === fieldId);
75
+ if (index < 0) return;
76
+ controller.selectedFieldIndices[controller.stepIndex] = index;
77
+ }
@@ -34,12 +34,12 @@ import {
34
34
  toggleCapability as toggleCapabilityForController,
35
35
  } from './onboarding-wizard-rules.ts';
36
36
  import { ensureSelectionVisible as ensureSelectionVisibleForController, getBlockingFieldLabels as getBlockingFieldLabelsForController, getCapabilitySelectionState as getCapabilitySelectionStateForController, getCompletedFieldCount as getCompletedFieldCountForController, getCompletedToggleCount as getCompletedToggleCountForController, getCurrentCapabilities as getCurrentCapabilitiesForController, getFieldById as getFieldByIdForController, getFieldValidationError as getFieldValidationErrorForController, getStepFieldCount as getStepFieldCountForController, getToggleFieldCount as getToggleFieldCountForController, hasExistingAccessState as hasExistingAccessStateForController, isFieldDirty as isFieldDirtyForController, isFieldDirtyByDefinition as isFieldDirtyByDefinitionForController, isFieldSatisfied as isFieldSatisfiedForController, isStepDirty as isStepDirtyForController, recalculateDirtyState as recalculateDirtyStateForController, reconcileStateWithCurrentDefinitions as reconcileStateWithCurrentDefinitionsForController, reconcileStepCursor as reconcileStepCursorForController, resetValuesFromCurrentDefinitions as resetValuesFromCurrentDefinitionsForController } from './onboarding-wizard-state.ts';
37
- import type { MutableModelSelectionMap, OnboardingWizardAction, OnboardingWizardApplyFeedback, OnboardingWizardFieldDefinition, OnboardingWizardFieldWindow, OnboardingWizardMode, OnboardingWizardModelSelection, OnboardingWizardRuntimeHydration, OnboardingWizardSnapshot, OnboardingWizardStepDefinition, OnboardingWizardStepId } from './onboarding-wizard-types.ts';
37
+ import type { OnboardingWizardControllerLike, MutableModelSelectionMap, OnboardingWizardAction, OnboardingWizardApplyFeedback, OnboardingWizardFieldDefinition, OnboardingWizardFieldWindow, OnboardingWizardMode, OnboardingWizardModelSelection, OnboardingWizardRuntimeHydration, OnboardingWizardSnapshot, OnboardingWizardStepDefinition, OnboardingWizardStepId } from './onboarding-wizard-types.ts';
38
38
 
39
- export type { OnboardingWizardAcknowledgementFieldDefinition, OnboardingWizardAction, OnboardingWizardActionFieldDefinition, OnboardingWizardApplyFeedback, OnboardingWizardApplyFeedbackSeverity, OnboardingWizardChecklistFieldDefinition, OnboardingWizardExternalSurfaceStepId, OnboardingWizardFieldDefinition, OnboardingWizardFieldKind, OnboardingWizardFieldWindow, OnboardingWizardMaskedFieldDefinition, OnboardingWizardMode, OnboardingWizardModelPickerFieldDefinition, OnboardingWizardModelSelection, OnboardingWizardRadioFieldDefinition, OnboardingWizardRadioOption, OnboardingWizardRuntimeHydration, OnboardingWizardSnapshot, OnboardingWizardStatusFieldDefinition, OnboardingWizardStepDefinition, OnboardingWizardStepId, OnboardingWizardTextFieldDefinition } from './onboarding-wizard-types.ts';
39
+ export type { OnboardingWizardControllerLike, OnboardingWizardAcknowledgementFieldDefinition, OnboardingWizardAction, OnboardingWizardActionFieldDefinition, OnboardingWizardApplyFeedback, OnboardingWizardApplyFeedbackSeverity, OnboardingWizardChecklistFieldDefinition, OnboardingWizardExternalSurfaceStepId, OnboardingWizardFieldDefinition, OnboardingWizardFieldKind, OnboardingWizardFieldWindow, OnboardingWizardMaskedFieldDefinition, OnboardingWizardMode, OnboardingWizardModelPickerFieldDefinition, OnboardingWizardModelSelection, OnboardingWizardRadioFieldDefinition, OnboardingWizardRadioOption, OnboardingWizardRuntimeHydration, OnboardingWizardSnapshot, OnboardingWizardStatusFieldDefinition, OnboardingWizardStepDefinition, OnboardingWizardStepId, OnboardingWizardTextFieldDefinition } from './onboarding-wizard-types.ts';
40
40
  export { getOnboardingWizardBodyRows, getOnboardingWizardVisibleFieldCount } from './onboarding-wizard-helpers.ts';
41
41
 
42
- export class OnboardingWizardController {
42
+ export class OnboardingWizardController implements OnboardingWizardControllerLike {
43
43
  public active = false;
44
44
  public mode: OnboardingWizardMode = 'new';
45
45
  public stepIndex = 0;
@@ -33,5 +33,10 @@ export function getNumericAdjustmentMeta(setting: ConfigSetting): {
33
33
  if (setting.key === 'wrfc.scoreThreshold') {
34
34
  return { step: 0.1, min: 0, max: 10, precision: 1 };
35
35
  }
36
+ if ((setting.key as string) === 'tts.speed') {
37
+ // Speed multiplier: 0.1 increments, min 0.1, no hard max (provider-defined).
38
+ // tts.speed is not yet a ConfigKey in the SDK schema; cast required.
39
+ return { step: 0.1, min: 0.1, precision: 1 };
40
+ }
36
41
  return { step: 1, precision: 0 };
37
42
  }