@pellux/goodvibes-tui 0.19.27 → 0.19.29

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 (41) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +3 -1
  3. package/docs/foundation-artifacts/operator-contract.json +1 -1
  4. package/package.json +2 -2
  5. package/src/cli/bundle-command.ts +3 -2
  6. package/src/cli/entrypoint.ts +2 -2
  7. package/src/cli/help.ts +1 -1
  8. package/src/cli/status.ts +9 -9
  9. package/src/cli/surface-command.ts +46 -11
  10. package/src/cli/tui-startup.ts +4 -4
  11. package/src/daemon/cli.ts +7 -0
  12. package/src/input/handler-interactions.ts +14 -1
  13. package/src/input/handler-onboarding.ts +161 -118
  14. package/src/input/handler.ts +1 -1
  15. package/src/input/onboarding/handler-onboarding-routes.ts +35 -15
  16. package/src/input/onboarding/onboarding-wizard-apply.ts +35 -25
  17. package/src/input/onboarding/onboarding-wizard-constants.ts +4 -5
  18. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +93 -5
  19. package/src/input/onboarding/onboarding-wizard-helpers.ts +2 -3
  20. package/src/input/onboarding/onboarding-wizard-rules.ts +40 -8
  21. package/src/input/onboarding/onboarding-wizard-state.ts +19 -8
  22. package/src/input/onboarding/onboarding-wizard-steps.ts +226 -93
  23. package/src/input/onboarding/onboarding-wizard-types.ts +15 -0
  24. package/src/input/onboarding/onboarding-wizard.ts +123 -6
  25. package/src/input/settings-modal-types.ts +2 -1
  26. package/src/input/settings-modal.ts +4 -0
  27. package/src/main.ts +35 -27
  28. package/src/renderer/compositor.ts +3 -3
  29. package/src/renderer/onboarding/onboarding-wizard.ts +141 -57
  30. package/src/renderer/settings-modal-helpers.ts +9 -0
  31. package/src/renderer/settings-modal.ts +3 -0
  32. package/src/runtime/bootstrap.ts +15 -0
  33. package/src/runtime/onboarding/apply.ts +45 -90
  34. package/src/runtime/onboarding/derivation.ts +7 -7
  35. package/src/runtime/onboarding/markers.ts +41 -55
  36. package/src/runtime/onboarding/snapshot.ts +1 -0
  37. package/src/runtime/onboarding/state.ts +6 -6
  38. package/src/runtime/onboarding/types.ts +24 -27
  39. package/src/runtime/onboarding/verify.ts +3 -65
  40. package/src/runtime/surface-feature-flags.ts +67 -0
  41. package/src/version.ts +1 -1
@@ -1,8 +1,14 @@
1
1
  import { NETWORK_MODE_OPTIONS, REASONING_OPTIONS, HITL_MODE_OPTIONS, GUIDANCE_MODE_OPTIONS, PERMISSION_MODE_OPTIONS, SECRET_POLICY_OPTIONS } from './onboarding-wizard-constants.ts';
2
- import { EXTERNAL_SURFACE_SPECS } from './onboarding-wizard-external-surfaces.ts';
2
+ import {
3
+ EXTERNAL_SURFACE_SPECS,
4
+ getExternalSurfaceAutoStartDefaultValue,
5
+ getExternalSurfaceAutoStartFieldId,
6
+ isExternalSurfaceSelectedByDefault,
7
+ type ExternalSurfaceSpec,
8
+ } from './onboarding-wizard-external-surfaces.ts';
3
9
  import { countSelected, modelSelectionLabel, normalizeText } from './onboarding-wizard-helpers.ts';
4
10
  import type { OnboardingWizardController } from './onboarding-wizard.ts';
