@pellux/goodvibes-agent 0.1.56 → 0.1.58

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 (95) hide show
  1. package/.goodvibes/GOODVIBES.md +1 -1
  2. package/CHANGELOG.md +18 -9
  3. package/README.md +3 -3
  4. package/docs/README.md +1 -1
  5. package/docs/getting-started.md +3 -3
  6. package/docs/release-and-publishing.md +2 -2
  7. package/package.json +1 -3
  8. package/src/agent/routine-schedule-args.ts +219 -0
  9. package/src/agent/routine-schedule-format.ts +173 -0
  10. package/src/agent/routine-schedule-promotion.ts +3 -811
  11. package/src/agent/routine-schedule-receipts.ts +502 -0
  12. package/src/cli/agent-knowledge-command.ts +6 -6
  13. package/src/cli/help.ts +3 -25
  14. package/src/cli/package-verification.ts +23 -16
  15. package/src/cli/redaction.ts +4 -1
  16. package/src/cli/routines-command.ts +10 -6
  17. package/src/cli/service-posture.ts +47 -280
  18. package/src/cli/status.ts +0 -1
  19. package/src/cli/tui-startup.ts +23 -0
  20. package/src/config/secret-config.ts +0 -2
  21. package/src/input/agent-workspace-categories.ts +219 -0
  22. package/src/input/agent-workspace-editors.ts +143 -0
  23. package/src/input/agent-workspace-snapshot.ts +265 -0
  24. package/src/input/agent-workspace-types.ts +142 -0
  25. package/src/input/agent-workspace.ts +22 -766
  26. package/src/input/commands/agent-runtime-profile-runtime.ts +1 -1
  27. package/src/input/commands/delegation-runtime.ts +1 -1
  28. package/src/input/commands/experience-runtime.ts +3 -4
  29. package/src/input/commands/guidance-runtime.ts +1 -2
  30. package/src/input/commands/health-runtime.ts +3 -65
  31. package/src/input/commands/knowledge.ts +7 -7
  32. package/src/input/commands/local-setup-review.ts +0 -61
  33. package/src/input/commands/local-setup-transfer.ts +0 -3
  34. package/src/input/commands/local-setup.ts +2 -15
  35. package/src/input/commands/planning-runtime.ts +4 -1
  36. package/src/input/commands/platform-access-runtime.ts +1 -10
  37. package/src/input/commands/platform-services-runtime.ts +0 -1
  38. package/src/input/commands/recall-query.ts +1 -1
  39. package/src/input/commands/routines-runtime.ts +10 -6
  40. package/src/input/commands/schedule-runtime.ts +10 -6
  41. package/src/input/commands/session-workflow.ts +1 -1
  42. package/src/input/commands/tasks-runtime.ts +1 -14
  43. package/src/input/commands.ts +0 -4
  44. package/src/input/handler-onboarding.ts +10 -120
  45. package/src/input/onboarding/onboarding-wizard-apply.ts +5 -196
  46. package/src/input/onboarding/onboarding-wizard-constants.ts +8 -119
  47. package/src/input/onboarding/onboarding-wizard-helpers.ts +2 -53
  48. package/src/input/onboarding/onboarding-wizard-rules.ts +2 -236
  49. package/src/input/onboarding/onboarding-wizard-state.ts +1 -69
  50. package/src/input/onboarding/onboarding-wizard-steps.ts +584 -737
  51. package/src/input/onboarding/onboarding-wizard-types.ts +8 -26
  52. package/src/input/onboarding/onboarding-wizard.ts +4 -109
  53. package/src/input/settings-modal-agent-policy.ts +10 -0
  54. package/src/input/settings-modal-types.ts +2 -4
  55. package/src/input/settings-modal.ts +3 -1
  56. package/src/input/submission-router.ts +0 -1
  57. package/src/main.ts +13 -12
  58. package/src/panels/approval-panel.ts +1 -2
  59. package/src/panels/builtin/operations.ts +1 -2
  60. package/src/panels/knowledge-panel.ts +2 -2
  61. package/src/panels/project-planning-panel.ts +4 -1
  62. package/src/panels/provider-health-domains.ts +0 -22
  63. package/src/panels/provider-health-panel.ts +1 -5
  64. package/src/panels/session-browser-panel.ts +0 -5
  65. package/src/panels/tasks-panel.ts +2 -64
  66. package/src/renderer/agent-workspace.ts +1 -1
  67. package/src/renderer/help-overlay.ts +1 -2
  68. package/src/renderer/semantic-diff.ts +1 -1
  69. package/src/renderer/settings-modal-helpers.ts +0 -16
  70. package/src/renderer/settings-modal.ts +3 -5
  71. package/src/runtime/bootstrap-hook-bridge.ts +0 -3
  72. package/src/runtime/bootstrap-shell.ts +2 -1
  73. package/src/runtime/bootstrap.ts +1 -1
  74. package/src/runtime/index.ts +0 -1
  75. package/src/runtime/onboarding/derivation.ts +1 -28
  76. package/src/runtime/onboarding/snapshot.ts +0 -1
  77. package/src/runtime/onboarding/types.ts +1 -4
  78. package/src/runtime/services.ts +4 -23
  79. package/src/runtime/ui-read-models.ts +4 -3
  80. package/src/shell/service-settings-sync.ts +15 -244
  81. package/src/tools/agent-context-policy.ts +1 -1
  82. package/src/tools/wrfc-agent-guard.ts +3 -3
  83. package/src/verification/live-verifier.ts +11 -5
  84. package/src/verification/verification-ledger.ts +3 -6
  85. package/src/version.ts +1 -1
  86. package/src/input/commands/agent-externalized-tui.ts +0 -73
  87. package/src/input/commands/cloudflare-runtime.ts +0 -385
  88. package/src/input/handler-onboarding-cloudflare.ts +0 -322
  89. package/src/input/onboarding/onboarding-runtime-status.ts +0 -87
  90. package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +0 -494
  91. package/src/input/onboarding/onboarding-wizard-cloudflare.ts +0 -199
  92. package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +0 -130
  93. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +0 -762
  94. package/src/runtime/cloudflare-control-plane.ts +0 -350
  95. package/src/runtime/sandbox-public-gaps.ts +0 -358
@@ -1,798 +1,645 @@
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 { shouldShowCloudflareStep } from './onboarding-wizard-cloudflare.ts';
3
- import { buildCloudflareStep } from './onboarding-wizard-cloudflare-step.ts';
4
- import {
5
- EXTERNAL_SURFACE_SPECS,
6
- getExternalSurfaceAutoStartDefaultValue,
7
- getExternalSurfaceAutoStartFieldId,
8
- isExternalSurfaceSelectedByDefault,
9
- type ExternalSurfaceSpec,
10
- } from './onboarding-wizard-external-surfaces.ts';
11
- import { countSelected, modelSelectionLabel, normalizeText } from './onboarding-wizard-helpers.ts';
1
+ import { REASONING_OPTIONS, HITL_MODE_OPTIONS, GUIDANCE_MODE_OPTIONS, PERMISSION_MODE_OPTIONS, SECRET_POLICY_OPTIONS } from './onboarding-wizard-constants.ts';
2
+ import { modelSelectionLabel, normalizeText } from './onboarding-wizard-helpers.ts';
12
3
  import type { OnboardingWizardController } from './onboarding-wizard.ts';
13
- import type { OnboardingWizardAcknowledgementFieldDefinition, OnboardingWizardActionFieldDefinition, OnboardingWizardChecklistFieldDefinition, OnboardingWizardExternalSurfaceStepId, OnboardingWizardFieldDefinition, OnboardingWizardModelPickerFieldDefinition, OnboardingWizardRadioFieldDefinition, OnboardingWizardRadioOption, OnboardingWizardStepDefinition } from './onboarding-wizard-types.ts';
4
+ import type { OnboardingWizardActionFieldDefinition, OnboardingWizardFieldDefinition, OnboardingWizardModelPickerFieldDefinition, OnboardingWizardRadioFieldDefinition, OnboardingWizardStepDefinition } from './onboarding-wizard-types.ts';
14
5
 
15
6
  export function buildOnboardingWizardSteps(controller: OnboardingWizardController): readonly OnboardingWizardStepDefinition[] {
16
7
  if (controller.hydrationPending || controller.hydrationError !== null) return [buildLoadingStep(controller)];
17
8
 
18
- const capabilities = controller.getCapabilitySelectionState();
19
- const hasServers = capabilities.some((item) => item.id !== 'local-tui-only' && item.selected);
20
- const wantsExternalServices = capabilities.some((item) => item.id === 'external-integrations' && item.selected);
21
- const steps: OnboardingWizardStepDefinition[] = [
22
- buildCapabilitiesStep(controller),
23
- ];
24
- if (hasServers) {
25
- steps.push(buildNetworkStep(controller));
26
- }
27
- if (hasServers || controller.hasExistingAccessState()) {
28
- steps.push(buildAccessStep(controller));
29
- }
30
- if (wantsExternalServices) {
31
- steps.push(buildExternalServicesStep(controller));
32
- for (const surface of getSelectedExternalSurfaceSpecs(controller)) {
33
- steps.push(buildExternalSurfaceStep(controller, surface));
34
- }
35
- }
36
- if (shouldShowCloudflareStep(controller)) {
37
- steps.push(buildCloudflareStep(controller));
38
- }
39
- steps.push(buildProviderAccessStep(controller));
40
- steps.push(buildDefaultModelStep(controller));
41
- steps.push(buildExperienceStep(controller));
42
- steps.push(buildReviewStep(controller));
43
- return steps.map(addApplyAndContinueAction);
9
+ return [
10
+ buildAgentSetupStep(controller),
11
+ buildProviderAccessStep(controller),
12
+ buildDefaultModelStep(controller),
13
+ buildCommunicationStep(),
14
+ buildToolsStep(),
15
+ buildAgentKnowledgeStep(),
16
+ buildLocalStateStep(),
17
+ buildAutomationStep(),
18
+ buildVoiceMediaStep(),
19
+ buildDelegationPolicyStep(),
20
+ buildExperienceStep(controller),
21
+ buildReviewStep(controller),
22
+ ].map(addApplyAndContinueAction);
44
23
  }
