@pellux/goodvibes-tui 0.20.3 → 0.21.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 (118) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +23 -2
  3. package/docs/foundation-artifacts/operator-contract.json +78 -1
  4. package/package.json +3 -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 +662 -0
  9. package/src/cli/config-overrides.ts +68 -0
  10. package/src/cli/help.ts +4 -2
  11. package/src/cli/management-commands.ts +1 -1
  12. package/src/cli/management.ts +1 -8
  13. package/src/cli/parser.ts +14 -18
  14. package/src/cli/service-command.ts +1 -1
  15. package/src/cli/surface-command.ts +1 -1
  16. package/src/cli/tui-startup.ts +72 -10
  17. package/src/cli/types.ts +12 -3
  18. package/src/cli-flags.ts +1 -0
  19. package/src/config/atomic-write.ts +70 -0
  20. package/src/config/read-versioned.ts +115 -0
  21. package/src/core/conversation-rendering.ts +49 -15
  22. package/src/core/conversation.ts +101 -16
  23. package/src/core/format-user-error.ts +192 -0
  24. package/src/core/stream-event-wiring.ts +144 -0
  25. package/src/core/stream-stall-watchdog.ts +103 -0
  26. package/src/core/system-message-router.ts +5 -1
  27. package/src/export/cost-utils.ts +71 -0
  28. package/src/export/gist-uploader.ts +136 -0
  29. package/src/input/command-registry.ts +31 -1
  30. package/src/input/commands/control-room-runtime.ts +5 -5
  31. package/src/input/commands/experience-runtime.ts +5 -4
  32. package/src/input/commands/knowledge.ts +1 -1
  33. package/src/input/commands/local-auth-runtime.ts +27 -5
  34. package/src/input/commands/local-setup.ts +4 -6
  35. package/src/input/commands/memory-product-runtime.ts +8 -6
  36. package/src/input/commands/operator-panel-runtime.ts +1 -1
  37. package/src/input/commands/operator-runtime.ts +3 -10
  38. package/src/input/commands/{integration-runtime.ts → plugin-runtime.ts} +1 -1
  39. package/src/input/commands/recall-review.ts +26 -2
  40. package/src/input/commands/services-runtime.ts +2 -2
  41. package/src/input/commands/session-workflow.ts +3 -3
  42. package/src/input/commands/share-runtime.ts +99 -12
  43. package/src/input/commands/tts-runtime.ts +30 -4
  44. package/src/input/commands.ts +2 -2
  45. package/src/input/delete-key-policy.ts +46 -0
  46. package/src/input/feed-context-factory.ts +2 -0
  47. package/src/input/handler-feed.ts +3 -0
  48. package/src/input/handler-interactions.ts +2 -15
  49. package/src/input/handler-modal-routes.ts +91 -12
  50. package/src/input/handler-modal-token-routes.ts +3 -0
  51. package/src/input/handler-onboarding-cloudflare.ts +1 -1
  52. package/src/input/handler-onboarding.ts +55 -69
  53. package/src/input/handler-types.ts +163 -0
  54. package/src/input/handler.ts +5 -2
  55. package/src/input/input-history.ts +76 -6
  56. package/src/input/model-picker-filter.ts +265 -0
  57. package/src/input/model-picker-items.ts +208 -0
  58. package/src/input/model-picker.ts +92 -325
  59. package/src/input/onboarding/handler-onboarding-routes.ts +7 -2
  60. package/src/input/onboarding/onboarding-verification-helpers.ts +76 -0
  61. package/src/input/onboarding/onboarding-wizard-apply.ts +4 -4
  62. package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +2 -2
  63. package/src/input/onboarding/onboarding-wizard-cloudflare.ts +8 -8
  64. package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +1 -1
  65. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +2 -29
  66. package/src/input/onboarding/onboarding-wizard-rules.ts +28 -28
  67. package/src/input/onboarding/onboarding-wizard-state.ts +20 -20
  68. package/src/input/onboarding/onboarding-wizard-steps.ts +18 -25
  69. package/src/input/onboarding/onboarding-wizard-types.ts +145 -3
  70. package/src/input/onboarding/onboarding-wizard.ts +3 -3
  71. package/src/input/settings-modal-data.ts +304 -0
  72. package/src/input/settings-modal-mutations.ts +154 -0
  73. package/src/input/settings-modal.ts +182 -220
  74. package/src/main.ts +57 -57
  75. package/src/panels/builtin/agent.ts +4 -1
  76. package/src/panels/builtin/development.ts +4 -1
  77. package/src/panels/confirm-state.ts +27 -12
  78. package/src/panels/cost-tracker-panel.ts +23 -67
  79. package/src/panels/eval-panel.ts +10 -9
  80. package/src/panels/knowledge-panel.ts +3 -5
  81. package/src/panels/local-auth-panel.ts +124 -4
  82. package/src/panels/project-planning-panel.ts +42 -4
  83. package/src/panels/search-focus.ts +11 -5
  84. package/src/panels/subscription-panel.ts +33 -25
  85. package/src/panels/types.ts +28 -1
  86. package/src/panels/wrfc-panel.ts +224 -41
  87. package/src/renderer/agent-detail-modal.ts +11 -10
  88. package/src/renderer/code-block.ts +10 -2
  89. package/src/renderer/compositor.ts +18 -4
  90. package/src/renderer/context-inspector.ts +1 -5
  91. package/src/renderer/diff.ts +94 -21
  92. package/src/renderer/markdown.ts +29 -13
  93. package/src/renderer/settings-modal-helpers.ts +1 -1
  94. package/src/renderer/settings-modal.ts +77 -8
  95. package/src/renderer/syntax-highlighter.ts +10 -3
  96. package/src/renderer/term-caps.ts +318 -0
  97. package/src/renderer/theme.ts +158 -0
  98. package/src/renderer/tool-call.ts +12 -2
  99. package/src/renderer/ui-factory.ts +50 -6
  100. package/src/runtime/bootstrap-command-context.ts +1 -0
  101. package/src/runtime/bootstrap-command-parts.ts +14 -0
  102. package/src/runtime/bootstrap-core.ts +121 -13
  103. package/src/runtime/bootstrap.ts +2 -0
  104. package/src/runtime/onboarding/apply.ts +4 -6
  105. package/src/runtime/onboarding/index.ts +1 -0
  106. package/src/runtime/onboarding/markers.ts +42 -49
  107. package/src/runtime/onboarding/progress.ts +148 -0
  108. package/src/runtime/onboarding/state.ts +133 -55
  109. package/src/runtime/onboarding/types.ts +20 -0
  110. package/src/runtime/services.ts +21 -0
  111. package/src/runtime/wrfc-persistence.ts +237 -0
  112. package/src/shell/blocking-input.ts +20 -5
  113. package/src/tools/wrfc-agent-guard.ts +64 -3
  114. package/src/utils/format-elapsed.ts +30 -0
  115. package/src/utils/terminal-width.ts +45 -0
  116. package/src/version.ts +1 -1
  117. package/src/work-plans/work-plan-store.ts +4 -6
  118. package/src/planning/project-planning-coordinator.ts +0 -543
