@pellux/goodvibes-tui 0.20.3 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/README.md +23 -2
- package/docs/foundation-artifacts/operator-contract.json +78 -1
- package/package.json +4 -2
- package/src/audio/spoken-turn-controller.ts +31 -1
- package/src/audio/spoken-turn-wiring.ts +26 -4
- package/src/cli/bundle-command.ts +1 -1
- package/src/cli/completions/generate.ts +658 -0
- package/src/cli/config-overrides.ts +68 -0
- package/src/cli/entrypoint.ts +6 -0
- package/src/cli/help.ts +4 -2
- package/src/cli/management-commands.ts +1 -1
- package/src/cli/management.ts +1 -8
- package/src/cli/parser.ts +31 -18
- package/src/cli/service-command.ts +1 -1
- package/src/cli/surface-command.ts +1 -1
- package/src/cli/tui-startup.ts +72 -10
- package/src/cli/types.ts +14 -3
- package/src/cli-flags.ts +1 -0
- package/src/config/atomic-write.ts +70 -0
- package/src/config/goodvibes-home-audit.ts +2 -0
- package/src/config/read-versioned.ts +115 -0
- package/src/core/context-auto-compact.ts +77 -0
- package/src/core/conversation-rendering.ts +49 -15
- package/src/core/conversation.ts +101 -16
- package/src/core/format-user-error.ts +192 -0
- package/src/core/stream-event-wiring.ts +144 -0
- package/src/core/stream-stall-watchdog.ts +103 -0
- package/src/core/system-message-router.ts +5 -1
- package/src/core/turn-event-wiring.ts +124 -0
- package/src/daemon/cli.ts +5 -0
- package/src/export/cost-utils.ts +71 -0
- package/src/export/gist-uploader.ts +136 -0
- package/src/input/command-registry.ts +32 -1
- package/src/input/commands/control-room-runtime.ts +10 -10
- package/src/input/commands/experience-runtime.ts +5 -4
- package/src/input/commands/knowledge.ts +1 -1
- package/src/input/commands/local-auth-runtime.ts +27 -5
- package/src/input/commands/local-setup.ts +4 -6
- package/src/input/commands/memory-product-runtime.ts +8 -6
- package/src/input/commands/operator-panel-runtime.ts +1 -1
- package/src/input/commands/operator-runtime.ts +3 -10
- package/src/input/commands/{integration-runtime.ts → plugin-runtime.ts} +1 -1
- package/src/input/commands/provider.ts +57 -3
- package/src/input/commands/recall-review.ts +26 -2
- package/src/input/commands/services-runtime.ts +2 -2
- package/src/input/commands/session-workflow.ts +8 -16
- package/src/input/commands/session.ts +70 -20
- package/src/input/commands/share-runtime.ts +99 -12
- package/src/input/commands/tts-runtime.ts +30 -4
- package/src/input/commands.ts +2 -4
- package/src/input/delete-key-policy.ts +46 -0
- package/src/input/feed-context-factory.ts +2 -0
- package/src/input/handler-feed.ts +3 -0
- package/src/input/handler-interactions.ts +2 -15
- package/src/input/handler-modal-routes.ts +128 -12
- package/src/input/handler-modal-token-routes.ts +22 -5
- package/src/input/handler-onboarding-cloudflare.ts +1 -1
- package/src/input/handler-onboarding.ts +73 -69
- package/src/input/handler-types.ts +163 -0
- package/src/input/handler.ts +6 -2
- package/src/input/input-history.ts +76 -6
- package/src/input/model-picker-filter.ts +265 -0
- package/src/input/model-picker-items.ts +208 -0
- package/src/input/model-picker.ts +92 -325
- package/src/input/onboarding/handler-onboarding-routes.ts +7 -2
- package/src/input/onboarding/onboarding-verification-helpers.ts +76 -0
- package/src/input/onboarding/onboarding-wizard-apply.ts +14 -4
- package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +16 -2
- package/src/input/onboarding/onboarding-wizard-cloudflare.ts +8 -8
- package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +1 -1
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +2 -29
- package/src/input/onboarding/onboarding-wizard-rules.ts +28 -28
- package/src/input/onboarding/onboarding-wizard-state.ts +20 -20
- package/src/input/onboarding/onboarding-wizard-steps.ts +24 -25
- package/src/input/onboarding/onboarding-wizard-types.ts +145 -3
- package/src/input/onboarding/onboarding-wizard-validation.ts +77 -0
- package/src/input/onboarding/onboarding-wizard.ts +3 -3
- package/src/input/settings-modal-behavior.ts +5 -0
- package/src/input/settings-modal-data.ts +378 -0
- package/src/input/settings-modal-mutations.ts +157 -0
- package/src/input/settings-modal-reset.ts +154 -0
- package/src/input/settings-modal.ts +236 -232
- package/src/main.ts +93 -85
- package/src/panels/agent-inspector-panel.ts +120 -18
- package/src/panels/agent-inspector-shared.ts +29 -0
- package/src/panels/builtin/agent.ts +4 -1
- package/src/panels/builtin/development.ts +5 -1
- package/src/panels/builtin/knowledge.ts +14 -13
- package/src/panels/builtin/operations.ts +22 -1
- package/src/panels/builtin/shared.ts +7 -0
- package/src/panels/cockpit-panel.ts +123 -3
- package/src/panels/cockpit-read-model.ts +232 -0
- package/src/panels/confirm-state.ts +27 -12
- package/src/panels/cost-tracker-panel.ts +23 -67
- package/src/panels/eval-panel.ts +10 -9
- package/src/panels/index.ts +1 -1
- package/src/panels/knowledge-graph-panel.ts +84 -0
- package/src/panels/local-auth-panel.ts +124 -4
- package/src/panels/memory-panel.ts +370 -40
- package/src/panels/project-planning-panel.ts +42 -4
- package/src/panels/search-focus.ts +11 -5
- package/src/panels/session-maintenance.ts +66 -15
- package/src/panels/subscription-panel.ts +33 -25
- package/src/panels/types.ts +28 -1
- package/src/panels/wrfc-panel.ts +224 -41
- package/src/renderer/agent-detail-modal.ts +118 -13
- package/src/renderer/code-block.ts +10 -2
- package/src/renderer/compositor.ts +18 -4
- package/src/renderer/context-inspector.ts +1 -5
- package/src/renderer/context-status-hint.ts +54 -0
- package/src/renderer/diff.ts +94 -21
- package/src/renderer/markdown.ts +29 -13
- package/src/renderer/settings-modal-helpers.ts +1 -1
- package/src/renderer/settings-modal.ts +90 -10
- package/src/renderer/shell-surface.ts +10 -0
- package/src/renderer/syntax-highlighter.ts +10 -3
- package/src/renderer/term-caps.ts +318 -0
- package/src/renderer/theme.ts +158 -0
- package/src/renderer/tool-call.ts +12 -2
- package/src/renderer/ui-factory.ts +50 -6
- package/src/runtime/bootstrap-command-context.ts +1 -0
- package/src/runtime/bootstrap-command-parts.ts +18 -0
- package/src/runtime/bootstrap-core.ts +145 -13
- package/src/runtime/bootstrap-shell.ts +11 -0
- package/src/runtime/bootstrap.ts +9 -0
- package/src/runtime/onboarding/apply.ts +4 -6
- package/src/runtime/onboarding/index.ts +1 -0
- package/src/runtime/onboarding/markers.ts +42 -49
- package/src/runtime/onboarding/progress.ts +148 -0
- package/src/runtime/onboarding/state.ts +133 -55
- package/src/runtime/onboarding/types.ts +20 -0
- package/src/runtime/services.ts +27 -1
- package/src/runtime/wrfc-persistence.ts +237 -0
- package/src/shell/blocking-input.ts +20 -5
- package/src/tools/wrfc-agent-guard.ts +64 -3
- package/src/utils/format-elapsed.ts +30 -0
- package/src/utils/terminal-width.ts +45 -0
- package/src/version.ts +1 -1
- package/src/work-plans/work-plan-store.ts +4 -6
- package/src/panels/knowledge-panel.ts +0 -345
- package/src/planning/project-planning-coordinator.ts +0 -543
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CLOUDFLARE_COMPONENT_IDS, DEFAULT_CLOUDFLARE_COMPONENT_SELECTION } from '../../runtime/cloudflare-control-plane.ts';
|
|
2
2
|
import { normalizeText } from './onboarding-wizard-helpers.ts';
|
|
3
|
-
import type {
|
|
3
|
+
import type { OnboardingWizardControllerLike } from './onboarding-wizard-types.ts';
|
|
4
4
|
import type { OnboardingWizardFieldDefinition, OnboardingWizardStepDefinition } from './onboarding-wizard-types.ts';
|
|
5
5
|
import {
|
|
6
6
|
CLOUDFLARE_BATCH_MODE_OPTIONS,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
getCloudflareSetupSource,
|
|
15
15
|
} from './onboarding-wizard-cloudflare.ts';
|
|
16
16
|
|
|
17
|
-
export function buildCloudflareStep(controller:
|
|
17
|
+
export function buildCloudflareStep(controller: OnboardingWizardControllerLike): OnboardingWizardStepDefinition {
|
|
18
18
|
const config = controller.runtimeSnapshot?.config.cloudflare;
|
|
19
19
|
const batch = controller.runtimeSnapshot?.config.batch;
|
|
20
20
|
const enabledDefault = controller.isCapabilitySelected('cloudflare-batch') || config?.enabled === true;
|
|
@@ -257,6 +257,19 @@ export function buildCloudflareStep(controller: OnboardingWizardController): Onb
|
|
|
257
257
|
);
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
+
// Trust-proxy notice — shown when Tunnel is selected so the
|
|
261
|
+
// operator sees the security implication before applying.
|
|
262
|
+
const tunnelSelected = enabled && components.zeroTrustTunnel;
|
|
263
|
+
if (tunnelSelected) {
|
|
264
|
+
fields.push({
|
|
265
|
+
kind: 'status',
|
|
266
|
+
id: 'cloudflare.trust-proxy-notice',
|
|
267
|
+
label: 'trustProxy will be enabled for control plane and HTTP listener',
|
|
268
|
+
defaultValue: 'Notice',
|
|
269
|
+
hint: 'Selecting Zero Trust Tunnel auto-writes controlPlane.trustProxy=true and httpListener.trustProxy=true so the login rate-limiter keys on the real client IP (CF-Connecting-IP) rather than the tunnel egress address. RESIDUAL RISK: until the SDK validates CF-Connecting-IP against Cloudflare published IP ranges (handoff Item 5), a client that reaches the listener directly can spoof this header to bypass the per-IP rate-limiter. See docs/deployment-and-services.md for the full risk posture.',
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
260
273
|
if (components.zeroTrustAccess) {
|
|
261
274
|
fields.push(
|
|
262
275
|
{
|
|
@@ -463,6 +476,7 @@ export function buildCloudflareStep(controller: OnboardingWizardController): Onb
|
|
|
463
476
|
`Components: ${enabled ? componentCount : 0} selected`,
|
|
464
477
|
`Token setup: ${enabled ? setupSource : 'not used'}`,
|
|
465
478
|
`Provision on final apply: ${enabled ? controller.getStringFieldValue('cloudflare.provision-on-apply', 'no') : 'no'}`,
|
|
479
|
+
...(enabled && components.zeroTrustTunnel ? ['trustProxy: enabled for control plane and HTTP listener (see security notice)'] : []),
|
|
466
480
|
],
|
|
467
481
|
fields,
|
|
468
482
|
};
|
|
@@ -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 {
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
135
|
+
export function getSelectedCloudflareComponents(controller: OnboardingWizardControllerLike): CloudflareComponentSelection {
|
|
136
136
|
return getCloudflareComponentSelection(controller);
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
export function getCloudflareBatchMode(controller:
|
|
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:
|
|
149
|
+
export function buildCloudflareProvisionRequest(controller: OnboardingWizardControllerLike, options: {
|
|
150
150
|
readonly includeTransientSecrets?: boolean;
|
|
151
151
|
} = {}): CloudflareProvisionRequest {
|
|
152
152
|
const components = getCloudflareComponentSelection(controller);
|
|
@@ -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
|
|
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 {
|
|
10
|
+
import type { OnboardingWizardControllerLike } from './onboarding-wizard-types.ts';
|
|
11
11
|
|
|
12
12
|
export function getSharedIpDefault(
|
|
13
|
-
controller:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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 {
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|