45
24
 
46
25
  function buildApplyAndContinueAction(step: OnboardingWizardStepDefinition): OnboardingWizardActionFieldDefinition {
47
- return {
48
- kind: 'action',
49
- id: `${step.id}.apply-and-continue`,
50
- action: 'apply-and-continue',
51
- label: 'Apply & Continue To Next Section',
52
- hint: 'Save the current wizard selections in this onboarding session and move to the next section. Settings are persisted on the final Review apply.',
53
- defaultValue: 'Apply & next',
54
- spacerBeforeRows: 2,
55
- };
56
- }
26
+ return {
27
+ kind: 'action',
28
+ id: `${step.id}.apply-and-continue`,
29
+ action: 'apply-and-continue',
30
+ label: 'Apply & Continue To Next Section',
31
+ hint: 'Save the current wizard selections in this onboarding session and move to the next section. Settings are persisted on the final Review apply.',
32
+ defaultValue: 'Apply & next',
33
+ spacerBeforeRows: 2,
34
+ };
35
+ }
57
36
 
58
37
  function addApplyAndContinueAction(step: OnboardingWizardStepDefinition): OnboardingWizardStepDefinition {
59
- if (step.id === 'loading' || step.id === 'review') return step;
60
- return {
61
- ...step,
62
- fields: [
63
- ...step.fields,
64
- buildApplyAndContinueAction(step),
65
- ],
66
- };
67
- }
68
-
69
- export function buildLoadingStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
70
- const failed = controller.hydrationError !== null;
71
- return {
72
- id: 'loading',
73
- title: failed ? 'Current settings unavailable' : 'Loading current settings',
74
- shortLabel: 'Loading',
75
- description: failed
76
- ? 'The wizard is locked because current runtime settings could not be collected. Close and reopen onboarding after fixing the reported issue.'
77
- : 'Collecting the current daemon, listener, provider, subscription, auth, and surface settings before the wizard becomes editable.',
78
- summaryTitle: failed ? 'Preload failed' : 'Preload required',
79
- summaryLines: [
80
- failed
81
- ? 'Editable fields remain locked to avoid applying defaults over existing configuration.'
82
- : 'Editable fields are locked until runtime settings are loaded.',
83
- failed
84
- ? controller.hydrationError ?? 'Unknown snapshot failure.'
85
- : 'This prevents the wizard from applying defaults over existing configuration.',
86
- ],
87
- fields: [
88
- {
89
- kind: 'status',
90
- id: 'loading.runtime-snapshot',
91
- label: failed ? 'Runtime settings snapshot failed' : 'Runtime settings snapshot',
92
- hint: failed
93
- ? controller.hydrationError ?? 'The runtime snapshot did not complete.'
94
- : 'Waiting for the current GoodVibes configuration and account state.',
95
- defaultValue: failed ? 'Locked' : 'Loading',
96
- },
97
- ],
98
- };
99
- }
38
+ if (step.id === 'loading' || step.id === 'review') return step;
39
+ return {
40
+ ...step,
41
+ fields: [...step.fields, buildApplyAndContinueAction(step)],
42
+ };
43
+ }
100
44
 