@@ -8,7 +8,7 @@ import {
8
8
  type CloudflareProvisionRequest,
9
9
  } from '../../runtime/cloudflare-control-plane.ts';
10
10
  import { buildGoodVibesSecretRef, normalizeText } from './onboarding-wizard-helpers.ts';
11
- import type { OnboardingWizardController } from './onboarding-wizard.ts';
11
+ import type { OnboardingWizardControllerLike } from './onboarding-wizard-types.ts';
12
12
  import type { OnboardingWizardRadioOption } from './onboarding-wizard-types.ts';
13
13
 
14
14
  export const CLOUDFLARE_SETUP_SOURCE_OPTIONS: readonly OnboardingWizardRadioOption[] = [
@@ -90,7 +90,7 @@ export function cloudflareComponentLabel(component: CloudflareComponent): string
90
90
  return CLOUDFLARE_COMPONENT_LABELS[component];
91
91
  }
92
92
 
93
- export function isCloudflareConfigured(controller: OnboardingWizardController): boolean {
93
+ export function isCloudflareConfigured(controller: OnboardingWizardControllerLike): boolean {
94
94
  const config = controller.runtimeSnapshot?.config.cloudflare;
95
95
  if (!config) return false;
96
96
  return config.enabled
@@ -100,11 +100,11 @@ export function isCloudflareConfigured(controller: OnboardingWizardController):
100
100
  || normalizeText(config.workerName).length > 0;
101
101
  }
102
102
 
103
- export function shouldShowCloudflareStep(controller: OnboardingWizardController): boolean {
103
+ export function shouldShowCloudflareStep(controller: OnboardingWizardControllerLike): boolean {
104
104
  return controller.isCapabilitySelected('cloudflare-batch') || isCloudflareConfigured(controller);
105
105
  }
106
106
 
107
- export function getCloudflareSetupSource(controller: OnboardingWizardController): CloudflareSetupSource {
107
+ export function getCloudflareSetupSource(controller: OnboardingWizardControllerLike): CloudflareSetupSource {
108
108
  const configuredTokenRef = controller.runtimeSnapshot?.config.cloudflare.apiTokenRef ?? '';
109
109
  const defaultValue = configuredTokenRef.startsWith('goodvibes://secrets/env/') ? 'operational-env' : 'save-only';
110
110
  const value = controller.getStringFieldValue('cloudflare.setup-source', defaultValue);
@@ -120,7 +120,7 @@ export function getCloudflareSetupSource(controller: OnboardingWizardController)
120
120
  return 'save-only';
121
121
  }
122
122
 
123
- export function getCloudflareComponentSelection(controller: OnboardingWizardController): Record<CloudflareComponent, boolean> {
123
+ export function getCloudflareComponentSelection(controller: OnboardingWizardControllerLike): Record<CloudflareComponent, boolean> {
124
124
  const selected: Record<CloudflareComponent, boolean> = { ...DEFAULT_CLOUDFLARE_COMPONENT_SELECTION };
125
125
  const configured = controller.runtimeSnapshot?.config.cloudflare;
126
126
  for (const component of CLOUDFLARE_COMPONENT_IDS) {
@@ -132,11 +132,11 @@ export function getCloudflareComponentSelection(controller: OnboardingWizardCont
132
132
  return selected;
133
133
  }
134
134
 
135
- export function getSelectedCloudflareComponents(controller: OnboardingWizardController): CloudflareComponentSelection {
135
+ export function getSelectedCloudflareComponents(controller: OnboardingWizardControllerLike): CloudflareComponentSelection {
136
136
  return getCloudflareComponentSelection(controller);
137
137
  }
138
138
 
139
- export function getCloudflareBatchMode(controller: OnboardingWizardController): CloudflareBatchMode {
139
+ export function getCloudflareBatchMode(controller: OnboardingWizardControllerLike): CloudflareBatchMode {
140
140
  const value = controller.getStringFieldValue('cloudflare.batch-mode', controller.runtimeSnapshot?.config.batch.mode ?? 'off');
141
141
  return value === 'explicit' || value === 'eligible-by-default' ? value : 'off';
142
142
  }
@@ -146,7 +146,7 @@ export function buildCloudflareApiTokenRef(envName: string): string {
146
146
  return `goodvibes://secrets/env/${encodeURIComponent(normalized)}`;
147
147
  }
148
148
 
149
- export function buildCloudflareProvisionRequest(controller: OnboardingWizardController, options: {
149
+ export function buildCloudflareProvisionRequest(controller: OnboardingWizardControllerLike, options: {
150
150
  readonly includeTransientSecrets?: boolean;
151
151
  } = {}): CloudflareProvisionRequest {
152
152
  const components = getCloudflareComponentSelection(controller);
@@ -1,4 +1,4 @@
1
- import type { ExternalSurfaceSpec } from './onboarding-wizard-external-surfaces.ts';
1
+ import type { ExternalSurfaceSpec } from './onboarding-wizard-types.ts';
2
2
 
3
3
  export const WEBHOOK_SURFACE_SPEC: ExternalSurfaceSpec = {
4
4
  id: 'webhook',
@@ -8,36 +8,9 @@ import { DEFAULT_CONFIG, type ConfigKey } from '../../config/index.ts';
8
8
  import type { OnboardingSnapshotState } from '../../runtime/onboarding/index.ts';
9
9
  import { TELEGRAM_MODE_OPTIONS, WHATSAPP_PROVIDER_OPTIONS } from './onboarding-wizard-constants.ts';
10
10
  import { HOME_ASSISTANT_SURFACE_SPEC, WEBHOOK_SURFACE_SPEC } from './onboarding-wizard-external-surface-extra-specs.ts';
11
- import type { OnboardingWizardRadioOption } from './onboarding-wizard-types.ts';
11
+ import type { ExternalSurfaceSetupFieldSpec, ExternalSurfaceSpec, OnboardingWizardRadioOption } from './onboarding-wizard-types.ts';
12
12
 
13
- export interface ExternalSurfaceSetupFieldSpec {
14
- readonly id: string;
15
- readonly configKey: ConfigKey;
16
- readonly kind: 'text' | 'masked' | 'radio';
17
- readonly valueType?: 'string' | 'number';
18
- readonly label: string;
19
- readonly hint: string;
20
- readonly placeholder: string;
21
- readonly options?: readonly OnboardingWizardRadioOption[];
22
- readonly defaultNumber?: number;
23
- readonly min?: number;
24
- readonly max?: number;
25
- readonly defaultValue: (snapshot: OnboardingSnapshotState | null) => string;
26
- }
27
-
28
- export interface ExternalSurfaceSpec {
29
- readonly id: string;
30
- readonly enabledFieldId: string;
31
- readonly enabledConfigKey: ConfigKey;
32
- readonly label: string;
33
- readonly hint: string;
34
- /**
35
- * Existing SDK config key. In onboarding this maps to the per-surface
36
- * auto-start choice, not to whether setup fields are shown.
37
- */
38
- readonly defaultEnabled: (snapshot: OnboardingSnapshotState | null) => boolean;
39
- readonly fields: readonly ExternalSurfaceSetupFieldSpec[];
40
- }
13
+ export type { ExternalSurfaceSetupFieldSpec, ExternalSurfaceSpec };
41
14
 
42
15
  function normalizeConfigValue(value: unknown): string {
43
16
  if (value === null || value === undefined) return '';
@@ -7,10 +7,10 @@ import {
7
7
  isExternalSurfaceSelectedByDefault,
8
8
  } from './onboarding-wizard-external-surfaces.ts';
9
9
  import { getExternalSurfaceSpecByFieldId, normalizeText, uniqueNonEmpty } from './onboarding-wizard-helpers.ts';
10
- import type { OnboardingWizardController } from './onboarding-wizard.ts';
10
+ import type { OnboardingWizardControllerLike } from './onboarding-wizard-types.ts';
11
11
 
12
12
  export function getSharedIpDefault(
13
- controller: OnboardingWizardController,
13
+ controller: OnboardingWizardControllerLike,
14
14
  enabled: { readonly controlPlane: boolean; readonly httpListener: boolean; readonly web: boolean },
15
15
  ): boolean {
16
16
  if (!controller.runtimeSnapshot) return true;
@@ -23,7 +23,7 @@ export function getSharedIpDefault(
23
23
  }
24
24
 
25
25
  export function getSharedIpHostDefault(
26
- controller: OnboardingWizardController,
26
+ controller: OnboardingWizardControllerLike,
27
27
  enabled: { readonly controlPlane: boolean; readonly httpListener: boolean; readonly web: boolean },
28
28
  ): string {
29
29
  if (!controller.runtimeSnapshot) return '0.0.0.0';
@@ -35,7 +35,7 @@ export function getSharedIpHostDefault(
35
35
  return hosts[0] ?? '0.0.0.0';
36
36
  }
37
37
 
38
- export function toggleCapability(controller: OnboardingWizardController, capabilityId: OnboardingStep1CapabilityId): void {
38
+ export function toggleCapability(controller: OnboardingWizardControllerLike, capabilityId: OnboardingStep1CapabilityId): void {
39
39
  if (capabilityId === 'local-tui-only') {
40
40
  for (const capability of controller.getCurrentCapabilities()) {
41
41
  controller.toggleState.set(`capabilities.${capability.id}`, capability.id === 'local-tui-only');
@@ -58,31 +58,31 @@ export function toggleCapability(controller: OnboardingWizardController, capabil
58
58
  if (!anyServerCapability) controller.toggleState.set('capabilities.local-tui-only', true);
59
59
  }
60
60
 
61
- export function selectAllServerCapabilities(controller: OnboardingWizardController): void {
61
+ export function selectAllServerCapabilities(controller: OnboardingWizardControllerLike): void {
62
62
  for (const capability of controller.getCurrentCapabilities()) {
63
63
  controller.toggleState.set(`capabilities.${capability.id}`, capability.id !== 'local-tui-only');
64
64
  }
65
65
  }
66
66
 
67
- export function selectLocalTuiOnly(controller: OnboardingWizardController): void {
67
+ export function selectLocalTuiOnly(controller: OnboardingWizardControllerLike): void {
68
68
  for (const capability of controller.getCurrentCapabilities()) {
69
69
  controller.toggleState.set(`capabilities.${capability.id}`, capability.id === 'local-tui-only');
70
70
  }
71
71
  }
72
72
 
73
- export function selectAllExternalSurfaces(controller: OnboardingWizardController): void {
73
+ export function selectAllExternalSurfaces(controller: OnboardingWizardControllerLike): void {
74
74
  for (const surface of EXTERNAL_SURFACE_SPECS) {
75
75
  controller.toggleState.set(surface.enabledFieldId, true);
76
76
  }
77
77
  }
78
78
 
79
- export function clearExternalSurfaces(controller: OnboardingWizardController): void {
79
+ export function clearExternalSurfaces(controller: OnboardingWizardControllerLike): void {
80
80
  for (const surface of EXTERNAL_SURFACE_SPECS) {
81
81
  controller.toggleState.set(surface.enabledFieldId, false);
82
82
  }
83
83
  }
84
84
 
85
- export function setCapabilityValue(controller: OnboardingWizardController, capabilityId: OnboardingStep1CapabilityId, selected: boolean): void {
85
+ export function setCapabilityValue(controller: OnboardingWizardControllerLike, capabilityId: OnboardingStep1CapabilityId, selected: boolean): void {
86
86
  if (capabilityId === 'local-tui-only') {
87
87
  if (selected) {
88
88
  for (const capability of controller.getCurrentCapabilities()) {
@@ -112,20 +112,20 @@ export function setCapabilityValue(controller: OnboardingWizardController, capab
112
112
  if (!anyServerCapability) controller.toggleState.set('capabilities.local-tui-only', true);
113
113
  }
114
114
 
115
- export function isCapabilitySelected(controller: OnboardingWizardController, capabilityId: OnboardingStep1CapabilityId): boolean {
115
+ export function isCapabilitySelected(controller: OnboardingWizardControllerLike, capabilityId: OnboardingStep1CapabilityId): boolean {
116
116
  return controller.getCapabilitySelectionState().some((capability) => capability.id === capabilityId && capability.selected);
117
117
  }
118
118
 
119
- export function hasServerCapabilitiesSelected(controller: OnboardingWizardController): boolean {
119
+ export function hasServerCapabilitiesSelected(controller: OnboardingWizardControllerLike): boolean {
120
120
  return controller.getCapabilitySelectionState().some((capability) => capability.id !== 'local-tui-only' && capability.selected);
121
121
  }
122
122
 
123
- export function shouldEnableBrowserSurface(controller: OnboardingWizardController): boolean {
123
+ export function shouldEnableBrowserSurface(controller: OnboardingWizardControllerLike): boolean {
124
124
  return controller.hasServerCapabilitiesSelected()
125
125
  && (controller.isCapabilitySelected('browser-access') || controller.isCapabilitySelected('network-access'));
126
126
  }
127
127
 
128
- export function hasSelectedInboundExternalSurface(controller: OnboardingWizardController): boolean {
128
+ export function hasSelectedInboundExternalSurface(controller: OnboardingWizardControllerLike): boolean {
129
129
  if (!controller.isCapabilitySelected('external-integrations')) return false;
130
130
  return EXTERNAL_SURFACE_SPECS.some((surface) => (
131
131
  INBOUND_EXTERNAL_SURFACE_IDS.has(surface.id)
@@ -140,7 +140,7 @@ export function hasSelectedInboundExternalSurface(controller: OnboardingWizardCo
140
140
  ));
141
141
  }
142
142
 
143
- export function isRequiredExternalSetupField(controller: OnboardingWizardController, fieldId: string): boolean {
143
+ export function isRequiredExternalSetupField(controller: OnboardingWizardControllerLike, fieldId: string): boolean {
144
144
  if (!REQUIRED_EXTERNAL_SETUP_FIELD_IDS.has(fieldId)) return false;
145
145
  const surface = getExternalSurfaceSpecByFieldId(fieldId);
146
146
  if (!surface) return false;
@@ -163,7 +163,7 @@ export function isRequiredExternalSetupField(controller: OnboardingWizardControl
163
163
  return true;
164
164
  }
165
165
 
166
- export function getSelectedSecretMedium(controller: OnboardingWizardController): 'secure' | 'plaintext' {
166
+ export function getSelectedSecretMedium(controller: OnboardingWizardControllerLike): 'secure' | 'plaintext' {
167
167
  const policy = controller.getStringFieldValue(
168
168
  'external-services.secret-policy',
169
169
  controller.runtimeSnapshot?.runtimeDefaults.secretStoragePolicy ?? 'preferred_secure',
@@ -174,12 +174,12 @@ export function getSelectedSecretMedium(controller: OnboardingWizardController):
174
174
  return 'plaintext';
175
175
  }
176
176
 
177
- export function shouldEnableHttpListener(controller: OnboardingWizardController): boolean {
177
+ export function shouldEnableHttpListener(controller: OnboardingWizardControllerLike): boolean {
178
178
  return controller.hasServerCapabilitiesSelected()
179
179
  && (controller.isCapabilitySelected('webhook-events') || controller.hasSelectedInboundExternalSurface());
180
180
  }
181
181
 
182
- export function shouldExposeHttpListenerNetworkFields(controller: OnboardingWizardController): boolean {
182
+ export function shouldExposeHttpListenerNetworkFields(controller: OnboardingWizardControllerLike): boolean {
183
183
  return controller.hasServerCapabilitiesSelected()
184
184
  && (
185
185
  controller.isCapabilitySelected('webhook-events')
@@ -188,31 +188,31 @@ export function shouldExposeHttpListenerNetworkFields(controller: OnboardingWiza
188
188
  );
189
189
  }
190
190
 
191
- export function shouldExposeControlPlaneNetwork(controller: OnboardingWizardController): boolean {
191
+ export function shouldExposeControlPlaneNetwork(controller: OnboardingWizardControllerLike): boolean {
192
192
  return controller.hasServerCapabilitiesSelected()
193
193
  && (controller.isCapabilitySelected('browser-access') || controller.isCapabilitySelected('network-access'));
194
194
  }
195
195
 
196
- export function requiresAuthBootstrap(controller: OnboardingWizardController): boolean {
196
+ export function requiresAuthBootstrap(controller: OnboardingWizardControllerLike): boolean {
197
197
  return controller.hasServerCapabilitiesSelected()
198
198
  && (!controller.hasLocalAuthUser() || controller.hasBootstrapCredentialPresent());
199
199
  }
200
200
 
201
- export function hasAdminAuthUser(controller: OnboardingWizardController): boolean {
201
+ export function hasAdminAuthUser(controller: OnboardingWizardControllerLike): boolean {
202
202
  return (controller.runtimeSnapshot?.auth.snapshot.users ?? [])
203
203
  .some((user) => user.roles.includes('admin'));
204
204
  }
205
205
 
206
- export function hasLocalAuthUser(controller: OnboardingWizardController): boolean {
206
+ export function hasLocalAuthUser(controller: OnboardingWizardControllerLike): boolean {
207
207
  return (controller.runtimeSnapshot?.auth.snapshot.userCount ?? 0) > 0
208
208
  || (controller.runtimeSnapshot?.auth.snapshot.users ?? []).length > 0;
209
209
  }
210
210
 
211
- export function hasBootstrapCredentialPresent(controller: OnboardingWizardController): boolean {
211
+ export function hasBootstrapCredentialPresent(controller: OnboardingWizardControllerLike): boolean {
212
212
  return controller.runtimeSnapshot?.auth.snapshot.bootstrapCredentialPresent === true;
213
213
  }
214
214
 
215
- export function getDefaultAdminUsername(controller: OnboardingWizardController): string {
215
+ export function getDefaultAdminUsername(controller: OnboardingWizardControllerLike): string {
216
216
  const users = controller.runtimeSnapshot?.auth.snapshot.users ?? [];
217
217
  if (controller.hasBootstrapCredentialPresent()) {
218
218
  const existingAdmin = users.find((user) => user.roles.includes('admin'));
@@ -225,29 +225,29 @@ export function getDefaultAdminUsername(controller: OnboardingWizardController):
225
225
  return candidate ?? `goodvibes-admin-${users.length + 1}`;
226
226
  }
227
227
 
228
- export function getBooleanFieldValue(controller: OnboardingWizardController, fieldId: string, fallback: boolean): boolean {
228
+ export function getBooleanFieldValue(controller: OnboardingWizardControllerLike, fieldId: string, fallback: boolean): boolean {
229
229
  return controller.toggleState.get(fieldId) ?? fallback;
230
230
  }
231
231
 
232
- export function getStringFieldValue(controller: OnboardingWizardController, fieldId: string, fallback: string): string {
232
+ export function getStringFieldValue(controller: OnboardingWizardControllerLike, fieldId: string, fallback: string): string {
233
233
  const value = controller.textState.get(fieldId) ?? controller.radioState.get(fieldId);
234
234
  return normalizeText(value ?? fallback);
235
235
  }
236
236
 
237
- export function parseIntegerFieldValue(controller: OnboardingWizardController, fieldId: string, fallback: number): number | null {
237
+ export function parseIntegerFieldValue(controller: OnboardingWizardControllerLike, fieldId: string, fallback: number): number | null {
238
238
  const raw = controller.getStringFieldValue(fieldId, String(fallback));
239
239
  if (!/^-?\d+$/.test(raw)) return null;
240
240
  const parsed = Number.parseInt(raw, 10);
241
241
  return Number.isInteger(parsed) ? parsed : null;
242
242
  }
243
243
 
244
- export function getPortFieldValue(controller: OnboardingWizardController, fieldId: string, fallback: number): number {
244
+ export function getPortFieldValue(controller: OnboardingWizardControllerLike, fieldId: string, fallback: number): number {
245
245
  const parsed = controller.parseIntegerFieldValue(fieldId, fallback);
246
246
  if (parsed === null || parsed < 1 || parsed > 65535) return fallback;
247
247
  return parsed;
248
248
  }
249
249
 
250
- export function getNumberFieldValue(controller: OnboardingWizardController, fieldId: string, fallback: number, min?: number, max?: number): number {
250
+ export function getNumberFieldValue(controller: OnboardingWizardControllerLike, fieldId: string, fallback: number, min?: number, max?: number): number {
251
251
  const parsed = controller.parseIntegerFieldValue(fieldId, fallback);
252
252
  if (parsed === null) return fallback;
253
253
  if (min !== undefined && parsed < min) return fallback;
@@ -2,16 +2,16 @@ import type { ModelPickerTarget } from '../model-picker.ts';
2
2
  import type { OnboardingStep1CapabilityItem } from '../../runtime/onboarding/index.ts';
3
3
  import { DEFAULT_CAPABILITIES, NETWORK_HOST_FIELD_IDS } from './onboarding-wizard-constants.ts';
4
4
  import { areSelectionsEqual, clamp, cloneSelection, getExternalSurfaceSetupFieldSpec, isMalformedGoodVibesSecretReferenceValue, isValidHostValue, normalizeText } from './onboarding-wizard-helpers.ts';
5
- import type { OnboardingWizardController } from './onboarding-wizard.ts';
5
+ import type { OnboardingWizardControllerLike } from './onboarding-wizard-types.ts';
6
6
  import type { OnboardingWizardFieldDefinition, OnboardingWizardModelSelection, OnboardingWizardStepDefinition } from './onboarding-wizard-types.ts';
7
7
 
8
- export function getToggleFieldCount(controller: OnboardingWizardController, stepIndex: number): number {
8
+ export function getToggleFieldCount(controller: OnboardingWizardControllerLike, stepIndex: number): number {
9
9
  const step = controller.steps[stepIndex];
10
10
  if (!step) return 0;
11
11
  return step.fields.filter((field) => field.kind === 'checklist' || field.kind === 'acknowledgement').length;
12
12
  }
13
13
 
14
- export function getCompletedToggleCount(controller: OnboardingWizardController, stepIndex: number): number {
14
+ export function getCompletedToggleCount(controller: OnboardingWizardControllerLike, stepIndex: number): number {
15
15
  const step = controller.steps[stepIndex];
16
16
  if (!step) return 0;
17
17
 
@@ -21,27 +21,27 @@ export function getCompletedToggleCount(controller: OnboardingWizardController,
21
21
  )).length;
22
22
  }
23
23
 
24
- export function getStepFieldCount(controller: OnboardingWizardController, stepIndex: number): number {
24
+ export function getStepFieldCount(controller: OnboardingWizardControllerLike, stepIndex: number): number {
25
25
  return controller.steps[stepIndex]?.fields.length ?? 0;
26
26
  }
27
27
 
28
- export function getCompletedFieldCount(controller: OnboardingWizardController, stepIndex: number): number {
28
+ export function getCompletedFieldCount(controller: OnboardingWizardControllerLike, stepIndex: number): number {
29
29
  const step = controller.steps[stepIndex];
30
30
  if (!step) return 0;
31
31
  return step.fields.filter((field) => controller.isFieldSatisfied(field)).length;
32
32
  }
33
33
 
34
- export function isStepDirty(controller: OnboardingWizardController, stepIndex: number): boolean {
34
+ export function isStepDirty(controller: OnboardingWizardControllerLike, stepIndex: number): boolean {
35
35
  const stepId = controller.steps[stepIndex]?.id;
36
36
  return stepId ? controller.dirtyStepIds.has(stepId) : false;
37
37
  }
38
38
 
39
- export function isFieldDirty(controller: OnboardingWizardController, fieldId: string): boolean {
39
+ export function isFieldDirty(controller: OnboardingWizardControllerLike, fieldId: string): boolean {
40
40
  const field = controller.getFieldById(fieldId);
41
41
  return field ? controller.isFieldDirtyByDefinition(field) : false;
42
42
  }
43
43
 
44
- export function getBlockingFieldLabels(controller: OnboardingWizardController): readonly string[] {
44
+ export function getBlockingFieldLabels(controller: OnboardingWizardControllerLike): readonly string[] {
45
45
  const labels: string[] = [];
46
46
  if (controller.hydrationPending) {
47
47
  labels.push('Loading: Current runtime settings are still being collected.');
@@ -65,7 +65,7 @@ export function getBlockingFieldLabels(controller: OnboardingWizardController):
65
65
  }
66
66
 
67
67
  export function getFieldValidationError(
68
- controller: OnboardingWizardController,
68
+ controller: OnboardingWizardControllerLike,
69
69
  step: OnboardingWizardStepDefinition,
70
70
  field: OnboardingWizardFieldDefinition,
71
71
  ): string | null {
@@ -127,7 +127,7 @@ export function getFieldValidationError(
127
127
  return null;
128
128
  }
129
129
 
130
- export function getFieldById(controller: OnboardingWizardController, fieldId: string): OnboardingWizardFieldDefinition | null {
130
+ export function getFieldById(controller: OnboardingWizardControllerLike, fieldId: string): OnboardingWizardFieldDefinition | null {
131
131
  for (const step of controller.steps) {
132
132
  const field = step.fields.find((entry) => entry.id === fieldId);
133
133
  if (field) return field;
@@ -135,7 +135,7 @@ export function getFieldById(controller: OnboardingWizardController, fieldId: st
135
135
  return null;
136
136
  }
137
137
 
138
- export function ensureSelectionVisible(controller: OnboardingWizardController, visibleFields: number): void {
138
+ export function ensureSelectionVisible(controller: OnboardingWizardControllerLike, visibleFields: number): void {
139
139
  const total = controller.currentStep.fields.length;
140
140
  if (total === 0) {
141
141
  controller.scrollOffsets[controller.stepIndex] = 0;
@@ -154,7 +154,7 @@ export function ensureSelectionVisible(controller: OnboardingWizardController, v
154
154
  controller.scrollOffsets[controller.stepIndex] = clamp(nextOffset, 0, maxStart);
155
155
  }
156
156
 
157
- export function reconcileStepCursor(controller: OnboardingWizardController, stepIndex: number): void {
157
+ export function reconcileStepCursor(controller: OnboardingWizardControllerLike, stepIndex: number): void {
158
158
  const total = controller.steps[stepIndex]?.fields.length ?? 0;
159
159
  if (total === 0) {
160
160
  controller.scrollOffsets[stepIndex] = 0;
@@ -166,7 +166,7 @@ export function reconcileStepCursor(controller: OnboardingWizardController, step
166
166
  controller.scrollOffsets[stepIndex] = clamp(controller.scrollOffsets[stepIndex] ?? 0, 0, total - 1);
167
167
  }
168
168
 
169
- export function resetValuesFromCurrentDefinitions(controller: OnboardingWizardController): void {
169
+ export function resetValuesFromCurrentDefinitions(controller: OnboardingWizardControllerLike): void {
170
170
  controller.toggleState.clear();
171
171
  controller.baselineToggleState.clear();
172
172
  controller.radioState.clear();
@@ -213,7 +213,7 @@ export function resetValuesFromCurrentDefinitions(controller: OnboardingWizardCo
213
213
  }
214
214
  }
215
215
 
216
- export function reconcileStateWithCurrentDefinitions(controller: OnboardingWizardController): void {
216
+ export function reconcileStateWithCurrentDefinitions(controller: OnboardingWizardControllerLike): void {
217
217
  const nextToggleKeys = new Set<string>();
218
218
  const nextRadioKeys = new Set<string>();
219
219
  const nextTextKeys = new Set<string>();
@@ -282,7 +282,7 @@ export function reconcileStateWithCurrentDefinitions(controller: OnboardingWizar
282
282
  }
283
283
  }
284
284
 
285
- export function recalculateDirtyState(controller: OnboardingWizardController): void {
285
+ export function recalculateDirtyState(controller: OnboardingWizardControllerLike): void {
286
286
  controller.reconcileStateWithCurrentDefinitions();
287
287
  controller.dirtyStepIds.clear();
288
288
 
@@ -293,7 +293,7 @@ export function recalculateDirtyState(controller: OnboardingWizardController): v
293
293
  }
294
294
  }
295
295
 
296
- export function isFieldDirtyByDefinition(controller: OnboardingWizardController, field: OnboardingWizardFieldDefinition): boolean {
296
+ export function isFieldDirtyByDefinition(controller: OnboardingWizardControllerLike, field: OnboardingWizardFieldDefinition): boolean {
297
297
  if (field.kind === 'checklist' || field.kind === 'acknowledgement') {
298
298
  return (controller.toggleState.get(field.id) ?? field.defaultValue)
299
299
  !== (controller.baselineToggleState.get(field.id) ?? field.defaultValue);
@@ -317,7 +317,7 @@ export function isFieldDirtyByDefinition(controller: OnboardingWizardController,
317
317
  );
318
318
  }
319
319
 
320
- export function isFieldSatisfied(controller: OnboardingWizardController, field: OnboardingWizardFieldDefinition): boolean {
320
+ export function isFieldSatisfied(controller: OnboardingWizardControllerLike, field: OnboardingWizardFieldDefinition): boolean {
321
321
  if (field.kind === 'checklist') {
322
322
  return true;
323
323
  }
@@ -341,20 +341,20 @@ export function isFieldSatisfied(controller: OnboardingWizardController, field:
341
341
  return selection.providerId.length > 0 || selection.modelId.length > 0;
342
342
  }
343
343
 
344
- export function getCurrentCapabilities(controller: OnboardingWizardController): readonly OnboardingStep1CapabilityItem[] {
344
+ export function getCurrentCapabilities(controller: OnboardingWizardControllerLike): readonly OnboardingStep1CapabilityItem[] {
345
345
  return controller.runtimeDerived.step1Capabilities.length > 0
346
346
  ? controller.runtimeDerived.step1Capabilities
347
347
  : DEFAULT_CAPABILITIES;
348
348
  }
349
349
 
350
- export function getCapabilitySelectionState(controller: OnboardingWizardController): readonly OnboardingStep1CapabilityItem[] {
350
+ export function getCapabilitySelectionState(controller: OnboardingWizardControllerLike): readonly OnboardingStep1CapabilityItem[] {
351
351
  return controller.getCurrentCapabilities().map((capability) => ({
352
352
  ...capability,
353
353
  selected: controller.toggleState.get(`capabilities.${capability.id}`) ?? capability.selected,
354
354
  }));
355
355
  }
356
356
 
357
- export function hasExistingAccessState(controller: OnboardingWizardController): boolean {
357
+ export function hasExistingAccessState(controller: OnboardingWizardControllerLike): boolean {
358
358
  const auth = controller.runtimeSnapshot?.auth.snapshot;
359
359
  return controller.mode !== 'new'
360
360
  || (controller.runtimeSnapshot?.subscriptions.active.length ?? 0) > 0
@@ -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();
@@ -636,7 +636,7 @@ export function buildNetworkStep(controller: OnboardingWizardController): Onboar
636
636
  };
637
637
  }
638
638
 
639
- export function buildAccountsStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
639
+ export function buildAccountsStep(controller: OnboardingWizardControllerLike): OnboardingWizardStepDefinition {
640
640
  const subscriptionsAck = controller.runtimeDerived.reopenEditAcknowledgements.subscriptions;
641
641
  const authAck = controller.runtimeDerived.reopenEditAcknowledgements.auth;
642
642
  const auth = controller.runtimeSnapshot?.auth.snapshot;
@@ -740,7 +740,7 @@ export function buildAccountsStep(controller: OnboardingWizardController): Onboa
740
740
  };
741
741
  }
742
742
 
743
- export function buildReviewStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
743
+ export function buildReviewStep(controller: OnboardingWizardControllerLike): OnboardingWizardStepDefinition {
744
744
  const feedback = controller.applyFeedback;
745
745
  const feedbackFields: OnboardingWizardFieldDefinition[] = feedback
746
746
  ? [
@@ -778,19 +778,12 @@ export function buildReviewStep(controller: OnboardingWizardController): Onboard
778
778
  ],
779
779
  fields: [
780
780
  ...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
781
  {
789
782
  kind: 'action',
790
783
  id: 'review.apply',
791
784
  action: 'apply',
792
785
  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.',
786
+ hint: 'Persist the wizard settings and verify the resulting runtime state. The global onboarding check marker is recorded when apply succeeds.',
794
787
  defaultValue: 'Ready',
795
788
  },
796
789
  ],