@pellux/goodvibes-agent 0.1.9 → 0.1.11

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 (97) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +1 -1
  3. package/docs/getting-started.md +1 -1
  4. package/docs/release-and-publishing.md +2 -2
  5. package/package.json +4 -1
  6. package/src/cli/agent-knowledge-command.ts +46 -20
  7. package/src/cli/help.ts +15 -2
  8. package/src/cli/management-commands.ts +3 -3
  9. package/src/cli/management.ts +7 -1
  10. package/src/cli/parser.ts +3 -0
  11. package/src/cli/service-posture.ts +6 -6
  12. package/src/cli/status.ts +9 -9
  13. package/src/cli/surface-command.ts +3 -3
  14. package/src/cli/types.ts +2 -0
  15. package/src/input/commands/cloudflare-runtime.ts +20 -5
  16. package/src/input/commands/confirmation.ts +24 -0
  17. package/src/input/commands/discovery-runtime.ts +16 -7
  18. package/src/input/commands/eval.ts +27 -14
  19. package/src/input/commands/experience-runtime.ts +66 -27
  20. package/src/input/commands/health-runtime.ts +1 -1
  21. package/src/input/commands/hooks-runtime.ts +79 -20
  22. package/src/input/commands/incident-runtime.ts +17 -6
  23. package/src/input/commands/integration-runtime.ts +93 -50
  24. package/src/input/commands/knowledge.ts +38 -12
  25. package/src/input/commands/local-auth-runtime.ts +36 -13
  26. package/src/input/commands/local-provider-runtime.ts +22 -11
  27. package/src/input/commands/local-runtime.ts +21 -11
  28. package/src/input/commands/local-setup.ts +35 -16
  29. package/src/input/commands/managed-runtime.ts +51 -20
  30. package/src/input/commands/marketplace-runtime.ts +31 -16
  31. package/src/input/commands/mcp-runtime.ts +65 -34
  32. package/src/input/commands/memory-product-runtime.ts +72 -35
  33. package/src/input/commands/memory.ts +9 -9
  34. package/src/input/commands/notify-runtime.ts +27 -8
  35. package/src/input/commands/operator-runtime.ts +85 -17
  36. package/src/input/commands/planning-runtime.ts +14 -2
  37. package/src/input/commands/platform-access-runtime.ts +88 -45
  38. package/src/input/commands/platform-services-runtime.ts +51 -25
  39. package/src/input/commands/product-runtime.ts +54 -27
  40. package/src/input/commands/profile-sync-runtime.ts +17 -6
  41. package/src/input/commands/recall-bundle.ts +38 -17
  42. package/src/input/commands/recall-query.ts +15 -4
  43. package/src/input/commands/recall-review.ts +9 -3
  44. package/src/input/commands/remote-runtime-setup.ts +45 -18
  45. package/src/input/commands/remote-runtime.ts +25 -9
  46. package/src/input/commands/replay-runtime.ts +9 -2
  47. package/src/input/commands/services-runtime.ts +21 -10
  48. package/src/input/commands/session-content.ts +53 -51
  49. package/src/input/commands/session-workflow.ts +10 -4
  50. package/src/input/commands/session.ts +1 -1
  51. package/src/input/commands/settings-sync-runtime.ts +40 -17
  52. package/src/input/commands/share-runtime.ts +12 -4
  53. package/src/input/commands/shell-core.ts +3 -3
  54. package/src/input/commands/subscription-runtime.ts +35 -20
  55. package/src/input/commands/teleport-runtime.ts +16 -5
  56. package/src/input/commands/work-plan-runtime.ts +23 -12
  57. package/src/input/handler-content-actions.ts +11 -62
  58. package/src/input/handler-interactions.ts +1 -1
  59. package/src/input/handler-onboarding-cloudflare.ts +48 -117
  60. package/src/input/handler.ts +1 -0
  61. package/src/input/keybindings.ts +1 -1
  62. package/src/input/mcp-workspace.ts +25 -49
  63. package/src/input/onboarding/onboarding-runtime-status.ts +8 -8
  64. package/src/input/onboarding/onboarding-wizard-apply.ts +13 -53
  65. package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +12 -12
  66. package/src/input/onboarding/onboarding-wizard-cloudflare.ts +2 -7
  67. package/src/input/onboarding/onboarding-wizard-constants.ts +7 -7
  68. package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +4 -4
  69. package/src/input/onboarding/onboarding-wizard-steps.ts +13 -13
  70. package/src/input/profile-picker-modal.ts +13 -31
  71. package/src/input/session-picker-modal.ts +4 -30
  72. package/src/input/settings-modal-agent-policy.ts +18 -0
  73. package/src/input/settings-modal-subscriptions.ts +3 -3
  74. package/src/input/settings-modal-types.ts +17 -0
  75. package/src/input/settings-modal.ts +30 -29
  76. package/src/main.ts +3 -26
  77. package/src/panels/incident-review-panel.ts +1 -1
  78. package/src/panels/local-auth-panel.ts +4 -4
  79. package/src/panels/provider-account-snapshot.ts +1 -1
  80. package/src/panels/provider-health-domains.ts +2 -2
  81. package/src/panels/settings-sync-panel.ts +2 -2
  82. package/src/panels/subscription-panel.ts +7 -7
  83. package/src/renderer/block-actions.ts +1 -1
  84. package/src/renderer/help-overlay.ts +2 -2
  85. package/src/renderer/mcp-workspace.ts +12 -12
  86. package/src/renderer/process-modal.ts +17 -8
  87. package/src/renderer/profile-picker-modal.ts +3 -11
  88. package/src/renderer/session-picker-modal.ts +2 -10
  89. package/src/renderer/settings-modal.ts +12 -8
  90. package/src/renderer/ui-factory.ts +4 -32
  91. package/src/runtime/bootstrap-shell.ts +0 -13
  92. package/src/runtime/bootstrap.ts +0 -10
  93. package/src/runtime/onboarding/derivation.ts +6 -6
  94. package/src/verification/live-verifier.ts +148 -13
  95. package/src/version.ts +10 -3
  96. package/src/input/commands/quit-shared.ts +0 -162
  97. package/src/renderer/git-status.ts +0 -89