101
- export function buildCapabilitiesStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
102
- const capabilities = controller.getCapabilitySelectionState();
103
- const selectedCount = countSelected(capabilities);
104
- const fields: OnboardingWizardFieldDefinition[] = [
105
- ...capabilities.map((capability) => ({
106
- kind: 'checklist' as const,
107
- id: `capabilities.${capability.id}`,
108
- capabilityId: capability.id,
109
- label: capability.label,
110
- hint: capability.detail,
111
- defaultValue: capability.selected,
112
- })),
45
+ export function buildCommunicationStep(): OnboardingWizardStepDefinition {
46
+ return {
47
+ id: 'agent-communication',
48
+ title: 'Channels and notifications',
49
+ shortLabel: 'Channels',
50
+ description: 'Prepare the Agent for companion pairing, messaging-channel awareness, notification delivery, and safe outbound communication without owning daemon listeners.',
51
+ summaryTitle: 'Communication posture',
52
+ summaryLines: [
53
+ 'Companion chat: paired through the external GoodVibes service',
54
+ 'Channel accounts: inspect readiness before using them',
55
+ 'Outbound messages: explicit user action only',
56
+ ],
57
+ fields: [
113
58
  {
114
- kind: 'action',
115
- id: 'capabilities.select-all',
116
- action: 'select-all-capabilities',
117
- label: 'Review external-daemon surfaces',
118
- hint: 'Review browser, LAN, webhooks/events, and external app surfaces without letting Agent own daemon lifecycle.',
119
- defaultValue: 'Action',
59
+ kind: 'status',
60
+ id: 'agent-communication.companion',
61
+ label: 'Companion pairing',
62
+ hint: 'Use /pair from the Agent workspace to pair companion clients through the already-running service.',
63
+ defaultValue: 'External service route',
120
64
  },
121
65
  {
122
- kind: 'action',
123
- id: 'capabilities.clear',
124
- action: 'clear-capabilities',
125
- label: 'Keep Agent local-only',
126
- hint: 'Clear external-daemon surfaces and keep Agent work in this terminal conversation.',
127
- defaultValue: 'Action',
66
+ kind: 'status',
67
+ id: 'agent-communication.channels',
68
+ label: 'Messaging channels',
69
+ hint: 'Use the Channels workspace to inspect account readiness, delivery posture, and recent communication without changing listener or service lifecycle.',
70
+ defaultValue: 'Inspectable',
128
71
  },
129
- ];
130
-
131
- return {
132
- id: 'capabilities',
133
- title: 'Choose Agent surfaces',
134
- shortLabel: 'Surfaces',
135
- description: 'Choose what Agent should prepare locally. Daemon-backed surfaces are reviewed as external dependencies; Agent does not enable service mode or autostart.',
136
- summaryTitle: 'Selected surfaces',
137
- summaryLines: [
138
- `${selectedCount}/${capabilities.length} surface option(s) selected`,
139
- `Mode: ${controller.mode === 'edit' ? 'edit existing shell state' : controller.mode === 'reopen' ? 'reopen review flow' : 'new setup'}`,
140
- controller.runtimeSnapshot?.collectionIssues.length
141
- ? `${controller.runtimeSnapshot.collectionIssues.length} runtime collection issue(s)`
142
- : 'Runtime snapshot collected cleanly',
143
- ],
144
- fields,
145
- };
146
- }
147
-
148
- export function buildProvidersStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
149
- const providerAck = controller.runtimeDerived.reopenEditAcknowledgements.providers;
150
- const activeSubscriptions = controller.runtimeSnapshot?.subscriptions.active ?? [];
151
- const pendingSubscriptions = controller.runtimeSnapshot?.subscriptions.pending ?? [];
152
- const openAiActive = activeSubscriptions.some((subscription) => subscription.provider === 'openai');
153
- const openAiPending = pendingSubscriptions.some((subscription) => subscription.provider === 'openai');
154
- const providerSecretCount = controller.runtimeSnapshot?.secrets.records.filter((record) => record.key.endsWith('_API_KEY') || record.key.endsWith('_TOKEN')).length ?? 0;
155
- const openAiApiKeyConfigured = controller.runtimeSnapshot?.secrets.records.some((record) => record.key === 'OPENAI_API_KEY') ?? false;
156
- const providerReviewField: OnboardingWizardAcknowledgementFieldDefinition = {
157
- kind: 'acknowledgement',
158
- id: 'providers.reviewed',
159
- label: 'Confirm provider access review',
160
- hint: providerAck.detail,
161
- defaultValue: providerAck.accepted,
162
- required: controller.mode !== 'new' && providerAck.required,
163
- reason: providerAck.reason,
164
- target: 'providers',
165
- };
166
-
167
- const fields: OnboardingWizardFieldDefinition[] = [
168
72
  {
169
73
  kind: 'status',
170
- id: 'providers.openai-subscription',
171
- label: 'OpenAI subscription status',
172
- hint: openAiActive
173
- ? 'An OpenAI subscription session is already available.'
174
- : openAiPending
175
- ? 'An OpenAI subscription login is pending.'
176
- : 'No OpenAI subscription session was found in the current runtime state.',
177
- defaultValue: openAiActive ? 'Active' : openAiPending ? 'Pending' : 'Not detected',
74
+ id: 'agent-communication.notifications',
75
+ label: 'Notification delivery',
76
+ hint: 'Routine, approval, and work-plan notifications require an explicit delivery target and command; Agent never silently sends external messages.',
77
+ defaultValue: 'Explicit only',
178
78
  },
179
79
  {
180
80
  kind: 'status',
181
- id: 'providers.api-key-inventory',
182
- label: 'Provider API key inventory',
183
- hint: providerSecretCount > 0
184
- ? `${providerSecretCount} provider credential reference(s) were found. Values stay masked.`
185
- : 'No provider API key references were detected in the current runtime state.',
186
- defaultValue: providerSecretCount > 0 ? `${providerSecretCount} configured` : 'None detected',
187
- },
188
- {
189
- kind: 'masked',
190
- id: 'providers.openai-api-key',
191
- label: 'OpenAI API key',
192
- hint: openAiApiKeyConfigured
193
- ? 'An OpenAI API key is already stored. Leave blank to keep it; enter a new key to replace it through the secret manager.'
194
- : 'Optional: enter an OpenAI API key now. The value is stored through the secret manager, not in config.',
195
- placeholder: openAiApiKeyConfigured ? 'already configured' : 'sk-...',
196
- defaultValue: '',
197
- },
198
- ...(openAiActive ? [] : [
199
- {
200
- kind: 'action' as const,
201
- id: 'providers.openai-subscription-start',
202
- action: 'start-openai-subscription' as const,
203
- label: openAiPending ? 'Restart OpenAI subscription sign-in' : 'Start OpenAI subscription sign-in',
204
- hint: 'Opens the OpenAI sign-in flow from the wizard and records pending login state here.',
205
- defaultValue: openAiPending ? 'Restart' : 'Start',
206
- },
207
- ...(openAiPending ? [
208
- {
209
- kind: 'text' as const,
210
- id: 'providers.openai-authorization-url',
211
- label: 'OpenAI authorization URL',
212
- hint: 'If the browser did not open, use this URL to continue sign-in without leaving the wizard.',
213
- placeholder: 'authorization URL appears after start',
214
- defaultValue: '',
215
- },
216
- {
217
- kind: 'text' as const,
218
- id: 'providers.openai-callback-code',
219
- label: 'OpenAI callback code or URL',
220
- hint: 'Paste the callback code or redirected URL after completing browser sign-in.',
221
- placeholder: 'code or callback URL',
222
- defaultValue: '',
223
- },
224
- {
225
- kind: 'action' as const,
226
- id: 'providers.openai-subscription-finish',
227
- action: 'finish-openai-subscription' as const,
228
- label: 'Finish OpenAI subscription sign-in',
229
- hint: 'Completes the pending OpenAI subscription login using the code above.',
230
- defaultValue: 'Finish',
231
- },
232
- ] : []),
233
- ]),
234
- providerReviewField,
235
- ];
236
-
237
- return {
238
- id: 'provider-access',
239
- title: 'AI provider access',
240
- shortLabel: 'Providers',
241
- description: 'Review subscription posture and optionally add an OpenAI API key directly through the wizard.',
242
- summaryTitle: 'Provider access summary',
243
- summaryLines: [
244
- `OpenAI subscription: ${openAiActive ? 'active' : openAiPending ? 'pending' : 'not detected'}`,
245
- `OpenAI API key: ${openAiApiKeyConfigured ? 'configured' : 'not detected'}`,
246
- `Provider credential references: ${providerSecretCount}`,
247
- `Review: ${controller.getFieldValueLabel(providerReviewField)}`,
248
- ],
249
- fields,
250
- };
251
- }
252
-
253
- export function buildProviderAccessStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
254
- return buildProvidersStep(controller);
255
- }
256
-
257
- export function buildDefaultModelStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
258
- const routing = controller.runtimeSnapshot?.providerRouting;
259
- const primarySelectionField: OnboardingWizardModelPickerFieldDefinition = {
260
- kind: 'modelPicker',
261
- id: 'default-model.primary-model',
262
- label: 'Default provider + model',
263
- hint: 'Open the nested model picker for the shell’s main routing target.',
264
- target: 'main',
265
- defaultSelection: {
266
- providerId: normalizeText(routing?.primaryProviderId),
267
- modelId: normalizeText(routing?.primaryModelId),
268
- enabled: true,
269
- },
270
- };
271
- const reasoningField: OnboardingWizardRadioFieldDefinition = {
272
- kind: 'radio',
273
- id: 'default-model.reasoning',
274
- label: 'Reasoning effort',
275
- hint: 'Use the shell reasoning default that matches the current provider routing.',
276
- options: REASONING_OPTIONS,
277
- defaultValue: normalizeText(routing?.primaryReasoningEffort) || 'medium',
278
- };
279
-
280
- return {
281
- id: 'default-model',
282
- title: 'Default model',
283
- shortLabel: 'Model',
284
- description: 'Choose the default model routing the shell should use after onboarding.',
285
- summaryTitle: 'Default model summary',
286
- summaryLines: [
287
- `Main: ${modelSelectionLabel(controller.modelSelectionState.get('main') ?? primarySelectionField.defaultSelection)}`,
288
- `Reasoning: ${controller.getFieldValueLabel(reasoningField)}`,
289
- ],
290
- fields: [
291
- primarySelectionField,
292
- reasoningField,
293
- ],
294
- };
295
- }
81
+ id: 'agent-communication.inbound-policy',
82
+ label: 'Inbound command policy',
83
+ hint: 'Incoming channel commands stay constrained by daemon-side policy, allowlists, and account posture.',
84
+ defaultValue: 'Policy gated',
85
+ },
86
+ ],
87
+ };
88
+ }
296
89
 
297
- export function buildExternalServicesStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
298
- const selectedCount = EXTERNAL_SURFACE_SPECS
299
- .filter((surface) => controller.getBooleanFieldValue(
300
- surface.enabledFieldId,
301
- isExternalSurfaceSelectedByDefault(surface, controller.runtimeSnapshot),
302
- ))
303
- .length;
304
- const fields: OnboardingWizardFieldDefinition[] = [];
90
+ export function buildToolsStep(): OnboardingWizardStepDefinition {
91
+ return {
92
+ id: 'agent-tools',
93
+ title: 'Tools and MCP',
94
+ shortLabel: 'Tools',
95
+ description: 'Review tool access for the Agent operator: MCP servers, browser/media helpers, safe read-only inspection, and explicit approval before side effects.',
96
+ summaryTitle: 'Tool posture',
97
+ summaryLines: [
98
+ 'MCP and tools: inspect before use',
99
+ 'Read/search/summarize: safe by default',
100
+ 'Writes, installs, external sends, and service changes: require explicit user action',
101
+ ],
102
+ fields: [
103
+ {
104
+ kind: 'status',
105
+ id: 'agent-tools.mcp',
106
+ label: 'MCP servers and tools',
107
+ hint: 'Use /mcp servers and the Agent workspace Tools area to inspect connected servers, roles, and tool readiness.',
108
+ defaultValue: 'Inspectable',
109
+ },
110
+ {
111
+ kind: 'status',
112
+ id: 'agent-tools.browser-media',
113
+ label: 'Browser and media helpers',
114
+ hint: 'Browser, image, audio, and file helpers are capability surfaces. Agent uses them only when the current task needs them and policy allows it.',
115
+ defaultValue: 'Task scoped',
116
+ },
117
+ {
118
+ kind: 'status',
119
+ id: 'agent-tools.approval-boundary',
120
+ label: 'Power action boundary',
121
+ hint: 'Workspace writes, package installs, external sends, account changes, and service changes require an explicit command or confirmation.',
122
+ defaultValue: 'Approval required',
123
+ },
124
+ {
125
+ kind: 'status',
126
+ id: 'agent-tools.no-hidden-work',
127
+ label: 'Hidden work policy',
128
+ hint: 'Tool use stays visible in the main Agent conversation or explicit command surface; no hidden background work is started from onboarding.',
129
+ defaultValue: 'Visible',
130
+ },
131
+ ],
132
+ };
133
+ }
305
134
 
306
- for (const surface of EXTERNAL_SURFACE_SPECS) {
307
- fields.push({
308
- kind: 'checklist',
309
- id: surface.enabledFieldId,
310
- label: surface.label,
311
- hint: `${surface.hint} Selecting this opens a dedicated setup screen; auto-start is chosen on that screen.`,
312
- defaultValue: isExternalSurfaceSelectedByDefault(surface, controller.runtimeSnapshot),
313
- });
314
- }
135
+ export function buildLoadingStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
136
+ const failed = controller.hydrationError !== null;
137
+ return {
138
+ id: 'loading',
139
+ title: failed ? 'Current settings unavailable' : 'Loading Agent setup',
140
+ shortLabel: 'Loading',
141
+ description: failed
142
+ ? 'The wizard is locked because current Agent settings could not be collected. Close and reopen onboarding after fixing the reported issue.'
143
+ : 'Collecting current Agent settings before the setup workspace becomes editable.',
144
+ summaryTitle: failed ? 'Preload failed' : 'Preload required',
145
+ summaryLines: [
146
+ failed ? controller.hydrationError ?? 'Unknown snapshot failure.' : 'Editable fields are locked until Agent settings are loaded.',
147
+ failed ? 'No setup values were changed.' : 'This prevents the wizard from applying defaults over existing Agent configuration.',
148
+ ],
149
+ fields: [
150
+ {
151
+ kind: 'status',
152
+ id: 'loading.runtime-snapshot',
153
+ label: failed ? 'Agent settings snapshot failed' : 'Agent settings snapshot',
154
+ hint: failed ? controller.hydrationError ?? 'The setup snapshot did not complete.' : 'Waiting for current Agent account, provider, and local setup state.',
155
+ defaultValue: failed ? 'Locked' : 'Loading',
156
+ },
157
+ ],
158
+ };
159
+ }
315
160
 
