@pellux/goodvibes-tui 0.19.28 → 0.19.30
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.
- package/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +2 -2
- package/src/cli/surface-command.ts +46 -11
- package/src/core/orchestrator.ts +5 -1
- package/src/daemon/cli.ts +7 -0
- package/src/input/handler-onboarding.ts +151 -44
- package/src/input/onboarding/handler-onboarding-routes.ts +4 -0
- package/src/input/onboarding/onboarding-wizard-apply.ts +35 -8
- package/src/input/onboarding/onboarding-wizard-constants.ts +4 -5
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +93 -5
- package/src/input/onboarding/onboarding-wizard-helpers.ts +2 -2
- package/src/input/onboarding/onboarding-wizard-rules.ts +22 -3
- package/src/input/onboarding/onboarding-wizard-state.ts +12 -7
- package/src/input/onboarding/onboarding-wizard-steps.ts +133 -59
- package/src/input/onboarding/onboarding-wizard-types.ts +10 -0
- package/src/input/onboarding/onboarding-wizard.ts +56 -4
- package/src/input/settings-modal-types.ts +2 -1
- package/src/input/settings-modal.ts +4 -0
- package/src/main.ts +33 -26
- package/src/renderer/compositor.ts +3 -3
- package/src/renderer/onboarding/onboarding-wizard.ts +38 -21
- package/src/renderer/settings-modal-helpers.ts +9 -0
- package/src/renderer/settings-modal.ts +3 -0
- package/src/runtime/bootstrap-core.ts +28 -3
- package/src/runtime/bootstrap.ts +20 -3
- package/src/runtime/onboarding/apply.ts +36 -8
- package/src/runtime/onboarding/derivation.ts +7 -7
- package/src/runtime/onboarding/snapshot.ts +1 -0
- package/src/runtime/onboarding/types.ts +4 -1
- package/src/runtime/onboarding/verify.ts +1 -1
- package/src/runtime/surface-feature-flags.ts +67 -0
- 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 {
|
|
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, OnboardingWizardExternalSurfaceStepId, 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)];
|
|
@@ -86,7 +92,7 @@ export function buildCapabilitiesStep(controller: OnboardingWizardController): O
|
|
|
86
92
|
id: 'capabilities.select-all',
|
|
87
93
|
action: 'select-all-capabilities',
|
|
88
94
|
label: 'Select all server-backed capabilities',
|
|
89
|
-
hint: 'Enable browser access,
|
|
95
|
+
hint: 'Enable browser access, LAN reachability, webhooks/events, and external app surfaces. Local TUI Only is turned off.',
|
|
90
96
|
defaultValue: 'Action',
|
|
91
97
|
},
|
|
92
98
|
{
|
|
@@ -103,7 +109,7 @@ export function buildCapabilitiesStep(controller: OnboardingWizardController): O
|
|
|
103
109
|
id: 'capabilities',
|
|
104
110
|
title: 'Choose GoodVibes capabilities',
|
|
105
111
|
shortLabel: 'Capabilities',
|
|
106
|
-
description: '
|
|
112
|
+
description: 'Choose what GoodVibes should be able to do. Local TUI Only avoids servers; any other choice enables service mode and autostart.',
|
|
107
113
|
summaryTitle: 'Selected capabilities',
|
|
108
114
|
summaryLines: [
|
|
109
115
|
`${selectedCount}/${capabilities.length} option(s) selected`,
|
|
@@ -266,8 +272,11 @@ export function buildDefaultModelStep(controller: OnboardingWizardController): O
|
|
|
266
272
|
}
|
|
267
273
|
|
|
268
274
|
export function buildExternalServicesStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
|
|
269
|
-
const
|
|
270
|
-
.filter((surface) => controller.getBooleanFieldValue(
|
|
275
|
+
const selectedCount = EXTERNAL_SURFACE_SPECS
|
|
276
|
+
.filter((surface) => controller.getBooleanFieldValue(
|
|
277
|
+
surface.enabledFieldId,
|
|
278
|
+
isExternalSurfaceSelectedByDefault(surface, controller.runtimeSnapshot),
|
|
279
|
+
))
|
|
271
280
|
.length;
|
|
272
281
|
const fields: OnboardingWizardFieldDefinition[] = [];
|
|
273
282
|
|
|
@@ -276,8 +285,8 @@ export function buildExternalServicesStep(controller: OnboardingWizardController
|
|
|
276
285
|
kind: 'checklist',
|
|
277
286
|
id: surface.enabledFieldId,
|
|
278
287
|
label: surface.label,
|
|
279
|
-
hint: surface.hint
|
|
280
|
-
defaultValue: surface
|
|
288
|
+
hint: `${surface.hint} Selecting this opens a dedicated setup screen; auto-start is chosen on that screen.`,
|
|
289
|
+
defaultValue: isExternalSurfaceSelectedByDefault(surface, controller.runtimeSnapshot),
|
|
281
290
|
});
|
|
282
291
|
}
|
|
283
292
|
|
|
@@ -287,7 +296,7 @@ export function buildExternalServicesStep(controller: OnboardingWizardController
|
|
|
287
296
|
id: 'external-services.select-all',
|
|
288
297
|
action: 'select-all-external-surfaces',
|
|
289
298
|
label: 'Select all external surfaces',
|
|
290
|
-
hint: '
|
|
299
|
+
hint: 'Show setup screens for every supported external surface. Auto-start stays controlled per surface.',
|
|
291
300
|
defaultValue: 'Action',
|
|
292
301
|
},
|
|
293
302
|
{
|
|
@@ -295,7 +304,7 @@ export function buildExternalServicesStep(controller: OnboardingWizardController
|
|
|
295
304
|
id: 'external-services.clear',
|
|
296
305
|
action: 'clear-external-surfaces',
|
|
297
306
|
label: 'Clear all external surfaces',
|
|
298
|
-
hint: '
|
|
307
|
+
hint: 'Hide all external surface setup screens. The HTTP listener can still be enabled separately by webhook/event capabilities.',
|
|
299
308
|
defaultValue: 'Action',
|
|
300
309
|
},
|
|
301
310
|
{
|
|
@@ -312,12 +321,12 @@ export function buildExternalServicesStep(controller: OnboardingWizardController
|
|
|
312
321
|
id: 'external-services',
|
|
313
322
|
title: 'Choose external surfaces',
|
|
314
323
|
shortLabel: 'Services',
|
|
315
|
-
description: 'Select the apps and integration surfaces GoodVibes should prepare.
|
|
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.',
|
|
316
325
|
summaryTitle: 'External surfaces',
|
|
317
326
|
summaryLines: [
|
|
318
|
-
`${
|
|
327
|
+
`${selectedCount} external surface(s) selected for setup`,
|
|
319
328
|
`Secret policy: ${controller.getStringFieldValue('external-services.secret-policy', controller.runtimeSnapshot?.runtimeDefaults.secretStoragePolicy ?? 'preferred_secure')}`,
|
|
320
|
-
|
|
329
|
+
selectedCount > 0 ? 'Selected surfaces appear as separate setup screens.' : 'No external surfaces selected.',
|
|
321
330
|
],
|
|
322
331
|
fields,
|
|
323
332
|
};
|
|
@@ -325,28 +334,47 @@ export function buildExternalServicesStep(controller: OnboardingWizardController
|
|
|
325
334
|
|
|
326
335
|
function getSelectedExternalSurfaceSpecs(controller: OnboardingWizardController): readonly ExternalSurfaceSpec[] {
|
|
327
336
|
return EXTERNAL_SURFACE_SPECS.filter((surface) => (
|
|
328
|
-
controller.getBooleanFieldValue(
|
|
337
|
+
controller.getBooleanFieldValue(
|
|
338
|
+
surface.enabledFieldId,
|
|
339
|
+
isExternalSurfaceSelectedByDefault(surface, controller.runtimeSnapshot),
|
|
340
|
+
)
|
|
329
341
|
));
|
|
330
342
|
}
|
|
331
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
|
+
|
|
332
357
|
function buildExternalSurfaceStep(
|
|
333
358
|
controller: OnboardingWizardController,
|
|
334
359
|
surface: ExternalSurfaceSpec,
|
|
335
360
|
): OnboardingWizardStepDefinition {
|
|
336
|
-
let
|
|
337
|
-
let
|
|
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);
|
|
338
366
|
const setupFields = surface.fields.map((setupField): OnboardingWizardFieldDefinition => {
|
|
339
|
-
const
|
|
340
|
-
if (
|
|
341
|
-
|
|
367
|
+
const suggested = controller.isRequiredExternalSetupField(setupField.id);
|
|
368
|
+
if (suggested) {
|
|
369
|
+
setupCount += 1;
|
|
342
370
|
if (normalizeText(setupField.defaultValue(controller.runtimeSnapshot)).length > 0
|
|
343
371
|
|| normalizeText(controller.getStringFieldValue(setupField.id, '')).length > 0) {
|
|
344
|
-
|
|
372
|
+
setupCompleteCount += 1;
|
|
345
373
|
}
|
|
346
374
|
}
|
|
347
375
|
|
|
348
|
-
const hint =
|
|
349
|
-
? `${setupField.hint}
|
|
376
|
+
const hint = suggested
|
|
377
|
+
? `${setupField.hint} Recommended because ${surface.label} is selected, but it will not block saving.`
|
|
350
378
|
: setupField.hint;
|
|
351
379
|
|
|
352
380
|
if (setupField.kind === 'radio') {
|
|
@@ -367,26 +395,46 @@ function buildExternalSurfaceStep(
|
|
|
367
395
|
hint,
|
|
368
396
|
placeholder: setupField.placeholder,
|
|
369
397
|
defaultValue: setupField.defaultValue(controller.runtimeSnapshot),
|
|
370
|
-
required,
|
|
371
398
|
};
|
|
372
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
|
+
: [];
|
|
373
407
|
const title = `${surface.label.replace(/ surface$/i, '')} setup`;
|
|
374
|
-
const
|
|
375
|
-
? '
|
|
376
|
-
: `
|
|
408
|
+
const setupSummary = setupCount === 0
|
|
409
|
+
? 'Suggested setup: none'
|
|
410
|
+
: `Suggested setup entered: ${setupCompleteCount}/${setupCount}`;
|
|
377
411
|
|
|
378
412
|
return {
|
|
379
413
|
id: `external-surface:${surface.id}` as OnboardingWizardExternalSurfaceStepId,
|
|
380
414
|
title,
|
|
381
415
|
shortLabel: surface.label.replace(/ surface$/i, ''),
|
|
382
|
-
description: `Configure ${surface.label}.
|
|
416
|
+
description: `Configure ${surface.label}. Settings are saved either way; auto-start controls whether this surface starts with the background service.`,
|
|
383
417
|
summaryTitle: `${surface.label} setup`,
|
|
384
418
|
summaryLines: [
|
|
385
|
-
|
|
419
|
+
`Auto-start: ${autoStartValue === 'yes' ? 'yes' : 'no'}`,
|
|
420
|
+
...ntfyTopicSummary,
|
|
421
|
+
setupSummary,
|
|
386
422
|
`Secret policy: ${controller.getStringFieldValue('external-services.secret-policy', controller.runtimeSnapshot?.runtimeDefaults.secretStoragePolicy ?? 'preferred_secure')}`,
|
|
387
|
-
|
|
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,
|
|
388
437
|
],
|
|
389
|
-
fields: setupFields,
|
|
390
438
|
};
|
|
391
439
|
}
|
|
392
440
|
|
|
@@ -574,32 +622,34 @@ export function buildAccountsStep(controller: OnboardingWizardController): Onboa
|
|
|
574
622
|
&& !needsAuthBootstrap
|
|
575
623
|
&& controller.hasLocalAuthUser();
|
|
576
624
|
const fields: OnboardingWizardFieldDefinition[] = [];
|
|
625
|
+
const defaultAdminUsername = controller.getDefaultAdminUsername();
|
|
577
626
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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
|
+
);
|
|
603
653
|
|
|
604
654
|
fields.push(
|
|
605
655
|
{
|
|
@@ -668,6 +718,29 @@ export function buildAccountsStep(controller: OnboardingWizardController): Onboa
|
|
|
668
718
|
}
|
|
669
719
|
|
|
670
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
|
+
|
|
671
744
|
return {
|
|
672
745
|
id: 'review',
|
|
673
746
|
title: 'Review and apply',
|
|
@@ -675,12 +748,13 @@ export function buildReviewStep(controller: OnboardingWizardController): Onboard
|
|
|
675
748
|
description: 'Review the selected settings and apply them directly from the wizard.',
|
|
676
749
|
summaryTitle: 'Review posture',
|
|
677
750
|
summaryLines: [
|
|
678
|
-
|
|
679
|
-
`${controller.buildApplyRequest().operations.length}
|
|
680
|
-
`
|
|
681
|
-
controller.isEditingTextField() ? `Editing: ${controller.editingFieldId}` : 'Ready
|
|
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',
|
|
682
755
|
],
|
|
683
756
|
fields: [
|
|
757
|
+
...feedbackFields,
|
|
684
758
|
{
|
|
685
759
|
kind: 'status',
|
|
686
760
|
id: 'review.global-marker',
|
|
@@ -49,6 +49,15 @@ export type OnboardingWizardAction =
|
|
|
49
49
|
| 'start-openai-subscription'
|
|
50
50
|
| 'finish-openai-subscription';
|
|
51
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
|
+
|
|
52
61
|
export interface OnboardingWizardRadioOption {
|
|
53
62
|
readonly id: string;
|
|
54
63
|
readonly label: string;
|
|
@@ -164,6 +173,7 @@ export interface OnboardingWizardSnapshot {
|
|
|
164
173
|
readonly editBuffer: string;
|
|
165
174
|
readonly hydrationPending: boolean;
|
|
166
175
|
readonly hydrationError: string | null;
|
|
176
|
+
readonly applyFeedback: OnboardingWizardApplyFeedback | null;
|
|
167
177
|
readonly hydration: OnboardingWizardRuntimeHydration;
|
|
168
178
|
}
|
|
169
179
|
|
|
@@ -34,9 +34,9 @@ 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, OnboardingWizardFieldDefinition, OnboardingWizardFieldWindow, OnboardingWizardMode, OnboardingWizardModelSelection, OnboardingWizardRuntimeHydration, OnboardingWizardSnapshot, OnboardingWizardStepDefinition, OnboardingWizardStepId } from './onboarding-wizard-types.ts';
|
|
37
|
+
import type { MutableModelSelectionMap, OnboardingWizardAction, OnboardingWizardApplyFeedback, OnboardingWizardFieldDefinition, OnboardingWizardFieldWindow, OnboardingWizardMode, OnboardingWizardModelSelection, OnboardingWizardRuntimeHydration, OnboardingWizardSnapshot, OnboardingWizardStepDefinition, OnboardingWizardStepId } from './onboarding-wizard-types.ts';
|
|
38
38
|
|
|
39
|
-
export type { OnboardingWizardAcknowledgementFieldDefinition, OnboardingWizardAction, OnboardingWizardActionFieldDefinition, 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 { 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
42
|
export class OnboardingWizardController {
|
|
@@ -58,6 +58,7 @@ export class OnboardingWizardController {
|
|
|
58
58
|
public editingFieldId: string | null = null;
|
|
59
59
|
public editBuffer = '';
|
|
60
60
|
public hydrationError: string | null = null;
|
|
61
|
+
public applyFeedback: OnboardingWizardApplyFeedback | null = null;
|
|
61
62
|
|
|
62
63
|
public readonly baselineToggleState = new Map<string, boolean>();
|
|
63
64
|
public readonly baselineRadioState = new Map<string, string>();
|
|
@@ -102,6 +103,7 @@ export class OnboardingWizardController {
|
|
|
102
103
|
this.stepIndex = 0;
|
|
103
104
|
this.pendingModelPickerTarget = null;
|
|
104
105
|
this.pendingAction = null;
|
|
106
|
+
this.applyFeedback = null;
|
|
105
107
|
this.editingFieldId = null;
|
|
106
108
|
this.editBuffer = '';
|
|
107
109
|
this.scrollOffsets.fill(0);
|
|
@@ -115,6 +117,7 @@ export class OnboardingWizardController {
|
|
|
115
117
|
this.hydrationError = null;
|
|
116
118
|
this.pendingModelPickerTarget = null;
|
|
117
119
|
this.pendingAction = null;
|
|
120
|
+
this.applyFeedback = null;
|
|
118
121
|
this.cancelEdit();
|
|
119
122
|
}
|
|
120
123
|
|
|
@@ -178,6 +181,7 @@ export class OnboardingWizardController {
|
|
|
178
181
|
editBuffer: this.editBuffer,
|
|
179
182
|
hydrationPending: this.hydrationPending,
|
|
180
183
|
hydrationError: this.hydrationError,
|
|
184
|
+
applyFeedback: this.applyFeedback,
|
|
181
185
|
hydration: this.captureHydratedState(),
|
|
182
186
|
};
|
|
183
187
|
}
|
|
@@ -191,6 +195,7 @@ export class OnboardingWizardController {
|
|
|
191
195
|
this.stepIndex = clamp(snapshot.stepIndex, 0, Math.max(0, this.steps.length - 1));
|
|
192
196
|
this.pendingModelPickerTarget = snapshot.pendingModelPickerTarget;
|
|
193
197
|
this.pendingAction = snapshot.pendingAction;
|
|
198
|
+
this.applyFeedback = snapshot.applyFeedback;
|
|
194
199
|
this.dirtyStepIds.clear();
|
|
195
200
|
for (const stepId of snapshot.dirtyStepIds) this.dirtyStepIds.add(stepId);
|
|
196
201
|
|
|
@@ -223,6 +228,7 @@ export class OnboardingWizardController {
|
|
|
223
228
|
public beginRuntimeHydration(): void {
|
|
224
229
|
this.hydrationPending = true;
|
|
225
230
|
this.hydrationError = null;
|
|
231
|
+
this.applyFeedback = null;
|
|
226
232
|
this.stepIndex = 0;
|
|
227
233
|
this.pendingModelPickerTarget = null;
|
|
228
234
|
this.pendingAction = null;
|
|
@@ -232,6 +238,7 @@ export class OnboardingWizardController {
|
|
|
232
238
|
public finishRuntimeHydration(): void {
|
|
233
239
|
this.hydrationPending = false;
|
|
234
240
|
this.hydrationError = null;
|
|
241
|
+
this.applyFeedback = null;
|
|
235
242
|
this.stepIndex = clamp(this.stepIndex, 0, Math.max(0, this.steps.length - 1));
|
|
236
243
|
this.reconcileStepCursor(this.stepIndex);
|
|
237
244
|
}
|
|
@@ -239,6 +246,12 @@ export class OnboardingWizardController {
|
|
|
239
246
|
public failRuntimeHydration(message: string): void {
|
|
240
247
|
this.hydrationPending = false;
|
|
241
248
|
this.hydrationError = message;
|
|
249
|
+
this.applyFeedback = {
|
|
250
|
+
severity: 'error',
|
|
251
|
+
title: 'Current settings could not load',
|
|
252
|
+
summary: message,
|
|
253
|
+
messages: [message],
|
|
254
|
+
};
|
|
242
255
|
this.stepIndex = 0;
|
|
243
256
|
this.pendingModelPickerTarget = null;
|
|
244
257
|
this.pendingAction = null;
|
|
@@ -344,6 +357,14 @@ export class OnboardingWizardController {
|
|
|
344
357
|
this.pendingAction = null;
|
|
345
358
|
}
|
|
346
359
|
|
|
360
|
+
public setApplyFeedback(feedback: OnboardingWizardApplyFeedback): void {
|
|
361
|
+
this.applyFeedback = feedback;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
public clearApplyFeedback(): void {
|
|
365
|
+
this.applyFeedback = null;
|
|
366
|
+
}
|
|
367
|
+
|
|
347
368
|
public consumePendingAction(): OnboardingWizardAction | null {
|
|
348
369
|
const action = this.pendingAction;
|
|
349
370
|
this.pendingAction = null;
|
|
@@ -360,6 +381,7 @@ export class OnboardingWizardController {
|
|
|
360
381
|
this.toggleCapability(field.capabilityId);
|
|
361
382
|
this.pendingModelPickerTarget = null;
|
|
362
383
|
this.pendingAction = null;
|
|
384
|
+
this.applyFeedback = null;
|
|
363
385
|
this.recalculateDirtyState();
|
|
364
386
|
return;
|
|
365
387
|
}
|
|
@@ -369,6 +391,7 @@ export class OnboardingWizardController {
|
|
|
369
391
|
this.toggleState.set(field.id, !current);
|
|
370
392
|
this.pendingModelPickerTarget = null;
|
|
371
393
|
this.pendingAction = null;
|
|
394
|
+
this.applyFeedback = null;
|
|
372
395
|
this.recalculateDirtyState();
|
|
373
396
|
return;
|
|
374
397
|
}
|
|
@@ -382,6 +405,7 @@ export class OnboardingWizardController {
|
|
|
382
405
|
this.radioState.set(field.id, next.id);
|
|
383
406
|
this.pendingModelPickerTarget = null;
|
|
384
407
|
this.pendingAction = null;
|
|
408
|
+
this.applyFeedback = null;
|
|
385
409
|
this.recalculateDirtyState();
|
|
386
410
|
return;
|
|
387
411
|
}
|
|
@@ -397,6 +421,7 @@ export class OnboardingWizardController {
|
|
|
397
421
|
this.selectAllServerCapabilities();
|
|
398
422
|
this.pendingAction = null;
|
|
399
423
|
this.pendingModelPickerTarget = null;
|
|
424
|
+
this.applyFeedback = null;
|
|
400
425
|
this.recalculateDirtyState();
|
|
401
426
|
return;
|
|
402
427
|
}
|
|
@@ -405,6 +430,7 @@ export class OnboardingWizardController {
|
|
|
405
430
|
this.selectLocalTuiOnly();
|
|
406
431
|
this.pendingAction = null;
|
|
407
432
|
this.pendingModelPickerTarget = null;
|
|
433
|
+
this.applyFeedback = null;
|
|
408
434
|
this.recalculateDirtyState();
|
|
409
435
|
return;
|
|
410
436
|
}
|
|
@@ -413,6 +439,7 @@ export class OnboardingWizardController {
|
|
|
413
439
|
this.selectAllExternalSurfaces();
|
|
414
440
|
this.pendingAction = null;
|
|
415
441
|
this.pendingModelPickerTarget = null;
|
|
442
|
+
this.applyFeedback = null;
|
|
416
443
|
this.recalculateDirtyState();
|
|
417
444
|
return;
|
|
418
445
|
}
|
|
@@ -421,6 +448,7 @@ export class OnboardingWizardController {
|
|
|
421
448
|
this.clearExternalSurfaces();
|
|
422
449
|
this.pendingAction = null;
|
|
423
450
|
this.pendingModelPickerTarget = null;
|
|
451
|
+
this.applyFeedback = null;
|
|
424
452
|
this.recalculateDirtyState();
|
|
425
453
|
return;
|
|
426
454
|
}
|
|
@@ -433,6 +461,7 @@ export class OnboardingWizardController {
|
|
|
433
461
|
this.pendingModelPickerTarget = field.target;
|
|
434
462
|
this.pendingAction = null;
|
|
435
463
|
this.touchedActionFields.add(field.id);
|
|
464
|
+
this.applyFeedback = null;
|
|
436
465
|
}
|
|
437
466
|
|
|
438
467
|
public beginEdit(fieldId: string): void {
|
|
@@ -458,6 +487,7 @@ export class OnboardingWizardController {
|
|
|
458
487
|
const field = this.getFieldById(fieldId);
|
|
459
488
|
if (field && (field.kind === 'text' || field.kind === 'masked')) {
|
|
460
489
|
this.textState.set(fieldId, this.editBuffer);
|
|
490
|
+
this.applyFeedback = null;
|
|
461
491
|
this.recalculateDirtyState();
|
|
462
492
|
}
|
|
463
493
|
this.editingFieldId = null;
|
|
@@ -479,6 +509,21 @@ export class OnboardingWizardController {
|
|
|
479
509
|
this.editBuffer = this.editBuffer.slice(0, -1);
|
|
480
510
|
}
|
|
481
511
|
|
|
512
|
+
public clearEditingValue(): void {
|
|
513
|
+
if (this.editingFieldId === null) return;
|
|
514
|
+
this.editBuffer = '';
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
public clearSelectedTextField(): boolean {
|
|
518
|
+
const field = this.getSelectedField();
|
|
519
|
+
if (!field || (field.kind !== 'text' && field.kind !== 'masked')) return false;
|
|
520
|
+
this.textState.set(field.id, '');
|
|
521
|
+
if (this.editingFieldId === field.id) this.editBuffer = '';
|
|
522
|
+
this.applyFeedback = null;
|
|
523
|
+
this.recalculateDirtyState();
|
|
524
|
+
return true;
|
|
525
|
+
}
|
|
526
|
+
|
|
482
527
|
public setFieldValue(fieldId: string, value: boolean | string): void {
|
|
483
528
|
const field = this.getFieldById(fieldId);
|
|
484
529
|
if (!field) return;
|
|
@@ -487,6 +532,7 @@ export class OnboardingWizardController {
|
|
|
487
532
|
if (typeof value === 'boolean') {
|
|
488
533
|
if (field.kind === 'checklist' && field.capabilityId) this.setCapabilityValue(field.capabilityId, value);
|
|
489
534
|
else this.toggleState.set(fieldId, value);
|
|
535
|
+
this.applyFeedback = null;
|
|
490
536
|
this.recalculateDirtyState();
|
|
491
537
|
}
|
|
492
538
|
return;
|
|
@@ -495,6 +541,7 @@ export class OnboardingWizardController {
|
|
|
495
541
|
if (field.kind === 'radio') {
|
|
496
542
|
if (typeof value === 'string' && field.options.some((option) => option.id === value)) {
|
|
497
543
|
this.radioState.set(fieldId, value);
|
|
544
|
+
this.applyFeedback = null;
|
|
498
545
|
this.recalculateDirtyState();
|
|
499
546
|
}
|
|
500
547
|
return;
|
|
@@ -504,6 +551,7 @@ export class OnboardingWizardController {
|
|
|
504
551
|
if (typeof value === 'string') {
|
|
505
552
|
this.textState.set(fieldId, value);
|
|
506
553
|
if (this.editingFieldId === fieldId) this.editBuffer = value;
|
|
554
|
+
this.applyFeedback = null;
|
|
507
555
|
this.recalculateDirtyState();
|
|
508
556
|
}
|
|
509
557
|
}
|
|
@@ -559,13 +607,15 @@ export class OnboardingWizardController {
|
|
|
559
607
|
|
|
560
608
|
if (field.kind === 'text') {
|
|
561
609
|
const value = normalizeText(this.getFieldValue(field) as string);
|
|
562
|
-
if (value.length === 0 &&
|
|
610
|
+
if (value.length === 0 && field.required === true) return 'Missing';
|
|
611
|
+
if (value.length === 0 && this.isRequiredExternalSetupField(field.id)) return 'Not set';
|
|
563
612
|
return value.length > 0 ? value : field.placeholder;
|
|
564
613
|
}
|
|
565
614
|
|
|
566
615
|
if (field.kind === 'masked') {
|
|
567
616
|
const value = normalizeText(this.getFieldValue(field) as string);
|
|
568
|
-
if (value.length === 0 &&
|
|
617
|
+
if (value.length === 0 && field.required === true) return 'Missing';
|
|
618
|
+
if (value.length === 0 && this.isRequiredExternalSetupField(field.id)) return 'Not set';
|
|
569
619
|
return value.length > 0 ? maskValue(value) : field.placeholder;
|
|
570
620
|
}
|
|
571
621
|
|
|
@@ -584,6 +634,7 @@ export class OnboardingWizardController {
|
|
|
584
634
|
enabled: selection.enabled ?? true,
|
|
585
635
|
});
|
|
586
636
|
this.pendingModelPickerTarget = null;
|
|
637
|
+
this.applyFeedback = null;
|
|
587
638
|
this.recalculateDirtyState();
|
|
588
639
|
}
|
|
589
640
|
|
|
@@ -601,6 +652,7 @@ export class OnboardingWizardController {
|
|
|
601
652
|
this.baselineModelSelectionState.clear();
|
|
602
653
|
for (const [key, value] of this.modelSelectionState) this.baselineModelSelectionState.set(key, cloneSelection(value));
|
|
603
654
|
this.dirtyStepIds.clear();
|
|
655
|
+
this.applyFeedback = null;
|
|
604
656
|
}
|
|
605
657
|
|
|
606
658
|
public getSharedIpDefault(enabled: { readonly controlPlane: boolean; readonly httpListener: boolean; readonly web: boolean }): boolean {
|
|
@@ -2,7 +2,7 @@ import type { ConfigSetting } from '@pellux/goodvibes-sdk/platform/config/schema
|
|
|
2
2
|
import type { ProviderAuthFreshness, ProviderAuthRoute } from '@pellux/goodvibes-sdk/platform/runtime/provider-accounts/registry';
|
|
3
3
|
import type { FeatureFlag, FlagState } from '@pellux/goodvibes-sdk/platform/runtime/feature-flags/types';
|
|
4
4
|
|
|
5
|
-
export type SettingsCategory = 'display' | 'ui' | 'provider' | 'subscriptions' | 'behavior' | 'storage' | 'permissions' | 'mcp' | 'sandbox' | 'danger' | 'tools' | 'flags' | 'network';
|
|
5
|
+
export type SettingsCategory = 'display' | 'ui' | 'provider' | 'subscriptions' | 'behavior' | 'storage' | 'permissions' | 'mcp' | 'sandbox' | 'surfaces' | 'danger' | 'tools' | 'flags' | 'network';
|
|
6
6
|
|
|
7
7
|
export const SETTINGS_CATEGORIES: SettingsCategory[] = [
|
|
8
8
|
'display',
|
|
@@ -14,6 +14,7 @@ export const SETTINGS_CATEGORIES: SettingsCategory[] = [
|
|
|
14
14
|
'permissions',
|
|
15
15
|
'mcp',
|
|
16
16
|
'sandbox',
|
|
17
|
+
'surfaces',
|
|
17
18
|
'danger',
|
|
18
19
|
'tools',
|
|
19
20
|
'flags',
|
|
@@ -519,6 +519,8 @@ export class SettingsModal {
|
|
|
519
519
|
cat = 'tools';
|
|
520
520
|
} else if (rawCat === 'controlPlane' || rawCat === 'httpListener' || rawCat === 'web') {
|
|
521
521
|
cat = 'network';
|
|
522
|
+
} else if (rawCat === 'surfaces') {
|
|
523
|
+
cat = 'surfaces';
|
|
522
524
|
} else {
|
|
523
525
|
cat = rawCat as SettingsCategory;
|
|
524
526
|
}
|
|
@@ -746,6 +748,8 @@ export class SettingsModal {
|
|
|
746
748
|
if (isRestartKey && previousValue !== value) {
|
|
747
749
|
this.lastSaveTriggeredRestart = 'web';
|
|
748
750
|
}
|
|
751
|
+
} else if (rawCat === 'surfaces') {
|
|
752
|
+
cat = 'surfaces';
|
|
749
753
|
} else {
|
|
750
754
|
cat = rawCat as SettingsCategory;
|
|
751
755
|
}
|