@@ -30,13 +30,13 @@ export const DEFAULT_CAPABILITIES: readonly OnboardingStep1CapabilityItem[] = [
30
30
  id: 'network-access',
31
31
  label: 'Let other devices use GoodVibes',
32
32
  selected: false,
33
- detail: 'Make enabled GoodVibes services reachable from other devices on your LAN. Local authentication is required.',
33
+ detail: 'Review external daemon surfaces that are reachable from other devices on your LAN. Local authentication is required.',
34
34
  },
35
35
  {
36
36
  id: 'webhook-events',
37
37
  label: 'Receive webhooks or events from other tools',
38
38
  selected: false,
39
- detail: 'Turn on the HTTP listener for incoming webhooks, callbacks, and automation events.',
39
+ detail: 'Review the external HTTP listener required for incoming webhooks, callbacks, and automation events.',
40
40
  },
41
41
  {
42
42
  id: 'external-integrations',
@@ -48,7 +48,7 @@ export const DEFAULT_CAPABILITIES: readonly OnboardingStep1CapabilityItem[] = [
48
48
  id: 'cloudflare-batch',
49
49
  label: 'Use Cloudflare for batch or remote daemon work',
50
50
  selected: false,
51
- detail: 'Optionally configure Cloudflare Workers and Queues for explicit or eligible background batch jobs. Immediate local daemon behavior stays the default unless enabled.',
51
+ detail: 'Optionally configure Cloudflare Workers and Queues for explicit or eligible background batch jobs. The external daemon still owns execution.',
52
52
  },
53
53
  ];
54
54
 
@@ -60,13 +60,13 @@ export const REASONING_OPTIONS: readonly OnboardingWizardRadioOption[] = [
60
60
  ];
61
61
 
62
62
  export const NETWORK_MODE_OPTIONS: readonly OnboardingWizardRadioOption[] = [
63
- { id: 'local-network-default', label: 'Local Network (Default)', hint: 'Use the default LAN-facing setup for enabled browser, service, and event features.' },
64
- { id: 'custom', label: 'Custom', hint: 'Choose IP addresses and ports for each enabled service.' },
63
+ { id: 'local-network-default', label: 'Local Network (Default)', hint: 'Review the default LAN-facing external daemon setup for browser, control-plane, and event features.' },
64
+ { id: 'custom', label: 'Custom', hint: 'Review IP addresses and ports for each external daemon surface.' },
65
65
  ];
66
66
 
67
67
  export const HITL_MODE_OPTIONS: readonly OnboardingWizardRadioOption[] = [
68
68
  { id: 'quiet', label: 'Quiet', hint: 'Only interrupt for important attention requests.' },
69
- { id: 'balanced', label: 'Balanced', hint: 'Show important activity without turning the TUI into a log stream.' },
69
+ { id: 'balanced', label: 'Balanced', hint: 'Show important activity without turning Agent into a log stream.' },
70
70
  { id: 'operator', label: 'Operator', hint: 'Keep operational activity visible for hands-on supervision.' },
71
71
  ];
72
72
 