316
- fields.push(
161
+ export function buildAgentSetupStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
162
+ const collectionIssues = controller.runtimeSnapshot?.collectionIssues.length ?? 0;
163
+ const secretPolicy = controller.runtimeSnapshot?.runtimeDefaults.secretStoragePolicy ?? 'preferred_secure';
164
+ return {
165
+ id: 'agent-setup',
166
+ title: 'Agent setup',
167
+ shortLabel: 'Agent',
168
+ description: 'Set up the Agent operator workspace: local identity, provider access, isolated Agent Knowledge, reusable local behavior, and explicit build delegation.',
169
+ summaryTitle: 'Agent setup posture',
170
+ summaryLines: [
171
+ 'Agent owns the operator TUI and local behavior registry.',
172
+ 'GoodVibes service lifecycle is external to this product.',
173
+ `Secret policy: ${controller.getStringFieldValue('agent-setup.secret-policy', secretPolicy)}`,
174
+ collectionIssues > 0 ? `${collectionIssues} setup snapshot issue(s)` : 'Setup snapshot collected cleanly',
175
+ ],
176
+ fields: [
317
177
  {
318
- kind: 'action',
319
- id: 'external-services.select-all',
320
- action: 'select-all-external-surfaces',
321
- label: 'Select all external surfaces',
322
- hint: 'Show setup screens for every supported external surface. Auto-start stays controlled per surface.',
323
- defaultValue: 'Action',
178
+ kind: 'status',
179
+ id: 'agent-setup.identity',
180
+ label: 'Product identity',
181
+ hint: 'GoodVibes Agent is a personal operator TUI with Agent-local profiles, memory, skills, personas, routines, and isolated Agent Knowledge.',
182
+ defaultValue: 'Agent operator',
324
183
  },
325
184
  {
326
- kind: 'action',
327
- id: 'external-services.clear',
328
- action: 'clear-external-surfaces',
329
- label: 'Clear all external surfaces',
330
- hint: 'Hide all external surface setup screens. The HTTP listener can still be enabled separately by webhook/event settings.',
331
- defaultValue: 'Action',
185
+ kind: 'status',
186
+ id: 'agent-setup.connection',
187
+ label: 'GoodVibes service connection',
188
+ hint: collectionIssues > 0
189
+ ? `${collectionIssues} setup snapshot issue(s) were reported. Status and doctor commands show connection details.`
190
+ : 'Agent connects to an already-running GoodVibes service for companion chat, work plans, approvals, automation, and Agent Knowledge.',
191
+ defaultValue: collectionIssues > 0 ? `${collectionIssues} issue(s)` : 'External service',
332
192
  },
333
193
  {
334
194
  kind: 'radio',
335
- id: 'external-services.secret-policy',
195
+ id: 'agent-setup.secret-policy',
336
196
  label: 'Secret storage policy',
337
- hint: 'Choose how selected surface secrets should be stored. Secret values are never shown in the wizard.',
197
+ hint: 'Choose how Agent setup should store provider keys. Secret values are never shown in the wizard.',
338
198
  options: SECRET_POLICY_OPTIONS,
339
- defaultValue: controller.runtimeSnapshot?.runtimeDefaults.secretStoragePolicy ?? 'preferred_secure',
199
+ defaultValue: secretPolicy,
340
200
  },
341
- );
342
-
343
- return {
344
- id: 'external-services',
345
- title: 'Choose external surfaces',
346
- shortLabel: 'Services',
347
- description: 'Select the apps and integration surfaces GoodVibes should prepare. Each selected surface gets its own setup screen and its own auto-start choice.',
348
- summaryTitle: 'External surfaces',
349
- summaryLines: [
350
- `${selectedCount} external surface(s) selected for setup`,
351
- `Secret policy: ${controller.getStringFieldValue('external-services.secret-policy', controller.runtimeSnapshot?.runtimeDefaults.secretStoragePolicy ?? 'preferred_secure')}`,
352
- selectedCount > 0 ? 'Selected surfaces appear as separate setup screens.' : 'No external surfaces selected.',
353
- ],
354
- fields,
355
- };
356
- }
357
-
358
- function getSelectedExternalSurfaceSpecs(controller: OnboardingWizardController): readonly ExternalSurfaceSpec[] {
359
- return EXTERNAL_SURFACE_SPECS.filter((surface) => (
360
- controller.getBooleanFieldValue(
361
- surface.enabledFieldId,
362
- isExternalSurfaceSelectedByDefault(surface, controller.runtimeSnapshot),
363
- )
364
- ));
365
- }
366
-
367
- const SURFACE_AUTO_START_OPTIONS: readonly OnboardingWizardRadioOption[] = [
368
- {
369
- id: 'yes',
370
- label: 'Yes',
371
- hint: 'Save the surface as enabled; the external daemon/service owner controls actual startup.',
372
- },
373
- {
374
- id: 'no',
375
- label: 'No',
376
- hint: 'Save these settings but leave the surface idle until it is enabled from Settings > Surfaces.',
377
- },
378
- ];
379
-
380
- function buildExternalSurfaceStep(
381
- controller: OnboardingWizardController,
382
- surface: ExternalSurfaceSpec,
383
- ): OnboardingWizardStepDefinition {
384
- let setupCount = 0;
385
- let setupCompleteCount = 0;
386
- const autoStartFieldId = getExternalSurfaceAutoStartFieldId(surface);
387
- const autoStartDefault = getExternalSurfaceAutoStartDefaultValue(surface, controller.runtimeSnapshot);
388
- const autoStartValue = controller.getStringFieldValue(autoStartFieldId, autoStartDefault);
389
- const setupFields = surface.fields.map((setupField): OnboardingWizardFieldDefinition => {
390
- const suggested = controller.isRequiredExternalSetupField(setupField.id);
391
- if (suggested) {
392
- setupCount += 1;
393
- if (normalizeText(setupField.defaultValue(controller.runtimeSnapshot)).length > 0
394
- || normalizeText(controller.getStringFieldValue(setupField.id, '')).length > 0) {
395
- setupCompleteCount += 1;
396
- }
397
- }
398
-
399
- const hint = suggested
400
- ? `${setupField.hint} Recommended because ${surface.label} is selected, but it will not block saving.`
401
- : setupField.hint;
402
-
403
- if (setupField.kind === 'radio') {
404
- return {
405
- kind: 'radio',
406
- id: setupField.id,
407
- label: setupField.label,
408
- hint,
409
- options: setupField.options ?? [],
410
- defaultValue: setupField.defaultValue(controller.runtimeSnapshot),
411
- };
412
- }
413
-
414
- return {
415
- kind: setupField.kind,
416
- id: setupField.id,
417
- label: setupField.label,
418
- hint,
419
- placeholder: setupField.placeholder,
420
- defaultValue: setupField.defaultValue(controller.runtimeSnapshot),
421
- };
422
- });
423
- const ntfyTopicSummary = surface.id === 'ntfy'
424
- ? [
425
- `Chat topic: ${controller.getStringFieldValue('external-services.ntfy.chat-topic', 'goodvibes-chat')}`,
426
- `Agent topic: ${controller.getStringFieldValue('external-services.ntfy.agent-topic', 'goodvibes-agent')}`,
427
- `Daemon-only remote topic: ${controller.getStringFieldValue('external-services.ntfy.remote-topic', 'goodvibes-ntfy')}`,
428
- ]
429
- : [];
430
- const title = `${surface.label.replace(/ surface$/i, '')} setup`;
431
- const setupSummary = setupCount === 0
432
- ? 'Suggested setup: none'
433
- : `Suggested setup entered: ${setupCompleteCount}/${setupCount}`;
434
-
435
- return {
436
- id: `external-surface:${surface.id}` as OnboardingWizardExternalSurfaceStepId,
437
- title,
438
- shortLabel: surface.label.replace(/ surface$/i, ''),
439
- description: `Configure ${surface.label}. Settings are saved either way; Agent does not start or own the external daemon service.`,
440
- summaryTitle: `${surface.label} setup`,
441
- summaryLines: [
442
- `External activation requested: ${autoStartValue === 'yes' ? 'yes' : 'no'}`,
443
- ...ntfyTopicSummary,
444
- setupSummary,
445
- `Secret policy: ${controller.getStringFieldValue('external-services.secret-policy', controller.runtimeSnapshot?.runtimeDefaults.secretStoragePolicy ?? 'preferred_secure')}`,
446
- autoStartValue === 'yes'
447
- ? 'Agent will save the requested enabled state, but daemon lifecycle remains external.'
448
- : 'Enable it later from Settings > Surfaces after the external daemon is ready.',
449
- ],
450
- fields: [
451
- {
452
- kind: 'radio',
453
- id: autoStartFieldId,
454
- label: 'Request external activation',
455
- hint: `Yes saves ${surface.enabledConfigKey}. No saves setup values but keeps the surface off until Settings > Surfaces enables it.`,
456
- options: SURFACE_AUTO_START_OPTIONS,
457
- defaultValue: autoStartDefault,
458
- },
459
- ...setupFields,
460
- ],
461
- };
462
- }
463
-
464
- export function buildAccessStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
465
- const step = buildAccountsStep(controller);
466
- return {
467
- ...step,
468
- id: 'access',
469
- title: 'Access and accounts',
470
- shortLabel: 'Access',
471
- };
472
- }
201
+ {
202
+ kind: 'status',
203
+ id: 'agent-setup.profile-guide',
204
+ label: 'Runtime profiles',
205
+ hint: 'Use /agent-profile guide after setup to create household, research, travel, operations, or custom Agent profiles.',
206
+ defaultValue: 'Local profiles',
207
+ },
208
+ ],
209
+ };
210
+ }
473
211
 