5
- import type { OnboardingWizardAcknowledgementFieldDefinition, OnboardingWizardChecklistFieldDefinition, OnboardingWizardFieldDefinition, OnboardingWizardModelPickerFieldDefinition, OnboardingWizardRadioFieldDefinition, OnboardingWizardStepDefinition } from './onboarding-wizard-types.ts';
11
+ import type { OnboardingWizardAcknowledgementFieldDefinition, OnboardingWizardChecklistFieldDefinition, OnboardingWizardExternalSurfaceStepId, OnboardingWizardFieldDefinition, OnboardingWizardModelPickerFieldDefinition, OnboardingWizardRadioFieldDefinition, OnboardingWizardRadioOption, OnboardingWizardStepDefinition } from './onboarding-wizard-types.ts';
6
12
 
7
13
  export function buildOnboardingWizardSteps(controller: OnboardingWizardController): readonly OnboardingWizardStepDefinition[] {
8
14
  if (controller.hydrationPending || controller.hydrationError !== null) return [buildLoadingStep(controller)];
@@ -24,6 +30,9 @@ export function buildOnboardingWizardSteps(controller: OnboardingWizardControlle
24
30
 
25
31
  if (wantsExternalServices) {
26
32
  steps.push(buildExternalServicesStep(controller));
33
+ for (const surface of getSelectedExternalSurfaceSpecs(controller)) {
34
+ steps.push(buildExternalSurfaceStep(controller, surface));
35
+ }
27
36
  }
28
37
 
29
38
  steps.push(buildProviderAccessStep(controller));
@@ -83,7 +92,7 @@ export function buildCapabilitiesStep(controller: OnboardingWizardController): O
83
92
  id: 'capabilities.select-all',
84
93
  action: 'select-all-capabilities',
85
94
  label: 'Select all server-backed capabilities',
86
- hint: 'Enable browser access, other-device LAN access, webhooks/events, and external integrations. Local TUI Only is turned off.',
95
+ hint: 'Enable browser access, LAN reachability, webhooks/events, and external app surfaces. Local TUI Only is turned off.',
87
96
  defaultValue: 'Action',
88
97
  },
89
98
  {
@@ -98,9 +107,9 @@ export function buildCapabilitiesStep(controller: OnboardingWizardController): O
98
107
 
99
108
  return {
100
109
  id: 'capabilities',
101
- title: 'What should GoodVibes be able to do?',
110
+ title: 'Choose GoodVibes capabilities',
102
111
  shortLabel: 'Capabilities',
103
- description: 'Choose the features this install should enable. Local TUI Only is selected when no server-backed capabilities are enabled.',
112
+ description: 'Choose what GoodVibes should be able to do. Local TUI Only avoids servers; any other choice enables service mode and autostart.',
104
113
  summaryTitle: 'Selected capabilities',
105
114
  summaryLines: [
106
115
  `${selectedCount}/${capabilities.length} option(s) selected`,
@@ -263,68 +272,169 @@ export function buildDefaultModelStep(controller: OnboardingWizardController): O
263
272
  }
264
273
 
265
274
  export function buildExternalServicesStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
266
- const enabledCount = EXTERNAL_SURFACE_SPECS
267
- .filter((surface) => controller.getBooleanFieldValue(surface.enabledFieldId, surface.defaultEnabled(controller.runtimeSnapshot)))
275
+ const selectedCount = EXTERNAL_SURFACE_SPECS
276
+ .filter((surface) => controller.getBooleanFieldValue(
277
+ surface.enabledFieldId,
278
+ isExternalSurfaceSelectedByDefault(surface, controller.runtimeSnapshot),
279
+ ))
268
280
  .length;
269
281
  const fields: OnboardingWizardFieldDefinition[] = [];
270
282
 
271
283
  for (const surface of EXTERNAL_SURFACE_SPECS) {
272
- const enabled = controller.getBooleanFieldValue(surface.enabledFieldId, surface.defaultEnabled(controller.runtimeSnapshot));
273
284
  fields.push({
274
285
  kind: 'checklist',
275
286
  id: surface.enabledFieldId,
276
287
  label: surface.label,
277
- hint: surface.hint,
278
- defaultValue: surface.defaultEnabled(controller.runtimeSnapshot),
288
+ hint: `${surface.hint} Selecting this opens a dedicated setup screen; auto-start is chosen on that screen.`,
289
+ defaultValue: isExternalSurfaceSelectedByDefault(surface, controller.runtimeSnapshot),
279
290
  });
291
+ }
280
292
 
281
- if (!enabled) continue;
282
- for (const setupField of surface.fields) {
283
- if (setupField.kind === 'radio') {
284
- fields.push({
285
- kind: 'radio',
286
- id: setupField.id,
287
- label: setupField.label,
288
- hint: setupField.hint,
289
- options: setupField.options ?? [],
290
- defaultValue: setupField.defaultValue(controller.runtimeSnapshot),
291
- });
292
- continue;
293
+ fields.push(
294
+ {
295
+ kind: 'action',
296
+ id: 'external-services.select-all',
297
+ action: 'select-all-external-surfaces',
298
+ label: 'Select all external surfaces',
299
+ hint: 'Show setup screens for every supported external surface. Auto-start stays controlled per surface.',
300
+ defaultValue: 'Action',
301
+ },
302
+ {
303
+ kind: 'action',
304
+ id: 'external-services.clear',
305
+ action: 'clear-external-surfaces',
306
+ label: 'Clear all external surfaces',
307
+ hint: 'Hide all external surface setup screens. The HTTP listener can still be enabled separately by webhook/event capabilities.',
308
+ defaultValue: 'Action',
309
+ },
310
+ {
311
+ kind: 'radio',
312
+ id: 'external-services.secret-policy',
313
+ label: 'Secret storage policy',
314
+ hint: 'Choose how selected surface secrets should be stored. Secret values are never shown in the wizard.',
315
+ options: SECRET_POLICY_OPTIONS,
316
+ defaultValue: controller.runtimeSnapshot?.runtimeDefaults.secretStoragePolicy ?? 'preferred_secure',
317
+ },
318
+ );
319
+
320
+ return {
321
+ id: 'external-services',
322
+ title: 'Choose external surfaces',
323
+ shortLabel: 'Services',
324
+ description: 'Select the apps and integration surfaces GoodVibes should prepare. Each selected surface gets its own setup screen and its own auto-start choice.',
325
+ summaryTitle: 'External surfaces',
326
+ summaryLines: [
327
+ `${selectedCount} external surface(s) selected for setup`,
328
+ `Secret policy: ${controller.getStringFieldValue('external-services.secret-policy', controller.runtimeSnapshot?.runtimeDefaults.secretStoragePolicy ?? 'preferred_secure')}`,
329
+ selectedCount > 0 ? 'Selected surfaces appear as separate setup screens.' : 'No external surfaces selected.',
330
+ ],
331
+ fields,
332
+ };
333
+ }
334
+
335
+ function getSelectedExternalSurfaceSpecs(controller: OnboardingWizardController): readonly ExternalSurfaceSpec[] {
336
+ return EXTERNAL_SURFACE_SPECS.filter((surface) => (
337
+ controller.getBooleanFieldValue(
338
+ surface.enabledFieldId,
339
+ isExternalSurfaceSelectedByDefault(surface, controller.runtimeSnapshot),
340
+ )
341
+ ));
342
+ }
343
+
344
+ const SURFACE_AUTO_START_OPTIONS: readonly OnboardingWizardRadioOption[] = [
345
+ {
346
+ id: 'yes',
347
+ label: 'Yes',
348
+ hint: 'Start this surface automatically when the GoodVibes service starts.',
349
+ },
350
+ {
351
+ id: 'no',
352
+ label: 'No',
353
+ hint: 'Save these settings but leave the surface idle until it is enabled from Settings > Surfaces.',
354
+ },
355
+ ];
356
+
357
+ function buildExternalSurfaceStep(
358
+ controller: OnboardingWizardController,
359
+ surface: ExternalSurfaceSpec,
360
+ ): OnboardingWizardStepDefinition {
361
+ let setupCount = 0;
362
+ let setupCompleteCount = 0;
363
+ const autoStartFieldId = getExternalSurfaceAutoStartFieldId(surface);
364
+ const autoStartDefault = getExternalSurfaceAutoStartDefaultValue(surface, controller.runtimeSnapshot);
365
+ const autoStartValue = controller.getStringFieldValue(autoStartFieldId, autoStartDefault);
366
+ const setupFields = surface.fields.map((setupField): OnboardingWizardFieldDefinition => {
367
+ const suggested = controller.isRequiredExternalSetupField(setupField.id);
368
+ if (suggested) {
369
+ setupCount += 1;
370
+ if (normalizeText(setupField.defaultValue(controller.runtimeSnapshot)).length > 0
371
+ || normalizeText(controller.getStringFieldValue(setupField.id, '')).length > 0) {
372
+ setupCompleteCount += 1;
293
373
  }
374
+ }
294
375
 
295
- fields.push({
296
- kind: setupField.kind,
376
+ const hint = suggested
377
+ ? `${setupField.hint} Recommended because ${surface.label} is selected, but it will not block saving.`
378
+ : setupField.hint;
379
+
380
+ if (setupField.kind === 'radio') {
381
+ return {
382
+ kind: 'radio',
297
383
  id: setupField.id,
298
384
  label: setupField.label,
299
- hint: setupField.hint,
300
- placeholder: setupField.placeholder,
385
+ hint,
386
+ options: setupField.options ?? [],
301
387
  defaultValue: setupField.defaultValue(controller.runtimeSnapshot),
302
- required: controller.isRequiredExternalSetupField(setupField.id),
303
- });
388
+ };
304
389
  }
305
- }
306
390
 
307
- fields.push({
308
- kind: 'radio',
309
- id: 'external-services.secret-policy',
310
- label: 'Secret storage policy',
311
- hint: 'Choose how external integration secrets should be stored.',
312
- options: SECRET_POLICY_OPTIONS,
313
- defaultValue: controller.runtimeSnapshot?.runtimeDefaults.secretStoragePolicy ?? 'preferred_secure',
391
+ return {
392
+ kind: setupField.kind,
393
+ id: setupField.id,
394
+ label: setupField.label,
395
+ hint,
396
+ placeholder: setupField.placeholder,
397
+ defaultValue: setupField.defaultValue(controller.runtimeSnapshot),
398
+ };
314
399
  });
400
+ const ntfyTopicSummary = surface.id === 'ntfy'
401
+ ? [
402
+ `Chat topic: ${controller.getStringFieldValue('external-services.ntfy.chat-topic', 'goodvibes-chat')}`,
403
+ `Agent topic: ${controller.getStringFieldValue('external-services.ntfy.agent-topic', 'goodvibes-agent')}`,
404
+ `Daemon-only remote topic: ${controller.getStringFieldValue('external-services.ntfy.remote-topic', 'goodvibes-ntfy')}`,
405
+ ]
406
+ : [];
407
+ const title = `${surface.label.replace(/ surface$/i, '')} setup`;
408
+ const setupSummary = setupCount === 0
409
+ ? 'Suggested setup: none'
410
+ : `Suggested setup entered: ${setupCompleteCount}/${setupCount}`;
315
411
 
316
412
  return {
317
- id: 'external-services',
318
- title: 'External apps and services',
319
- shortLabel: 'Services',
320
- description: 'Select each external surface this install should prepare and enter its setup values directly in the wizard. Sensitive fields remain masked.',
321
- summaryTitle: 'External service summary',
413
+ id: `external-surface:${surface.id}` as OnboardingWizardExternalSurfaceStepId,
414
+ title,
415
+ shortLabel: surface.label.replace(/ surface$/i, ''),
416
+ description: `Configure ${surface.label}. Settings are saved either way; auto-start controls whether this surface starts with the background service.`,
417
+ summaryTitle: `${surface.label} setup`,
322
418
  summaryLines: [
323
- `${enabledCount} external surface(s) selected`,
419
+ `Auto-start: ${autoStartValue === 'yes' ? 'yes' : 'no'}`,
420
+ ...ntfyTopicSummary,
421
+ setupSummary,
324
422
  `Secret policy: ${controller.getStringFieldValue('external-services.secret-policy', controller.runtimeSnapshot?.runtimeDefaults.secretStoragePolicy ?? 'preferred_secure')}`,
325
- 'Secrets remain masked and policy-controlled.',
423
+ autoStartValue === 'yes'
424
+ ? 'The surface will be enabled after apply.'
425
+ : 'Start it later from Settings > Surfaces by turning Enabled on.',
426
+ ],
427
+ fields: [
428
+ {
429
+ kind: 'radio',
430
+ id: autoStartFieldId,
431
+ label: 'Auto-start this surface',
432
+ hint: `Yes turns on ${surface.enabledConfigKey}. No saves setup values but keeps the surface off until Settings > Surfaces enables it.`,
433
+ options: SURFACE_AUTO_START_OPTIONS,
434
+ defaultValue: autoStartDefault,
435
+ },
436
+ ...setupFields,
326
437
  ],
327
- fields,
328
438
  };
329
439
  }
330
440
 
@@ -510,34 +620,36 @@ export function buildAccountsStep(controller: OnboardingWizardController): Onboa
510
620
  const needsAuthBootstrap = controller.requiresAuthBootstrap();
511
621
  const needsExistingAuthAcknowledgement = controller.hasServerCapabilitiesSelected()
512
622
  && !needsAuthBootstrap
513
- && controller.hasAdminAuthUser();
623
+ && controller.hasLocalAuthUser();
514
624
  const fields: OnboardingWizardFieldDefinition[] = [];
625
+ const defaultAdminUsername = controller.getDefaultAdminUsername();
515
626
 
516
- if (needsAuthBootstrap) {
517
- const defaultAdminUsername = controller.getDefaultAdminUsername();
518
- fields.push(
519
- {
520
- kind: 'text',
521
- id: 'accounts.admin-username',
522
- label: 'Local auth admin username',
523
- hint: 'Required before any background service, browser surface, or listener is exposed.',
524
- placeholder: defaultAdminUsername,
525
- defaultValue: defaultAdminUsername,
526
- required: true,
527
- },
528
- {
529
- kind: 'masked',
530
- id: 'accounts.admin-password',
531
- label: 'Local auth admin password',
532
- hint: controller.hasBootstrapCredentialPresent()
533
- ? 'Creates a new local admin, removes the bootstrap credential file, and retires the bootstrap admin before LAN/server settings are applied.'
534
- : 'Creates the first local admin user and an initial session before LAN/server settings are applied.',
535
- placeholder: 'password required',
536
- defaultValue: '',
537
- required: true,
538
- },
539
- );
540
- }
627
+ fields.push(
628
+ {
629
+ kind: 'text',
630
+ id: 'accounts.admin-username',
631
+ label: 'Local auth admin username',
632
+ hint: needsAuthBootstrap
633
+ ? 'Required before any background service, browser surface, or listener is exposed.'
634
+ : 'Optional. Enter an existing admin username to rotate its password, or a new username to create another admin.',
635
+ placeholder: defaultAdminUsername,
636
+ defaultValue: defaultAdminUsername,
637
+ required: needsAuthBootstrap,
638
+ },
639
+ {
640
+ kind: 'masked',
641
+ id: 'accounts.admin-password',
642
+ label: 'Local auth admin password',
643
+ hint: needsAuthBootstrap
644
+ ? controller.hasBootstrapCredentialPresent()
645
+ ? 'Creates or updates the named local admin, removes the bootstrap credential file, and retires the bootstrap admin when it is a different user.'
646
+ : 'Creates the first local admin user and an initial session before LAN/server settings are applied.'
647
+ : 'Optional. Leave blank to keep existing local auth unchanged; enter a password to create or rotate the named admin user.',
648
+ placeholder: needsAuthBootstrap ? 'password required' : 'leave blank to keep unchanged',
649
+ defaultValue: '',
650
+ required: needsAuthBootstrap,
651
+ },
652
+ );
541
653
 
542
654
  fields.push(
543
655
  {
@@ -563,13 +675,17 @@ export function buildAccountsStep(controller: OnboardingWizardController): Onboa
563
675
  {
564
676
  kind: 'status',
565
677
  id: 'accounts.bootstrap',
566
- label: 'Bootstrap credential hint',
678
+ label: 'Local auth readiness',
567
679
  hint: needsAuthBootstrap
568
680
  ? 'The wizard will create local auth before applying network-accessible settings.'
569
- : 'Masked auth state stays visible without leaking sensitive values.',
681
+ : controller.hasAdminAuthUser()
682
+ ? 'An existing local auth admin user was detected and will be kept.'
683
+ : controller.hasLocalAuthUser()
684
+ ? 'Existing local auth users were detected and will be kept.'
685
+ : 'No server-backed capability is selected, so local auth is not required.',
570
686
  defaultValue: needsAuthBootstrap
571
687
  ? controller.hasBootstrapCredentialPresent() ? 'Bootstrap replacement required' : 'Local admin required'
572
- : auth?.bootstrapCredentialPresent ? 'Configured' : 'Not detected',
688
+ : controller.hasAdminAuthUser() ? 'Admin detected' : controller.hasLocalAuthUser() ? 'Local auth detected' : 'Not required',
573
689
  },
574
690
  {
575
691
  kind: 'status',
@@ -586,7 +702,7 @@ export function buildAccountsStep(controller: OnboardingWizardController): Onboa
586
702
  shortLabel: 'Accounts',
587
703
  description: needsAuthBootstrap
588
704
  ? 'Create wizard-owned local auth before any LAN, browser, service, or listener settings are applied.'
589
- : 'Require explicit acknowledgement for existing subscription or local-auth state when reopening the wizard in edit mode.',
705
+ : 'Review existing subscription and local auth state. Existing local auth is kept unless you change it elsewhere.',
590
706
  summaryTitle: 'Stored account state',
591
707
  summaryLines: [
592
708
  `Subscriptions: ${controller.runtimeSnapshot?.subscriptions.active.length ?? 0} active / ${controller.runtimeSnapshot?.subscriptions.pending.length ?? 0} pending`,
@@ -595,46 +711,63 @@ export function buildAccountsStep(controller: OnboardingWizardController): Onboa
595
711
  ? controller.hasBootstrapCredentialPresent()
596
712
  ? 'Bootstrap credentials will be replaced before network settings are applied'
597
713
  : 'Local admin will be created before network settings are applied'
598
- : auth?.bootstrapCredentialPresent ? 'Bootstrap credential file present' : 'No bootstrap credential file detected',
714
+ : controller.hasLocalAuthUser() ? 'Existing local auth will be kept' : 'Local auth is not required for this setup',
599
715
  ],
600
716
  fields,
601
717
  };
602
718
  }
603
719
 
604
720
  export function buildReviewStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
721
+ const feedback = controller.applyFeedback;
722
+ const feedbackFields: OnboardingWizardFieldDefinition[] = feedback
723
+ ? [
724
+ {
725
+ kind: 'status',
726
+ id: 'review.feedback',
727
+ label: feedback.title,
728
+ hint: feedback.summary,
729
+ defaultValue: feedback.severity === 'error' ? 'Needs attention' : feedback.severity === 'warning' ? 'Warning' : 'Info',
730
+ },
731
+ ...feedback.messages.slice(0, 8).map((message, index): OnboardingWizardFieldDefinition => ({
732
+ kind: 'status',
733
+ id: `review.feedback.${index}`,
734
+ label: message,
735
+ hint: message,
736
+ defaultValue: feedback.severity === 'error' ? 'Error' : feedback.severity === 'warning' ? 'Warning' : 'Info',
737
+ })),
738
+ ]
739
+ : [];
740
+ const unsavedLabel = controller.dirtyStepCount === 1
741
+ ? '1 screen has unapplied changes'
742
+ : `${controller.dirtyStepCount} screens have unapplied changes`;
743
+
605
744
  return {
606
745
  id: 'review',
607
- title: 'Review and completion',
746
+ title: 'Review and apply',
608
747
  shortLabel: 'Review',
609
748
  description: 'Review the selected settings and apply them directly from the wizard.',
610
749
  summaryTitle: 'Review posture',
611
750
  summaryLines: [
612
- `${controller.dirtyStepCount} dirty step(s)`,
613
- `${controller.buildApplyRequest().operations.length} operation(s) ready to apply`,
614
- `Pending picker: ${controller.pendingModelPickerTarget ?? 'none'}`,
615
- controller.isEditingTextField() ? `Editing: ${controller.editingFieldId}` : 'Ready for apply/verify',
751
+ unsavedLabel,
752
+ `${controller.buildApplyRequest().operations.length} settings change(s) ready to apply`,
753
+ feedback ? `Last apply: ${feedback.title}` : 'No apply errors reported',
754
+ controller.isEditingTextField() ? `Editing: ${controller.editingFieldId}` : 'Ready to apply',
616
755
  ],
617
756
  fields: [
757
+ ...feedbackFields,
618
758
  {
619
- kind: 'checklist',
620
- id: 'review.project-marker',
621
- label: 'Write project completion marker',
622
- hint: 'Project scope keeps the workspace-specific completion state close to the repo.',
623
- defaultValue: true,
624
- },
625
- {
626
- kind: 'checklist',
627
- id: 'review.user-marker',
628
- label: 'Write user completion marker',
629
- hint: 'User scope keeps the shell-level completion marker available outside this workspace.',
630
- defaultValue: controller.defaultReviewUserMarker(),
759
+ kind: 'status',
760
+ id: 'review.global-marker',
761
+ label: 'Global onboarding check',
762
+ hint: 'Opening this wizard marks onboarding as shown for this user account, so new projects do not reopen it automatically.',
763
+ defaultValue: 'Already marked as shown',
631
764
  },
632
765
  {
633
766
  kind: 'action',
634
767
  id: 'review.apply',
635
768
  action: 'apply',
636
769
  label: 'Apply settings and verify',
637
- hint: 'Persist the wizard settings, write completion markers, and verify the resulting runtime state.',
770
+ hint: 'Persist the wizard settings and verify the resulting runtime state. The global onboarding check was already recorded when the wizard opened.',
638
771
  defaultValue: 'Ready',
639
772
  },
640
773
  ],
@@ -16,12 +16,15 @@ import type { ConfigKey } from '../../config/index.ts';
16
16
 
17
17
  export type OnboardingWizardMode = OnboardingMode;
18
18
 
19
+ export type OnboardingWizardExternalSurfaceStepId = `external-surface:${string}`;
20
+
19
21
  export type OnboardingWizardStepId =
20
22
  | 'loading'
21
23
  | 'capabilities'
22
24
  | 'network'
23
25
  | 'access'
24
26
  | 'external-services'
27
+ | OnboardingWizardExternalSurfaceStepId
25
28
  | 'provider-access'
26
29
  | 'default-model'
27
30
  | 'experience'
@@ -41,9 +44,20 @@ export type OnboardingWizardAction =
41
44
  | 'apply'
42
45
  | 'select-all-capabilities'
43
46
  | 'clear-capabilities'
47
+ | 'select-all-external-surfaces'
48
+ | 'clear-external-surfaces'
44
49
  | 'start-openai-subscription'
45
50
  | 'finish-openai-subscription';
46
51
 
52
+ export type OnboardingWizardApplyFeedbackSeverity = 'info' | 'warning' | 'error';
53
+
54
+ export interface OnboardingWizardApplyFeedback {
55
+ readonly severity: OnboardingWizardApplyFeedbackSeverity;
56
+ readonly title: string;
57
+ readonly summary: string;
58
+ readonly messages: readonly string[];
59
+ }
60
+
47
61
  export interface OnboardingWizardRadioOption {
48
62
  readonly id: string;
49
63
  readonly label: string;
@@ -159,6 +173,7 @@ export interface OnboardingWizardSnapshot {
159
173
  readonly editBuffer: string;
160
174
  readonly hydrationPending: boolean;
161
175
  readonly hydrationError: string | null;
176
+ readonly applyFeedback: OnboardingWizardApplyFeedback | null;
162
177
  readonly hydration: OnboardingWizardRuntimeHydration;
163
178
  }
164
179