@@ -90,7 +90,7 @@ export const SECRET_POLICY_OPTIONS: readonly OnboardingWizardRadioOption[] = [
90
90
 
91
91
  export const TELEGRAM_MODE_OPTIONS: readonly OnboardingWizardRadioOption[] = [
92
92
  { id: 'webhook', label: 'Webhook', hint: 'Receive Telegram updates through a webhook.' },
93
- { id: 'polling', label: 'Polling', hint: 'Poll Telegram for updates from the background service.' },
93
+ { id: 'polling', label: 'Polling', hint: 'The external daemon polls Telegram for updates.' },
94
94
  ];
95
95
 
96
96
  export const WHATSAPP_PROVIDER_OPTIONS: readonly OnboardingWizardRadioOption[] = [
@@ -105,8 +105,8 @@ export const HOME_ASSISTANT_SURFACE_SPEC: ExternalSurfaceSpec = {
105
105
  kind: 'text',
106
106
  label: 'Home Assistant device ID',
107
107
  hint: 'Stable device identifier exposed by the GoodVibes daemon.',
108
- placeholder: 'goodvibes-daemon',
109
- defaultValue: (snapshot) => snapshot?.config.surfaces.homeassistant.deviceId ?? 'goodvibes-daemon',
108
+ placeholder: 'goodvibes-agent',
109
+ defaultValue: (snapshot) => snapshot?.config.surfaces.homeassistant.deviceId ?? 'goodvibes-agent',
110
110
  },
111
111
  {
112
112
  id: 'external-services.homeassistant.device-name',
@@ -114,8 +114,8 @@ export const HOME_ASSISTANT_SURFACE_SPEC: ExternalSurfaceSpec = {
114
114
  kind: 'text',
115
115
  label: 'Home Assistant device name',
116
116
  hint: 'Display name for the GoodVibes daemon device in Home Assistant.',
117
- placeholder: 'GoodVibes Daemon',
118
- defaultValue: (snapshot) => snapshot?.config.surfaces.homeassistant.deviceName ?? 'GoodVibes Daemon',
117
+ placeholder: 'GoodVibes Agent',
118
+ defaultValue: (snapshot) => snapshot?.config.surfaces.homeassistant.deviceName ?? 'GoodVibes Agent',
119
119
  },
120
120
  {
121
121
  id: 'external-services.homeassistant.event-type',
@@ -436,7 +436,7 @@ function buildExternalSurfaceStep(
436
436
  id: `external-surface:${surface.id}` as OnboardingWizardExternalSurfaceStepId,
437
437
  title,
438
438
  shortLabel: surface.label.replace(/ surface$/i, ''),
439
- description: `Configure ${surface.label}. Settings are saved either way; Agent does not start or own the background service.`,
439
+ description: `Configure ${surface.label}. Settings are saved either way; Agent does not start or own the external daemon service.`,
440
440
  summaryTitle: `${surface.label} setup`,
441
441
  summaryLines: [
442
442
  `External activation requested: ${autoStartValue === 'yes' ? 'yes' : 'no'}`,
@@ -540,8 +540,8 @@ export function buildNetworkStep(controller: OnboardingWizardController): Onboar
540
540
  const sharedIpField: OnboardingWizardChecklistFieldDefinition = {
541
541
  kind: 'checklist',
542
542
  id: 'network.shared-ip',
543
- label: 'Use the same IP address for all services',
544
- hint: 'When included, browser, GoodVibes service, and webhook listener network bindings share one IP address.',
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
545
  defaultValue: controller.getSharedIpDefault(networkEnabled),
546
546
  };
547
547
  const sharedIp = controller.getBooleanFieldValue(sharedIpField.id, sharedIpField.defaultValue);
@@ -561,8 +561,8 @@ export function buildNetworkStep(controller: OnboardingWizardController): Onboar
561
561
  fields.push({
562
562
  kind: 'text',
563
563
  id: 'network.service-port',
564
- label: 'GoodVibes service port',
565
- hint: 'Port for the background service and control plane.',
564
+ label: 'External daemon control-plane port',
565
+ hint: 'Port exposed by the external daemon control plane.',
566
566
  placeholder: '3421',
567
567
  defaultValue: String(bindSettings?.controlPlane.port ?? 3421),
568
568
  });
@@ -570,8 +570,8 @@ export function buildNetworkStep(controller: OnboardingWizardController): Onboar
570
570
  fields.push({
571
571
  kind: 'text',
572
572
  id: 'network.service-ip',
573
- label: 'GoodVibes service IP address',
574
- hint: 'IP address for the background service and control plane.',
573
+ label: 'External daemon control-plane IP address',
574
+ hint: 'IP address exposed by the external daemon control plane.',
575
575
  placeholder: '0.0.0.0',
576
576
  defaultValue: normalizeText(bindSettings?.controlPlane.host) || '0.0.0.0',
577
577
  });
@@ -625,7 +625,7 @@ export function buildNetworkStep(controller: OnboardingWizardController): Onboar
625
625
  id: 'network',
626
626
  title: 'Network setup',
627
627
  shortLabel: 'Network',
628
- description: 'Choose the LAN default or customize IP addresses and ports for the enabled browser, service, and listener surfaces.',
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
629
  summaryTitle: 'Bind posture',
630
630
  summaryLines: [
631
631
  `Mode: ${custom ? 'custom' : 'local network default'}`,
@@ -653,7 +653,7 @@ export function buildAccountsStep(controller: OnboardingWizardController): Onboa
653
653
  id: 'accounts.admin-username',
654
654
  label: 'Local auth admin username',
655
655
  hint: needsAuthBootstrap
656
- ? 'Required before any background service, browser surface, or listener is exposed.'
656
+ ? 'Required before the external daemon exposes browser, control-plane, or listener surfaces.'
657
657
  : 'Optional. Enter an existing admin username to rotate its password, or a new username to create another admin.',
658
658
  placeholder: defaultAdminUsername,
659
659
  defaultValue: defaultAdminUsername,
@@ -666,7 +666,7 @@ export function buildAccountsStep(controller: OnboardingWizardController): Onboa
666
666
  hint: needsAuthBootstrap
667
667
  ? controller.hasBootstrapCredentialPresent()
668
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 LAN/server settings are applied.'
669
+ : 'Creates the first local admin user and an initial session before external daemon network settings are used by the daemon owner.'
670
670
  : 'Optional. Leave blank to keep existing local auth unchanged; enter a password to create or rotate the named admin user.',
671
671
  placeholder: needsAuthBootstrap ? 'password required' : 'leave blank to keep unchanged',
672
672
  defaultValue: '',
@@ -724,7 +724,7 @@ export function buildAccountsStep(controller: OnboardingWizardController): Onboa
724
724
  title: 'Subscriptions and auth review',
725
725
  shortLabel: 'Accounts',
726
726
  description: needsAuthBootstrap
727
- ? 'Create wizard-owned local auth before any LAN, browser, service, or listener settings are applied.'
727
+ ? 'Create local auth state before external daemon LAN, browser, service, or listener settings are applied by the daemon owner.'
728
728
  : 'Review existing subscription and local auth state. Existing local auth is kept unless you change it elsewhere.',
729
729
  summaryTitle: 'Stored account state',
730
730
  summaryLines: [
@@ -732,8 +732,8 @@ export function buildAccountsStep(controller: OnboardingWizardController): Onboa
732
732
  `Auth: ${auth?.userCount ?? 0} users / ${auth?.sessionCount ?? 0} sessions`,
733
733
  needsAuthBootstrap
734
734
  ? controller.hasBootstrapCredentialPresent()
735
- ? 'Bootstrap credentials will be replaced before network settings are applied'
736
- : 'Local admin will be created before network settings are applied'
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
737
  : controller.hasLocalAuthUser() ? 'Existing local auth will be kept' : 'Local auth is not required for this setup',
738
738
  ],
739
739
  fields,
@@ -2,7 +2,7 @@
2
2
  * ProfilePickerModal — state management for the /profiles picker modal.
3
3
  *
4
4
  * Lists profiles from ProfileManager.list(), tracks selected index,
5
- * and handles load/delete/save actions.
5
+ * and handles load actions.
6
6
  */
7
7
 
8
8
  import type { ProfileInfo, ProfileData, ProfileManager } from '@pellux/goodvibes-sdk/platform/profiles';
@@ -141,39 +141,12 @@ export class ProfilePickerModal {
141
141
  }
142
142
  }
143
143
 
144
- /**
145
- * Delete the selected profile from disk.
146
- * Refreshes the list after deletion.
147
- */
148
144
  deleteSelected(): boolean {
149
145
  const profile = this.getSelected();
150
146
  if (!profile) return false;
151
- if (this.deleteConfirmationTarget !== profile.name) {
152
- this.deleteConfirmationTarget = profile.name;
153
- this.statusMessage = `Press delete again to remove profile: ${profile.name}`;
154
- return false;
155
- }
156
-
157
- try {
158
- const deleted = this.profileManager.delete(profile.name);
159
- if (!deleted) {
160
- this.statusMessage = `Profile not found: ${profile.name}`;
161
- this.deleteConfirmationTarget = null;
162
- return false;
163
- }
164
- this.profiles = this.profileManager.list();
165
- if (this.selectedIndex >= this.profiles.length) {
166
- this.selectedIndex = Math.max(0, this.profiles.length - 1);
167
- }
168
- this._clampScroll();
169
- this.deleteConfirmationTarget = null;
170
- this.statusMessage = `Deleted: ${profile.name}`;
171
- return true;
172
- } catch (e) {
173
- this.deleteConfirmationTarget = null;
174
- this.statusMessage = `Error: ${summarizeError(e)}`;
175
- return false;
176
- }
147
+ this.deleteConfirmationTarget = null;
148
+ this.statusMessage = `Deletion requires an explicit command: /profiles delete ${profile.name} --yes`;
149
+ return false;
177
150
  }
178
151
 
179
152
  /**
@@ -184,7 +157,16 @@ export class ProfilePickerModal {
184
157
  this.statusMessage = 'Profile name cannot be empty';
185
158
  return false;
186
159
  }
160
+ void configManager;
161
+ this.statusMessage = `Saving requires an explicit command: /profiles save ${name} --yes`;
162
+ return false;
163
+ }
187
164
 
165
+ public saveCurrentAsConfirmed(name: string, configManager: ConfigManager): boolean {
166
+ if (!name || !name.trim()) {
167
+ this.statusMessage = 'Profile name cannot be empty';
168
+ return false;
169
+ }
188
170
  try {
189
171
  const all = configManager.getAll();
190
172
  const data: ProfileData = {
@@ -2,10 +2,9 @@
2
2
  * SessionPickerModal — state management for the /sessions picker modal.
3
3
  *
4
4
  * Lists sessions from SessionManager.list(), tracks selected index,
5
- * and handles load/delete actions.
5
+ * and handles load actions.
6
6
  */
7
7
 
8
- import { unlinkSync } from 'node:fs';
9
8
  import type { SessionInfo, SessionManager } from '@pellux/goodvibes-sdk/platform/sessions';
10
9
  import type { ConversationManager } from '../core/conversation';
11
10
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
@@ -90,37 +89,12 @@ export class SessionPickerModal {
90
89
  }
91
90
  }
92
91
 
93
- /**
94
- * Delete the currently selected session from disk.
95
- * Refreshes the list after deletion.
96
- */
97
92
  deleteSelected(): boolean {
98
93
  const session = this.getSelected();
99
94
  if (!session) return false;
100
- if (this.deleteConfirmationTarget !== session.name) {
101
- this.deleteConfirmationTarget = session.name;
102
- this.statusMessage = `Press d again to delete ${session.name}.`;
103
- return false;
104
- }
105
-
106
- try {
107
- // Delete directly via filePath so it works with any session directory
108
- unlinkSync(session.filePath);
109
- // Reload list from the global session manager (removes the deleted entry)
110
- this.sessions = this.sessionManager.list();
111
- // Adjust selection
112
- if (this.selectedIndex >= this.sessions.length) {
113
- this.selectedIndex = Math.max(0, this.sessions.length - 1);
114
- }
115
- this._clampScroll();
116
- this.deleteConfirmationTarget = null;
117
- this.statusMessage = `Deleted: ${session.name}`;
118
- return true;
119
- } catch (e) {
120
- this.deleteConfirmationTarget = null;
121
- this.statusMessage = `Error: ${summarizeError(e)}`;
122
- return false;
123
- }
95
+ this.deleteConfirmationTarget = null;
96
+ this.statusMessage = `Deletion requires an explicit command: /session delete ${session.name} --yes`;
97
+ return false;
124
98
  }
125
99
 
126
100
  private _clampScroll(): void {
@@ -0,0 +1,18 @@
1
+ export const AGENT_EXTERNAL_DAEMON_SETTING_LOCK_REASON = 'GoodVibes Agent connects to an external daemon. Change this from GoodVibes TUI or the daemon host; Agent settings are read-only for daemon lifecycle and bind posture.';
2
+
3
+ const EXTERNAL_DAEMON_SETTING_PREFIXES = [
4
+ 'service.',
5
+ 'controlPlane.',
6
+ 'httpListener.',
7
+ 'web.',
8
+ ] as const;
9
+
10
+ const EXTERNAL_DAEMON_SETTING_KEYS = new Set<string>([
11
+ 'danger.daemon',
12
+ 'danger.httpListener',
13
+ ]);
14
+
15
+ export function isExternalDaemonOwnedSettingKey(key: string): boolean {
16
+ return EXTERNAL_DAEMON_SETTING_KEYS.has(key)
17
+ || EXTERNAL_DAEMON_SETTING_PREFIXES.some((prefix) => key.startsWith(prefix));
18
+ }
@@ -23,7 +23,7 @@ export function buildSubscriptionEntries(
23
23
  activeRoute: 'unconfigured',
24
24
  authFreshness: 'unconfigured',
25
25
  routeReason: 'Built-in subscription adapter is available, but no active subscription session is stored yet.',
26
- nextActions: [`Use /subscription login ${provider} start to begin browser sign-in.`],
26
+ nextActions: [`Use /subscription login ${provider} start --yes to begin browser sign-in.`],
27
27
  });
28
28
  }
29
29
 
@@ -38,7 +38,7 @@ export function buildSubscriptionEntries(
38
38
  activeRoute: providers.get(provider)?.activeRoute ?? 'unconfigured',
39
39
  authFreshness: providers.get(provider)?.authFreshness ?? 'unconfigured',
40
40
  routeReason: providers.get(provider)?.routeReason ?? 'OAuth metadata is configured for this provider.',
41
- nextActions: providers.get(provider)?.nextActions ?? [`Use /subscription login ${provider} start to begin browser sign-in.`],
41
+ nextActions: providers.get(provider)?.nextActions ?? [`Use /subscription login ${provider} start --yes to begin browser sign-in.`],
42
42
  });
43
43
  }
44
44
 
@@ -51,7 +51,7 @@ export function buildSubscriptionEntries(
51
51
  activeRoute: 'unconfigured',
52
52
  authFreshness: 'pending',
53
53
  routeReason: 'OAuth login is pending completion for this provider.',
54
- nextActions: [`Finish /subscription login ${pending.provider} finish <code> to activate this session.`],
54
+ nextActions: [`Finish /subscription login ${pending.provider} finish <code> --yes to activate this session.`],
55
55
  });
56
56
  }