474
- export function buildExperienceStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
475
- return {
476
- id: 'experience',
477
- title: 'Shell experience',
478
- shortLabel: 'Experience',
479
- description: 'Tune review noise, guidance, and permission posture for day-to-day use.',
480
- summaryTitle: 'Experience posture',
481
- summaryLines: [
482
- `Human-in-the-Loop (HITL): ${controller.getStringFieldValue('experience.hitl', controller.runtimeSnapshot?.runtimeDefaults.behavior.hitlMode ?? 'balanced')}`,
483
- `Guidance: ${controller.getStringFieldValue('experience.guidance', controller.runtimeSnapshot?.runtimeDefaults.behavior.guidanceMode ?? 'minimal')}`,
484
- `Permissions: ${controller.getStringFieldValue('experience.permissions', controller.runtimeSnapshot?.runtimeDefaults.permissionsMode ?? 'prompt')}`,
485
- ],
486
- fields: [
212
+ export function buildProviderAccessStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
213
+ const activeSubscriptions = controller.runtimeSnapshot?.subscriptions.active ?? [];
214
+ const pendingSubscriptions = controller.runtimeSnapshot?.subscriptions.pending ?? [];
215
+ const openAiActive = activeSubscriptions.some((subscription) => subscription.provider === 'openai');
216
+ const openAiPending = pendingSubscriptions.some((subscription) => subscription.provider === 'openai');
217
+ const providerSecretCount = controller.runtimeSnapshot?.secrets.records.filter((record) => record.key.endsWith('_API_KEY') || record.key.endsWith('_TOKEN')).length ?? 0;
218
+ const openAiApiKeyConfigured = controller.runtimeSnapshot?.secrets.records.some((record) => record.key === 'OPENAI_API_KEY') ?? false;
219
+ const fields: OnboardingWizardFieldDefinition[] = [
220
+ {
221
+ kind: 'status',
222
+ id: 'providers.openai-subscription',
223
+ label: 'OpenAI subscription status',
224
+ hint: openAiActive
225
+ ? 'An OpenAI subscription session is already available.'
226
+ : openAiPending
227
+ ? 'An OpenAI subscription login is pending.'
228
+ : 'No OpenAI subscription session was found in the current Agent state.',
229
+ defaultValue: openAiActive ? 'Active' : openAiPending ? 'Pending' : 'Not detected',
230
+ },
231
+ {
232
+ kind: 'status',
233
+ id: 'providers.api-key-inventory',
234
+ label: 'Provider API key inventory',
235
+ hint: providerSecretCount > 0 ? `${providerSecretCount} provider credential reference(s) were found. Values stay masked.` : 'No provider API key references were detected in the current Agent state.',
236
+ defaultValue: providerSecretCount > 0 ? `${providerSecretCount} configured` : 'None detected',
237
+ },
238
+ {
239
+ kind: 'masked',
240
+ id: 'providers.openai-api-key',
241
+ label: 'OpenAI API key',
242
+ hint: openAiApiKeyConfigured
243
+ ? 'An OpenAI API key is already stored. Leave blank to keep it; enter a new key to replace it through the secret manager.'
244
+ : 'Optional: enter an OpenAI API key now. The value is stored through the secret manager, not in config.',
245
+ placeholder: openAiApiKeyConfigured ? 'already configured' : 'sk-...',
246
+ defaultValue: '',
247
+ },
248
+ ...(openAiActive ? [] : [
249
+ {
250
+ kind: 'action' as const,
251
+ id: 'providers.openai-subscription-start',
252
+ action: 'start-openai-subscription' as const,
253
+ label: openAiPending ? 'Restart OpenAI subscription sign-in' : 'Start OpenAI subscription sign-in',
254
+ hint: 'Opens the OpenAI sign-in flow from the wizard and records pending login state here.',
255
+ defaultValue: openAiPending ? 'Restart' : 'Start',
256
+ },
257
+ ...(openAiPending ? [
487
258
  {
488
- kind: 'radio',
489
- id: 'experience.hitl',
490
- label: 'Human-in-the-Loop (HITL) mode',
491
- hint: 'Choose how much operational activity should be surfaced.',
492
- options: HITL_MODE_OPTIONS,
493
- defaultValue: controller.runtimeSnapshot?.runtimeDefaults.behavior.hitlMode ?? 'balanced',
259
+ kind: 'text' as const,
260
+ id: 'providers.openai-authorization-url',
261
+ label: 'OpenAI authorization URL',
262
+ hint: 'If the browser did not open, use this URL to continue sign-in without leaving the wizard.',
263
+ placeholder: 'authorization URL appears after start',
264
+ defaultValue: '',
494
265
  },
495
266
  {
496
- kind: 'radio',
497
- id: 'experience.guidance',
498
- label: 'Guidance verbosity',
499
- hint: 'Choose how much explanation the shell should provide.',
500
- options: GUIDANCE_MODE_OPTIONS,
501
- defaultValue: controller.runtimeSnapshot?.runtimeDefaults.behavior.guidanceMode ?? 'minimal',
267
+ kind: 'text' as const,
268
+ id: 'providers.openai-callback-code',
269
+ label: 'OpenAI callback code or URL',
270
+ hint: 'Paste the callback code or redirected URL after completing browser sign-in.',
271
+ placeholder: 'code or callback URL',
272
+ defaultValue: '',
502
273
  },
503
274
  {
504
- kind: 'radio',
505
- id: 'experience.permissions',
506
- label: 'Permission posture',
507
- hint: 'Choose how aggressively the shell should ask before powerful actions.',
508
- options: PERMISSION_MODE_OPTIONS,
509
- defaultValue: controller.runtimeSnapshot?.runtimeDefaults.permissionsMode ?? 'prompt',
275
+ kind: 'action' as const,
276
+ id: 'providers.openai-subscription-finish',
277
+ action: 'finish-openai-subscription' as const,
278
+ label: 'Finish OpenAI subscription sign-in',
279
+ hint: 'Completes the pending OpenAI subscription login using the code above.',
280
+ defaultValue: 'Finish',
510
281
  },
511
- ],
512
- };
513
- }
514
-
515
- export function buildNetworkStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
516
- const bindSettings = controller.runtimeSnapshot?.bindSettings;
517
- const browserEnabled = controller.shouldEnableBrowserSurface();
518
- const listenerEnabled = controller.shouldExposeHttpListenerNetworkFields();
519
- const listenerWillApply = controller.shouldEnableHttpListener();
520
- const controlPlaneRemote = controller.shouldExposeControlPlaneNetwork();
521
- const networkEnabled = {
522
- controlPlane: controlPlaneRemote,
523
- httpListener: listenerEnabled,
524
- web: browserEnabled,
525
- };
526
- const mode = controller.getStringFieldValue('network.mode', controller.runtimeDerived.step1_5NetworkMode);
527
- const custom = mode === 'custom';
528
- const fields: OnboardingWizardFieldDefinition[] = [
529
- {
530
- kind: 'radio',
531
- id: 'network.mode',
532
- label: 'Network mode',
533
- hint: 'Choose Local Network for the default LAN setup, or Custom to set IP addresses and ports.',
534
- options: NETWORK_MODE_OPTIONS,
535
- defaultValue: controller.runtimeDerived.step1_5NetworkMode,
536
- },
537
- ];
538
-
539
- if (custom) {
540
- const sharedIpField: OnboardingWizardChecklistFieldDefinition = {
541
- kind: 'checklist',
542
- id: 'network.shared-ip',
543
- label: 'Use the same IP address for all external daemon surfaces',
544
- hint: 'When included, browser, external daemon control plane, and webhook listener network bindings share one IP address in the daemon host configuration.',
545
- defaultValue: controller.getSharedIpDefault(networkEnabled),
546
- };
547
- const sharedIp = controller.getBooleanFieldValue(sharedIpField.id, sharedIpField.defaultValue);
548
- fields.push(sharedIpField);
549
- if (sharedIp) {
550
- fields.push({
551
- kind: 'text',
552
- id: 'network.shared-ip-address',
553
- label: 'Shared IP address',
554
- hint: 'IP address used by each enabled service.',
555
- placeholder: '0.0.0.0',
556
- defaultValue: controller.getSharedIpHostDefault(networkEnabled),
557
- });
558
- }
559
-
560
- if (controlPlaneRemote) {
561
- fields.push({
562
- kind: 'text',
563
- id: 'network.service-port',
564
- label: 'External daemon control-plane port',
565
- hint: 'Port exposed by the external daemon control plane.',
566
- placeholder: '3421',
567
- defaultValue: String(bindSettings?.controlPlane.port ?? 3421),
568
- });
569
- if (!sharedIp) {
570
- fields.push({
571
- kind: 'text',
572
- id: 'network.service-ip',
573
- label: 'External daemon control-plane IP address',
574
- hint: 'IP address exposed by the external daemon control plane.',
575
- placeholder: '0.0.0.0',
576
- defaultValue: normalizeText(bindSettings?.controlPlane.host) || '0.0.0.0',
577
- });
578
- }
579
- }
282
+ ] : []),
283
+ ]),
284
+ ];
580
285
 