57
57
 
@@ -1,7 +1,24 @@
1
1
  import type { ConfigSetting } from '@pellux/goodvibes-sdk/platform/config';
2
+ import type { ConfigKey } from '@pellux/goodvibes-sdk/platform/config';
2
3
  import type { ProviderAuthFreshness, ProviderAuthRoute } from '@/runtime/index.ts';
3
4
  import type { FeatureFlag, FlagState } from '@/runtime/index.ts';
4
5
 
6
+ export interface SettingsModalChange {
7
+ readonly key: ConfigKey;
8
+ readonly previousValue: unknown;
9
+ readonly value: unknown;
10
+ }
11
+
12
+ export interface SettingsModalChangeResult {
13
+ readonly message?: string;
14
+ }
15
+
16
+ export type SettingsModalChangeHandler = (change: SettingsModalChange) => SettingsModalChangeResult | void;
17
+
18
+ export interface SettingsModalOpenOptions {
19
+ readonly onSettingApplied?: SettingsModalChangeHandler;
20
+ }
21
+
5
22
  export type SettingsCategory =
6
23
  | 'display'
7
24
  | 'ui'
@@ -1,14 +1,4 @@
1
- /**
2
- * SettingsModal — state management for the /settings and /config fullscreen workspace.
3
- *
4
- * Loads CONFIG_SCHEMA, groups settings by category, and tracks UI state:
5
- * - Active category (Tab to cycle)
6
- * - Selected setting index within category (↑↓)
7
- * - Editing mode for inline string/number input
8
- * - Feature flags tab with runtime toggle support
9
- *
10
- * Saves changes via configManager.set(key, value) or featureFlagManager methods.
11
- */
1
+ /** SettingsModal state for the /settings and /config fullscreen workspace. */
12
2
 
13
3
  import { CONFIG_SCHEMA, type ConfigKey, type PersistedFlagState } from '@pellux/goodvibes-sdk/platform/config';
14
4
  import type { ModelPickerTarget } from './model-picker.ts';
@@ -40,24 +30,11 @@ import {
40
30
  type SettingEntry,
41
31
  type SettingsCategory,
42
32
  type SettingsFocusPane,
33
+ type SettingsModalChangeHandler,
34
+ type SettingsModalOpenOptions,
43
35
  type SubscriptionEntry,
44
36
  } from './settings-modal-types.ts';
45
-
46
- export interface SettingsModalChange {
47
- readonly key: ConfigKey;
48
- readonly previousValue: unknown;
49
- readonly value: unknown;
50
- }
51
-
52
- export interface SettingsModalChangeResult {
53
- readonly message?: string;
54
- }
55
-
56
- export type SettingsModalChangeHandler = (change: SettingsModalChange) => SettingsModalChangeResult | void;
57
-
58
- export interface SettingsModalOpenOptions {
59
- readonly onSettingApplied?: SettingsModalChangeHandler;
60
- }
37
+ import { AGENT_EXTERNAL_DAEMON_SETTING_LOCK_REASON, isExternalDaemonOwnedSettingKey } from './settings-modal-agent-policy.ts';
61
38
 