581
- if (browserEnabled) {
582
- fields.push({
583
- kind: 'text',
584
- id: 'network.browser-port',
585
- label: 'Browser surface port',
586
- hint: 'Port for browser access to GoodVibes.',
587
- placeholder: '3423',
588
- defaultValue: String(bindSettings?.web.port ?? 3423),
589
- });
590
- if (!sharedIp) {
591
- fields.push({
592
- kind: 'text',
593
- id: 'network.browser-ip',
594
- label: 'Browser surface IP address',
595
- hint: 'IP address for browser access.',
596
- placeholder: '0.0.0.0',
597
- defaultValue: normalizeText(bindSettings?.web.host) || '0.0.0.0',
598
- });
599
- }
600
- }
286
+ return {
287
+ id: 'provider-access',
288
+ title: 'AI provider access',
289
+ shortLabel: 'Providers',
290
+ description: 'Review provider access for the Agent conversation. Credentials stay masked and are stored through the secret manager.',
291
+ summaryTitle: 'Provider access summary',
292
+ summaryLines: [
293
+ `OpenAI subscription: ${openAiActive ? 'active' : openAiPending ? 'pending' : 'not detected'}`,
294
+ `OpenAI API key: ${openAiApiKeyConfigured ? 'configured' : 'not detected'}`,
295
+ `Provider credential references: ${providerSecretCount}`,
296
+ ],
297
+ fields,
298
+ };
299
+ }
601
300
 
602
- if (listenerEnabled) {
603
- fields.push({
604
- kind: 'text',
605
- id: 'network.webhook-port',
606
- label: 'HTTP listener port',
607
- hint: 'Port for incoming webhooks and events.',
608
- placeholder: '3422',
609
- defaultValue: String(bindSettings?.httpListener.port ?? 3422),
610
- });
611
- if (!sharedIp) {
612
- fields.push({
613
- kind: 'text',
614
- id: 'network.webhook-ip',
615
- label: 'HTTP listener IP address',
616
- hint: 'IP address for incoming webhooks and events.',
617
- placeholder: '0.0.0.0',
618
- defaultValue: normalizeText(bindSettings?.httpListener.host) || '0.0.0.0',
619
- });
620
- }
621
- }
622
- }
301
+ export function buildDefaultModelStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
302
+ const routing = controller.runtimeSnapshot?.providerRouting;
303
+ const primarySelectionField: OnboardingWizardModelPickerFieldDefinition = {
304
+ kind: 'modelPicker',
305
+ id: 'default-model.primary-model',
306
+ label: 'Default provider + model',
307
+ hint: 'Open the model picker for the Agent conversation route.',
308
+ target: 'main',
309
+ defaultSelection: {
310
+ providerId: normalizeText(routing?.primaryProviderId),
311
+ modelId: normalizeText(routing?.primaryModelId),
312
+ enabled: true,
313
+ },
314
+ };
315
+ const reasoningField: OnboardingWizardRadioFieldDefinition = {
316
+ kind: 'radio',
317
+ id: 'default-model.reasoning',
318
+ label: 'Reasoning effort',
319
+ hint: 'Choose the default reasoning effort for the serial Agent conversation.',
320
+ options: REASONING_OPTIONS,
321
+ defaultValue: normalizeText(routing?.primaryReasoningEffort) || 'medium',
322
+ };
323
+
324
+ return {
325
+ id: 'default-model',
326
+ title: 'Default model',
327
+ shortLabel: 'Model',
328
+ description: 'Choose the default provider, model, and reasoning posture for normal Agent conversation.',
329
+ summaryTitle: 'Default model summary',
330
+ summaryLines: [
331
+ `Main: ${modelSelectionLabel(controller.modelSelectionState.get('main') ?? primarySelectionField.defaultSelection)}`,
332
+ `Reasoning: ${controller.getFieldValueLabel(reasoningField)}`,
333
+ ],
334
+ fields: [primarySelectionField, reasoningField],
335
+ };
336
+ }
623
337
 
624
- return {
625
- id: 'network',
626
- title: 'Network setup',
627
- shortLabel: 'Network',
628
- description: 'Review LAN defaults or IP addresses and ports for the external daemon browser, control-plane, and listener surfaces. Agent does not apply daemon bind changes.',
629
- summaryTitle: 'Bind posture',
630
- summaryLines: [
631
- `Mode: ${custom ? 'custom' : 'local network default'}`,
632
- `Browser surface: ${browserEnabled ? 'enabled' : 'not selected'}`,
633
- `HTTP listener: ${listenerWillApply ? 'enabled' : listenerEnabled ? 'available for selected external apps' : 'not selected'}`,
634
- ],
635
- fields,
636
- };
637
- }
338
+ export function buildAgentKnowledgeStep(): OnboardingWizardStepDefinition {
339
+ return {
340
+ id: 'agent-knowledge',
341
+ title: 'Agent Knowledge',
342
+ shortLabel: 'Knowledge',
343
+ description: 'Agent Knowledge is isolated to the GoodVibes Agent product segment. It never falls back to default Knowledge/Wiki or any non-Agent product segment.',
344
+ summaryTitle: 'Knowledge isolation',
345
+ summaryLines: [
346
+ 'Route segment: /api/goodvibes-agent/knowledge/*',
347
+ 'Default wiki fallback: disabled',
348
+ 'Non-Agent route fallback: disabled',
349
+ ],
350
+ fields: [
351
+ {
352
+ kind: 'status',
353
+ id: 'agent-knowledge.route',
354
+ label: 'Isolated Agent Knowledge route',
355
+ hint: 'Ask, search, status, and ingest use /api/goodvibes-agent/knowledge/* only.',
356
+ defaultValue: 'Isolated',
357
+ },
358
+ {
359
+ kind: 'status',
360
+ id: 'agent-knowledge.no-default-wiki',
361
+ label: 'Default Knowledge/Wiki fallback',
362
+ hint: 'Agent setup and Agent ask/search must not query the default wiki when Agent Knowledge has no answer.',
363
+ defaultValue: 'Blocked',
364
+ },
365
+ {
366
+ kind: 'status',
367
+ id: 'agent-knowledge.no-non-agent-routes',
368
+ label: 'Non-Agent route fallback',
369
+ hint: 'Other product routes are not part of Agent Knowledge.',
370
+ defaultValue: 'Blocked',
371
+ },
372
+ ],
373
+ };
374
+ }
638
375
 
639
- export function buildAccountsStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
640
- const subscriptionsAck = controller.runtimeDerived.reopenEditAcknowledgements.subscriptions;
641
- const authAck = controller.runtimeDerived.reopenEditAcknowledgements.auth;
642
- const auth = controller.runtimeSnapshot?.auth.snapshot;
643
- const needsAuthBootstrap = controller.requiresAuthBootstrap();
644
- const needsExistingAuthAcknowledgement = controller.hasServerCapabilitiesSelected()
645
- && !needsAuthBootstrap
646
- && controller.hasLocalAuthUser();
647
- const fields: OnboardingWizardFieldDefinition[] = [];
648
- const defaultAdminUsername = controller.getDefaultAdminUsername();
376
+ export function buildLocalStateStep(): OnboardingWizardStepDefinition {
377
+ return {
378
+ id: 'agent-local-state',
379
+ title: 'Local memory and behavior',
380
+ shortLabel: 'Memory',
381
+ description: 'Review the Agent-local behavior model. Memory, personas, skills, routines, and runtime profiles stay local until a stable shared registry exists.',
382
+ summaryTitle: 'Local Agent state',
383
+ summaryLines: [
384
+ 'Memory/personas/skills/routines: local Agent registries',
385
+ 'Secrets: rejected or stored by secret reference',
386
+ 'Profiles: isolated Agent homes',
387
+ ],
388
+ fields: [
389
+ {
390
+ kind: 'status',
391
+ id: 'agent-local-state.memory',
392
+ label: 'Local memory',
393
+ hint: 'Use /memory to create, review, stale, search, and delete Agent-local memory records.',
394
+ defaultValue: 'Local registry',
395
+ },
396
+ {
397
+ kind: 'status',
398
+ id: 'agent-local-state.personas',
399
+ label: 'Personas',
400
+ hint: 'Use /personas to create and activate serial operating modes for the main conversation.',
401
+ defaultValue: 'Local registry',
402
+ },
403
+ {
404
+ kind: 'status',
405
+ id: 'agent-local-state.skills',
406
+ label: 'Skills',
407
+ hint: 'Use /agent-skills and /skills local to manage reusable Agent procedures.',
408
+ defaultValue: 'Local registry',
409
+ },
410
+ {
411
+ kind: 'status',
412
+ id: 'agent-local-state.routines',
413
+ label: 'Routines',
414
+ hint: 'Use /routines for reusable local procedures. Starting a routine prints steps in the main conversation and does not spawn hidden work.',
415
+ defaultValue: 'Local registry',
416
+ },
417
+ ],
418
+ };
419
+ }
649
420
 