62
39
  export {
63
40
  SETTINGS_CATEGORIES,
@@ -67,8 +44,13 @@ export {
67
44
  type SettingEntry,
68
45
  type SettingsCategory,
69
46
  type SettingsFocusPane,
47
+ type SettingsModalChange,
48
+ type SettingsModalChangeHandler,
49
+ type SettingsModalChangeResult,
50
+ type SettingsModalOpenOptions,
70
51
  type SubscriptionEntry,
71
52
  } from './settings-modal-types.ts';
53
+ export { AGENT_EXTERNAL_DAEMON_SETTING_LOCK_REASON, isExternalDaemonOwnedSettingKey } from './settings-modal-agent-policy.ts';
72
54
 
73
55
  // ---------------------------------------------------------------------------
74
56
  // SettingsModal
@@ -369,6 +351,7 @@ export class SettingsModal {
369
351
 
370
352
  const entry = this.getSelected();
371
353
  if (!entry || !this.configManager) return;
354
+ if (this._blockExternalDaemonOwnedSetting(entry)) return;
372
355
 
373
356
  const { setting } = entry;
374
357
 
@@ -433,6 +416,7 @@ export class SettingsModal {
433
416
 
434
417
  const entry = this.getSelected();
435
418
  if (!entry || !this.configManager) return;
419
+ if (this._blockExternalDaemonOwnedSetting(entry)) return;
436
420
  const { setting } = entry;
437
421
 
438
422
  if (setting.type === 'boolean') {
@@ -554,6 +538,11 @@ export class SettingsModal {
554
538
 
555
539
  const entry = this.getSelected();
556
540
  if (!entry || !this.configManager) return false;
541
+ if (this._blockExternalDaemonOwnedSetting(entry)) {
542
+ this.editingMode = false;
543
+ this.editBuffer = '';
544
+ return false;
545
+ }
557
546
 
558
547
  const { setting } = entry;
559
548
  let parsed: unknown = this.editBuffer;
@@ -600,6 +589,7 @@ export class SettingsModal {
600
589
  if (this.editingMode || !this.configManager) return null;
601
590
  const entry = this.getSelected();
602
591
  if (!entry) return null;
592
+ if (this._blockExternalDaemonOwnedSetting(entry)) return null;
603
593
  const key = entry.setting.key as ConfigKey;
604
594
  this._setValue(key, entry.setting.default);
605
595
  if (isSecretConfigKey(key) && this.secretsManager) {
@@ -635,15 +625,16 @@ export class SettingsModal {
635
625
  const cat = rawCat as SettingsCategory;
636
626
  const currentValue = configManager.get(setting.key as ConfigKey);
637
627
  const resolved = getResolvedSettingLookup(configManager, setting.key as ConfigKey)?.entry;
628
+ const daemonOwned = isExternalDaemonOwnedSettingKey(setting.key);
638
629
  const entry: SettingEntry = {
639
630
  setting,
640
631
  currentValue,
641
632
  isDefault: currentValue === setting.default,
642
633
  effectiveSource: resolved?.effectiveSource,
643
- locked: resolved?.locked,
634
+ locked: daemonOwned || resolved?.locked,
644
635
  conflict: resolved?.conflict,
645
636
  sourceLabel: resolved?.sourceLabel,
646
- lockReason: resolved?.lockReason,
637
+ lockReason: daemonOwned ? AGENT_EXTERNAL_DAEMON_SETTING_LOCK_REASON : resolved?.lockReason,
647
638
  };
648
639
  if (this.groups.has(cat)) this.groups.get(cat)!.push(entry);
649
640
  if ((rawCat === 'controlPlane' || rawCat === 'httpListener' || rawCat === 'web') && this.groups.has('network')) {
@@ -752,6 +743,10 @@ export class SettingsModal {
752
743
 
753
744
  private _setValue(key: ConfigKey, value: unknown): void {
754
745
  if (!this.configManager) return;
746
+ if (isExternalDaemonOwnedSettingKey(key)) {
747
+ this.lastSettingEffectMessage = AGENT_EXTERNAL_DAEMON_SETTING_LOCK_REASON;
748
+ return;
749
+ }
755
750
  // Diff previous value before writing — avoids false restart notices on no-op saves
756
751
  const previousValue = this.configManager.get(key);
757
752
  const isRestartKey = ['host', 'port', 'hostMode', 'enabled'].includes(key.split('.')[1] ?? '');
@@ -790,4 +785,10 @@ export class SettingsModal {
790
785
  }
791
786
  }
792
787
 
788
+ private _blockExternalDaemonOwnedSetting(entry: SettingEntry): boolean {
789
+ if (!isExternalDaemonOwnedSettingKey(entry.setting.key)) return false;
790
+ this.lastSettingEffectMessage = entry.lockReason ?? AGENT_EXTERNAL_DAEMON_SETTING_LOCK_REASON;
791
+ return true;
792
+ }
793
+
793
794
  }
package/src/main.ts CHANGED
@@ -20,8 +20,6 @@ import { registerBuiltinCommands } from './input/commands.ts';
20
20
  import { ScheduleManager } from '@pellux/goodvibes-sdk/platform/tools';
21
21
  import { InputHistory } from './input/input-history.ts';
22
22
  import { getTierPromptSupplement, getTierForContextWindow } from '@pellux/goodvibes-sdk/platform/providers';
23
- import { GitStatusProvider } from './renderer/git-status.ts';
24
- import type { GitHeaderInfo } from './renderer/git-status.ts';
25
23
  import { createShellLayout } from './renderer/layout-engine.ts';
26
24
  import { buildShellFooter, estimateShellFooterHeight } from './renderer/shell-surface.ts';
27
25
  import { buildConversationViewport } from './renderer/conversation-layout.ts';
@@ -53,7 +51,6 @@ import { attachSpokenTurnModelRouting, createSpokenTurnInputOptions } from './au
53
51
  import { allowTerminalWrite, installTuiTerminalOutputGuard } from './runtime/terminal-output-guard.ts';
54
52
  import { ProjectPlanningCoordinator } from './planning/project-planning-coordinator.ts';
55
53
  import { buildCommandArgsHint } from './input/command-args-hint.ts';
56
- import { summarizeRunningAgents } from './renderer/process-summary.ts';
57
54
  import { GOODVIBES_AGENT_PAIRING_SURFACE } from './config/surface.ts';
58
55
 
59
56
  const ALT_SCREEN_ENTER = '\x1b[?1049h';
@@ -93,8 +90,6 @@ async function main() {
93
90
  commandRegistry,
94
91
  inputHistory,
95
92
  hookDispatcher,
96
- gitStatusProvider,
97
- lastGitInfoRef,
98
93
  bootstrapUnsubs,
99
94
  agentStatusIntervalRef,
100
95
  orchestratorRefs,
@@ -133,15 +128,12 @@ async function main() {
133
128
  const sessionSnapshot = uiServices.readModels.session.getSnapshot();
134
129
  const tasksSnapshot = uiServices.readModels.tasks.getSnapshot();
135
130
  const remoteSnapshot = uiServices.readModels.remote.getSnapshot();
136
- const worktreeSnapshot = uiServices.readModels.worktrees.getSnapshot();
137
131
  return {
138
132
  pendingApprovals: sessionSnapshot.pendingApproval ? 1 : 0,
139
133
  activeTasks: tasksSnapshot.tasks.filter((task) => task.status === 'running' || task.status === 'queued').length,
140
134
  blockedTasks: tasksSnapshot.tasks.filter((task) => task.status === 'blocked').length,
141
135
  remoteContracts: remoteSnapshot.contracts.length,
142
136
  remoteRunners: remoteSnapshot.contracts.slice(0, 4).map((contract) => contract.runnerId),
143
- worktreeCount: worktreeSnapshot.records.length,
144
- worktreePaths: worktreeSnapshot.records.slice(0, 3).map((record) => record.path),
145
137
  openPanels: panelManager.getAllOpen().map((panel) => panel.id),
146
138
  };
147
139
  };
@@ -478,15 +470,9 @@ async function main() {
478
470
  // Cache the current model for consistent values across the entire render frame
479
471
  const currentModel = providerRegistry.getCurrentModel();
480
472
  const sessionSnapshot = uiServices.readModels.session.getSnapshot();
481
- const agentSnapshot = uiServices.readModels.agents.getSnapshot();
482
473
 
483
- const headerLines = UIFactory.createHeader(width, currentModel.id, currentModel.provider, conversation.title || undefined, lastGitInfoRef.value);
484
- const managerAgents = agentManager.list().filter(
485
- (a) => a.status === 'running' || a.status === 'pending',
486
- );
487
- const runtimeAgents = agentSnapshot.active;
488
- const runningAgentSummary = summarizeRunningAgents(managerAgents, runtimeAgents, ctx.services.wrfcController.listChains());
489
- const runningAgentCount = runningAgentSummary.count;
474
+ const headerLines = UIFactory.createHeader(width, currentModel.id, currentModel.provider, conversation.title || undefined);
475
+ const runningAgentCount = 0;
490
476
  const runningProcessCount = processManager.list().filter((p) => !p.status.startsWith('done')).length;
491
477
  const cw = getPromptContentWidth();
492
478
  const promptInfo = input.getWrappedPromptInfo(cw);
@@ -533,7 +519,7 @@ async function main() {
533
519
  runningAgentCount,
534
520
  runningProcessCount,
535
521
  indicatorFocused: input.indicatorFocused,
536
- runningAgentProgress: runningAgentSummary.progress,
522
+ runningAgentProgress: undefined,
537
523
  composerMode: composerState.modeLabel,
538
524
  composerStatus: composerState.statusLabel,
539
525
  composerFlags: composerState.flags,
@@ -684,8 +670,6 @@ async function main() {
684
670
  });
685
671
 
686
672
  // --- Streaming speed + tool preview wiring ---
687
- const refreshGit = () => gitStatusProvider.refresh().then((info) => { lastGitInfoRef.value = info; render(); }).catch(() => { /* non-fatal */ });
688
- // Refresh git status after each turn completes or after tool results arrive
689
673
  unsubs.push(uiServices.events.turns.on('TURN_COMPLETED', () => {
690
674
  // Auto-save after every LLM turn so kills don't lose the session
691
675
  try {
@@ -701,13 +685,6 @@ async function main() {
701
685
  );
702
686
  hookDispatcher.fire({ path: 'Lifecycle:session:save' as HookEventPath, phase: 'Lifecycle' as HookPhase, category: 'session' as HookCategory, specific: 'save', sessionId: runtime.sessionId, timestamp: Date.now(), payload: { sessionId: runtime.sessionId } }).catch((err: unknown) => logger.debug('hook fire error', { error: summarizeError(err) }));
703
687
  } catch (e) { logger.debug('auto-save on turn:complete failed', { error: summarizeError(e) }); }
704
- refreshGit();
705
- }));
706
- unsubs.push(uiServices.events.tools.on('TOOL_SUCCEEDED', () => {
707
- refreshGit();
708
- }));
709
- unsubs.push(uiServices.events.tools.on('TOOL_FAILED', () => {
710
- refreshGit();
711
688
  }));
712
689
 
713
690
  unsubs.push(uiServices.events.turns.on('STREAM_START', () => {
@@ -185,7 +185,7 @@ export class IncidentReviewPanel extends ScrollableListPanel<FailureReport> {
185
185
  }
186
186
  }
187
187
  footerLines.push(buildPanelLine(width, [[' Action Rail', C.label]]));
188
- footerLines.push(buildPanelLine(width, [[` /incident latest /incident export ${selected.id} /recall capture incident ${selected.id}`, C.info]]));
188
+ footerLines.push(buildPanelLine(width, [[` /incident latest /incident export ${selected.id} <path> --yes /incident capture ${selected.id} --yes`, C.info]]));
189
189
  footerLines.push(buildGuidanceLine(width, '/security', 'open the broader trust and incident posture control room', C));
190
190
 
191
191
  return this.renderList(width, height, {
@@ -81,7 +81,7 @@ export class LocalAuthPanel extends ScrollableListPanel<LocalAuthUser> {
81
81
  ...(issueMessages.length > 0
82
82
  ? issueMessages.map((issue) => buildPanelLine(width, [[` issue: ${issue}`.slice(0, Math.max(0, width)), C.warn]]))
83
83
  : [buildPanelLine(width, [[' local auth posture looks healthy.', C.good]])]),
84
- buildGuidanceLine(width, '/auth local rotate-password <user> <password>', 'rotate bootstrap/default credentials and revoke older sessions as needed', C),
84
+ buildGuidanceLine(width, '/auth local rotate-password <user> <password> --yes', 'rotate bootstrap/default credentials and revoke older sessions as needed', C),
85
85
  ], C),
86
86
  ];
87
87
 
@@ -104,8 +104,8 @@ export class LocalAuthPanel extends ScrollableListPanel<LocalAuthUser> {
104
104
  footerLines.push(
105
105
  ...buildDetailBlock(width, 'Selected user', [
106
106
  buildPanelLine(width, [[' username ', C.label], [selected.username, C.value], [' roles ', C.label], [formatRoles(selected.roles).slice(0, Math.max(0, width - 23)), C.info]]),
107
- buildPanelLine(width, [[` next: /auth local rotate-password ${selected.username} <password>`.slice(0, Math.max(0, width)), C.dim]]),
108
- buildPanelLine(width, [[` next: /auth local delete-user ${selected.username}`.slice(0, Math.max(0, width)), C.dim]]),
107
+ buildPanelLine(width, [[` next: /auth local rotate-password ${selected.username} <password> --yes`.slice(0, Math.max(0, width)), C.dim]]),
108
+ buildPanelLine(width, [[` next: /auth local delete-user ${selected.username} --yes`.slice(0, Math.max(0, width)), C.dim]]),
109
109
  ], C),
110
110
  );
111
111
  }
@@ -119,7 +119,7 @@ export class LocalAuthPanel extends ScrollableListPanel<LocalAuthUser> {
119
119
  ])),
120
120
  );
121
121
  }
122
- footerLines.push(buildPanelLine(width, [[' /auth local review /auth local add-user /auth local rotate-password /auth local revoke-session ', C.dim]]));
122
+ footerLines.push(buildPanelLine(width, [[' /auth local review mutations require --yes: add-user rotate-password revoke-session ', C.dim]]));
123
123
 
124
124
  return this.renderList(width, height, {
125
125
  title: 'Local Auth Control Room',
@@ -209,7 +209,7 @@ export async function buildProviderAccountSnapshot(
209
209
  }
210
210
  if (pending) {
211
211
  issues.push('Provider has a pending OAuth login that has not been completed yet.');
212
- recommendedActions.push(`Finish /subscription login ${providerId} finish <code> or clear the pending login.`);
212
+ recommendedActions.push(`Finish /subscription login ${providerId} finish <code> --yes or clear the pending login.`);
213
213
  }
214
214
  if (hasSubscription && hasApiKey) {
215
215
  issues.push('Provider has both subscription and API-key auth paths; routing must remain explicit.');
@@ -54,13 +54,13 @@ export function buildProviderHealthDomainSummaries(
54
54
  summary: auth.bootstrapCredentialPresent
55
55
  ? 'bootstrap credential file still present'
56
56
  : `${auth.userCount} users / ${auth.sessionCount} sessions`,
57
- next: auth.bootstrapCredentialPresent ? '/auth local clear-bootstrap-file' : '/auth local review',
57
+ next: auth.bootstrapCredentialPresent ? '/auth local clear-bootstrap-file --yes' : '/auth local review',
58
58
  details: [
59
59
  auth.bootstrapCredentialPresent ? 'bootstrap credential file should be cleared after rotation' : `${auth.userCount} local auth users configured`,
60
60
  auth.userCount <= 1 ? 'only one local auth user configured' : `${auth.sessionCount} active local auth sessions`,
61
61
  ].filter(Boolean),
62
62
  nextSteps: auth.bootstrapCredentialPresent
63
- ? ['/auth local review', '/auth local rotate-password <user> <password>', '/auth local clear-bootstrap-file']
63
+ ? ['/auth local review', '/auth local rotate-password <user> <password> --yes', '/auth local clear-bootstrap-file --yes']
64
64
  : ['/auth local review'],
65
65
  });
66
66