650
- fields.push(
651
- {
652
- kind: 'text',
653
- id: 'accounts.admin-username',
654
- label: 'Local auth admin username',
655
- hint: needsAuthBootstrap
656
- ? 'Required before the external daemon exposes browser, control-plane, or listener surfaces.'
657
- : 'Optional. Enter an existing admin username to rotate its password, or a new username to create another admin.',
658
- placeholder: defaultAdminUsername,
659
- defaultValue: defaultAdminUsername,
660
- required: needsAuthBootstrap,
661
- },
662
- {
663
- kind: 'masked',
664
- id: 'accounts.admin-password',
665
- label: 'Local auth admin password',
666
- hint: needsAuthBootstrap
667
- ? controller.hasBootstrapCredentialPresent()
668
- ? 'Creates or updates the named local admin, removes the bootstrap credential file, and retires the bootstrap admin when it is a different user.'
669
- : 'Creates the first local admin user and an initial session before external daemon network settings are used by the daemon owner.'
670
- : 'Optional. Leave blank to keep existing local auth unchanged; enter a password to create or rotate the named admin user.',
671
- placeholder: needsAuthBootstrap ? 'password required' : 'leave blank to keep unchanged',
672
- defaultValue: '',
673
- required: needsAuthBootstrap,
674
- },
675
- );
421
+ export function buildAutomationStep(): OnboardingWizardStepDefinition {
422
+ return {
423
+ id: 'agent-automation',
424
+ title: 'Routines and automation',
425
+ shortLabel: 'Routines',
426
+ description: 'Set the Agent automation posture: local routines run in the main conversation, while daemon schedules remain externally owned and explicit.',
427
+ summaryTitle: 'Routine and schedule posture',
428
+ summaryLines: [
429
+ 'Local routines: reusable main-conversation workflows',
430
+ 'Daemon schedules: explicit promotion only',
431
+ 'Runs/cancels/retries: command-confirmed side effects',
432
+ ],
433
+ fields: [
434
+ {
435
+ kind: 'status',
436
+ id: 'agent-automation.local-routines',
437
+ label: 'Local routine library',
438
+ hint: 'Use /routines or the Agent workspace to create, review, enable, and start local routines without spawning hidden jobs.',
439
+ defaultValue: 'Local registry',
440
+ },
441
+ {
442
+ kind: 'status',
443
+ id: 'agent-automation.schedule-observability',
444
+ label: 'Schedule observability',
445
+ hint: 'Use /schedule list, /schedule reconcile, and automation views to inspect externally owned jobs and runs.',
446
+ defaultValue: 'Read first',
447
+ },
448
+ {
449
+ kind: 'status',
450
+ id: 'agent-automation.schedule-promotion',
451
+ label: 'Routine-to-schedule promotion',
452
+ hint: 'Creating daemon schedules from routines requires a reviewed routine, a real timing expression, optional delivery target, and explicit confirmation.',
453
+ defaultValue: 'Explicit command',
454
+ },
455
+ {
456
+ kind: 'status',
457
+ id: 'agent-automation.mutations',
458
+ label: 'Automation mutations',
459
+ hint: 'Run, pause, resume, cancel, retry, approve, and deny actions are never inferred from chat; they require exact commands and confirmation.',
460
+ defaultValue: 'Confirmed only',
461
+ },
462
+ ],
463
+ };
464
+ }
676
465
 
677
- fields.push(
466
+ export function buildVoiceMediaStep(): OnboardingWizardStepDefinition {
467
+ return {
468
+ id: 'agent-voice-media',
469
+ title: 'Voice and media',
470
+ shortLabel: 'Voice',
471
+ description: 'Prepare voice, speech, image input, and media understanding as Agent operator surfaces rather than daemon lifecycle features.',
472
+ summaryTitle: 'Voice and media posture',
473
+ summaryLines: [
474
+ 'Voice and speech: optional operator surfaces',
475
+ 'Image/audio inputs: explicit attachment workflows',
476
+ 'Media generation and playback: provider-backed and policy-gated',
477
+ ],
478
+ fields: [
678
479
  {
679
- kind: 'acknowledgement',
680
- id: 'accounts.subscriptions',
681
- label: 'Confirm stored subscription state',
682
- hint: subscriptionsAck.detail,
683
- defaultValue: subscriptionsAck.accepted,
684
- required: controller.mode !== 'new' && subscriptionsAck.required,
685
- reason: subscriptionsAck.reason,
686
- target: 'subscriptions',
480
+ kind: 'status',
481
+ id: 'agent-voice-media.voice',
482
+ label: 'Voice interaction',
483
+ hint: 'Use the voice/media workspace and TTS settings to configure spoken responses for the Agent conversation.',
484
+ defaultValue: 'Optional',
687
485
  },
688
486
  {
689
- kind: 'acknowledgement',
690
- id: 'accounts.auth',
691
- label: 'Confirm local auth posture',
692
- hint: authAck.detail,
693
- defaultValue: authAck.accepted,
694
- required: needsExistingAuthAcknowledgement || (controller.mode !== 'new' && authAck.required),
695
- reason: authAck.reason,
696
- target: 'auth',
487
+ kind: 'status',
488
+ id: 'agent-voice-media.attachments',
489
+ label: 'Image and audio input',
490
+ hint: 'Attach files explicitly to a prompt or command. Agent does not ingest media into Knowledge without an Agent Knowledge ingest action.',
491
+ defaultValue: 'Explicit input',
697
492
  },
698
493
  {
699
494
  kind: 'status',
700
- id: 'accounts.bootstrap',
701
- label: 'Local auth readiness',
702
- hint: needsAuthBootstrap
703
- ? 'The wizard will create local auth before applying network-accessible settings.'
704
- : controller.hasAdminAuthUser()
705
- ? 'An existing local auth admin user was detected and will be kept.'
706
- : controller.hasLocalAuthUser()
707
- ? 'Existing local auth users were detected and will be kept.'
708
- : 'No server-backed surface is selected, so local auth is not required.',
709
- defaultValue: needsAuthBootstrap
710
- ? controller.hasBootstrapCredentialPresent() ? 'Bootstrap replacement required' : 'Local admin required'
711
- : controller.hasAdminAuthUser() ? 'Admin detected' : controller.hasLocalAuthUser() ? 'Local auth detected' : 'Not required',
495
+ id: 'agent-voice-media.output',
496
+ label: 'Generated media and playback',
497
+ hint: 'Media output uses configured providers and visible command/turn flow; external publication still requires explicit approval.',
498
+ defaultValue: 'Policy gated',
712
499
  },
713
500
  {
714
501
  kind: 'status',
715
- id: 'accounts.user-store',
716
- label: 'Local auth store path',
717
- hint: 'Carry the current auth store location into edit/review mode.',
718
- defaultValue: normalizeText(auth?.userStorePath) || 'No local auth store path',
502
+ id: 'agent-voice-media.nodes',
503
+ label: 'Node and device posture',
504
+ hint: 'Remote devices and nodes are inspected as capability surfaces. Agent does not own runner topology or launch service processes from onboarding.',
505
+ defaultValue: 'External',
719
506
  },
720
- );
507
+ ],
508
+ };
509
+ }
721
510
 
722
- return {
723
- id: 'access',
724
- title: 'Subscriptions and auth review',
725
- shortLabel: 'Accounts',
726
- description: needsAuthBootstrap
727
- ? 'Create local auth state before external daemon LAN, browser, service, or listener settings are applied by the daemon owner.'
728
- : 'Review existing subscription and local auth state. Existing local auth is kept unless you change it elsewhere.',
729
- summaryTitle: 'Stored account state',
730
- summaryLines: [
731
- `Subscriptions: ${controller.runtimeSnapshot?.subscriptions.active.length ?? 0} active / ${controller.runtimeSnapshot?.subscriptions.pending.length ?? 0} pending`,
732
- `Auth: ${auth?.userCount ?? 0} users / ${auth?.sessionCount ?? 0} sessions`,
733
- needsAuthBootstrap
734
- ? controller.hasBootstrapCredentialPresent()
735
- ? 'Bootstrap credentials will be replaced before external network settings are used'
736
- : 'Local admin will be created before external network settings are used'
737
- : controller.hasLocalAuthUser() ? 'Existing local auth will be kept' : 'Local auth is not required for this setup',
738
- ],
739
- fields,
740
- };
741
- }
511
+ export function buildDelegationPolicyStep(): OnboardingWizardStepDefinition {
512
+ return {
513
+ id: 'agent-delegation',
514
+ title: 'Build delegation',
515
+ shortLabel: 'Delegate',
516
+ description: 'GoodVibes Agent is not the coding TUI. Explicit build, fix, review, or implementation work is delegated to GoodVibes TUI; ordinary assistant work stays serial in this conversation.',
517
+ summaryTitle: 'Delegation policy',
518
+ summaryLines: [
519
+ 'Normal chat: main Agent conversation',
520
+ 'Build/fix/review: explicit GoodVibes TUI delegation',
521
+ 'WRFC: only when explicitly requested for build/fix/review',
522
+ ],
523
+ fields: [
524
+ {
525
+ kind: 'status',
526
+ id: 'agent-delegation.normal-chat',
527
+ label: 'Normal assistant work',
528
+ hint: 'Planning, research, summaries, local memory updates, and safe read-only checks stay in the main Agent conversation.',
529
+ defaultValue: 'Serial',
530
+ },
531
+ {
532
+ kind: 'status',
533
+ id: 'agent-delegation.build-work',
534
+ label: 'Build/fix/review work',
535
+ hint: 'Use /delegate with the full original task. GoodVibes TUI owns coding execution and WRFC chains.',
536
+ defaultValue: 'Explicit delegation',
537
+ },
538
+ {
539
+ kind: 'status',
540
+ id: 'agent-delegation.wrfc',
541
+ label: 'WRFC policy',
542
+ hint: 'Agent never uses WRFC by default; request it only for explicit build, fix, review, or implementation work.',
543
+ defaultValue: 'Explicit only',
544
+ },
545
+ ],
546
+ };
547
+ }
742
548
 
743
- export function buildReviewStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
744
- const feedback = controller.applyFeedback;
745
- const feedbackFields: OnboardingWizardFieldDefinition[] = feedback
746
- ? [
747
- {
748
- kind: 'status',
749
- id: 'review.feedback',
750
- label: feedback.title,
751
- hint: feedback.summary,
752
- defaultValue: feedback.severity === 'error' ? 'Needs attention' : feedback.severity === 'warning' ? 'Warning' : 'Info',
753
- },
754
- ...feedback.messages.slice(0, 8).map((message, index): OnboardingWizardFieldDefinition => ({
755
- kind: 'status',
756
- id: `review.feedback.${index}`,
757
- label: message,
758
- hint: message,
759
- defaultValue: feedback.severity === 'error' ? 'Error' : feedback.severity === 'warning' ? 'Warning' : 'Info',
760
- })),
761
- ]
762
- : [];
763
- const unsavedLabel = controller.dirtyStepCount === 1
764
- ? '1 screen has unapplied changes'
765
- : `${controller.dirtyStepCount} screens have unapplied changes`;
549
+ export function buildExperienceStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
550
+ return {
551
+ id: 'experience',
552
+ title: 'Assistant experience',
553
+ shortLabel: 'Behavior',
554
+ description: 'Tune the Agent conversation style and approval posture for day-to-day operator use.',
555
+ summaryTitle: 'Experience posture',
556
+ summaryLines: [
557
+ `Human-in-the-Loop (HITL): ${controller.getStringFieldValue('experience.hitl', controller.runtimeSnapshot?.runtimeDefaults.behavior.hitlMode ?? 'balanced')}`,
558
+ `Guidance: ${controller.getStringFieldValue('experience.guidance', controller.runtimeSnapshot?.runtimeDefaults.behavior.guidanceMode ?? 'minimal')}`,
559
+ `Permissions: ${controller.getStringFieldValue('experience.permissions', controller.runtimeSnapshot?.runtimeDefaults.permissionsMode ?? 'prompt')}`,
560
+ ],
561
+ fields: [
562
+ {
563
+ kind: 'radio',
564
+ id: 'experience.hitl',
565
+ label: 'Human-in-the-Loop (HITL) mode',
566
+ hint: 'Choose how much operational activity should be shown.',
567
+ options: HITL_MODE_OPTIONS,
568
+ defaultValue: controller.runtimeSnapshot?.runtimeDefaults.behavior.hitlMode ?? 'balanced',
569
+ },
570
+ {
571
+ kind: 'radio',
572
+ id: 'experience.guidance',
573
+ label: 'Guidance verbosity',
574
+ hint: 'Choose how much explanation the Agent should provide while working.',
575
+ options: GUIDANCE_MODE_OPTIONS,
576
+ defaultValue: controller.runtimeSnapshot?.runtimeDefaults.behavior.guidanceMode ?? 'minimal',
577
+ },
578
+ {
579
+ kind: 'radio',
580
+ id: 'experience.permissions',
581
+ label: 'Permission posture',
582
+ hint: 'Choose how aggressively the Agent should ask before powerful actions.',
583
+ options: PERMISSION_MODE_OPTIONS,
584
+ defaultValue: controller.runtimeSnapshot?.runtimeDefaults.permissionsMode ?? 'prompt',
585
+ },
586
+ ],
587
+ };
588
+ }
766
589
 
767
- return {
768
- id: 'review',
769
- title: 'Review and apply',
770
- shortLabel: 'Review',
771
- description: 'Review the selected settings and apply them directly from the wizard.',
772
- summaryTitle: 'Review posture',
773
- summaryLines: [
774
- unsavedLabel,
775
- `${controller.buildApplyRequest().operations.length} settings change(s) ready to apply`,
776
- feedback ? `Last apply: ${feedback.title}` : 'No apply errors reported',
777
- controller.isEditingTextField() ? `Editing: ${controller.editingFieldId}` : 'Ready to apply',
778
- ],
779
- fields: [
780
- ...feedbackFields,
590
+ export function buildReviewStep(controller: OnboardingWizardController): OnboardingWizardStepDefinition {
591
+ const feedback = controller.applyFeedback;
592
+ const feedbackFields: OnboardingWizardFieldDefinition[] = feedback
593
+ ? [
781
594
  {
782
595
  kind: 'status',
783
- id: 'review.global-marker',
784
- label: 'Global onboarding check',
785
- hint: 'Opening this wizard marks onboarding as shown for this user account, so new projects do not reopen it automatically.',
786
- defaultValue: 'Already marked as shown',
787
- },
788
- {
789
- kind: 'action',
790
- id: 'review.apply',
791
- action: 'apply',
792
- label: 'Apply settings and verify',
793
- hint: 'Persist the wizard settings and verify the resulting runtime state. The global onboarding check was already recorded when the wizard opened.',
794
- defaultValue: 'Ready',
596
+ id: 'review.feedback',
597
+ label: feedback.title,
598
+ hint: feedback.summary,
599
+ defaultValue: feedback.severity === 'error' ? 'Needs attention' : feedback.severity === 'warning' ? 'Warning' : 'Info',
795
600
  },
796
- ],
797
- };
798
- }
601
+ ...feedback.messages.slice(0, 8).map((message, index): OnboardingWizardFieldDefinition => ({
602
+ kind: 'status',
603
+ id: `review.feedback.${index}`,
604
+ label: message,
605
+ hint: message,
606
+ defaultValue: feedback.severity === 'error' ? 'Error' : feedback.severity === 'warning' ? 'Warning' : 'Info',
607
+ })),
608
+ ]
609
+ : [];
610
+ const unsavedLabel = controller.dirtyStepCount === 1
611
+ ? '1 screen has unapplied changes'
612
+ : `${controller.dirtyStepCount} screens have unapplied changes`;
613
+
614
+ return {
615
+ id: 'review',
616
+ title: 'Review and apply',
617
+ shortLabel: 'Review',
618
+ description: 'Review Agent-owned settings and apply them directly from the wizard.',
619
+ summaryTitle: 'Review posture',
620
+ summaryLines: [
621
+ unsavedLabel,
622
+ `${controller.buildApplyRequest().operations.length} Agent setting change(s) ready to apply`,
623
+ feedback ? `Last apply: ${feedback.title}` : 'No apply errors reported',
624
+ controller.isEditingTextField() ? `Editing: ${controller.editingFieldId}` : 'Ready to apply',
625
+ ],
626
+ fields: [
627
+ ...feedbackFields,
628
+ {
629
+ kind: 'status',
630
+ id: 'review.global-marker',
631
+ label: 'Agent setup check',
632
+ hint: 'Opening this wizard marks Agent setup as shown for this user account, so it does not reopen automatically.',
633
+ defaultValue: 'Already marked as shown',
634
+ },
635
+ {
636
+ kind: 'action',
637
+ id: 'review.apply',
638
+ action: 'apply',
639
+ label: 'Apply Agent settings and verify',
640
+ hint: 'Persist the Agent-owned settings and verify that no service lifecycle, non-Agent entrypoint, default wiki, or non-Agent knowledge setup was requested.',
641
+ defaultValue: 'Ready',
642
+ },
643
+ ],
644
+ };
645
+ }