@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
@@ -86,7 +86,7 @@ export class SettingsSyncPanel extends ScrollableListPanel<ResolvedEntry> {
86
86
  : [buildPanelLine(width, [[' No recent sync or managed-setting failures.', C.dim]])]),
87
87
  // Conflicts
88
88
  ...(snapshot.conflicts.length > 0
89
- ? snapshot.conflicts.map((conflict) => buildPanelLine(width, [[` ${conflict.key}`.padEnd(30), C.value], [` ${conflict.source}`.padEnd(10), C.warn], [` resolve: /settingssync resolve ${conflict.key} local|synced`.slice(0, Math.max(0, width - 42)), C.dim]]))
89
+ ? snapshot.conflicts.map((conflict) => buildPanelLine(width, [[` ${conflict.key}`.padEnd(30), C.value], [` ${conflict.source}`.padEnd(10), C.warn], [` resolve: /settingssync resolve ${conflict.key} local|synced --yes`.slice(0, Math.max(0, width - 42)), C.dim]]))
90
90
  : [buildPanelLine(width, [[' No settings conflicts detected.', C.dim]])]),
91
91
  // Rollback History
92
92
  ...(snapshot.rollbackHistory.length > 0
@@ -108,7 +108,7 @@ export class SettingsSyncPanel extends ScrollableListPanel<ResolvedEntry> {
108
108
  buildPanelLine(width, [[' managed ', C.label], [String(selectedEntry.managedValue ?? '(unset)').slice(0, Math.max(0, width - 11)), C.warn]]),
109
109
  ], C)
110
110
  : []),
111
- buildPanelLine(width, [[' ↑/↓ browse /settingssync show <key> /settingssync resolve <key> <local|synced> /managed apply-staged [key...] ', C.dim]]),
111
+ buildPanelLine(width, [[' ↑/↓ browse /settingssync show <key> mutations require --yes: resolve/apply-staged ', C.dim]]),
112
112
  ];
113
113
 
114
114
  return this.renderList(width, height, {
@@ -78,8 +78,8 @@ export class SubscriptionPanel extends ScrollableListPanel<SubscriptionRow> {
78
78
  protected override getEmptyStateMessage() { return ' No provider subscriptions are active yet.'; }
79
79
  protected override getEmptyStateActions() {
80
80
  return [
81
- { command: '/subscription login openai start', summary: 'start the first-class OpenAI subscription flow' },
82
- { command: '/login provider <name> start', summary: 'use the front-door auth surface for supported providers' },
81
+ { command: '/subscription login openai start --yes', summary: 'start the first-class OpenAI subscription flow' },
82
+ { command: '/login provider <name> start --yes', summary: 'use the front-door auth surface for supported providers' },
83
83
  { command: '/services auth-review', summary: 'inspect configured service auth posture and stored secrets' },
84
84
  ];
85
85
  }
@@ -187,7 +187,7 @@ export class SubscriptionPanel extends ScrollableListPanel<SubscriptionRow> {
187
187
  { label: 'selected', value: (this.rows[this.selectedIndex]?.provider ?? 'none'), valueColor: this.rows[this.selectedIndex] ? C.value : C.dim },
188
188
  { label: 'status', value: this.rows[this.selectedIndex] ? statusOf(this.rows[this.selectedIndex]!) : 'n/a', valueColor: this.rows[this.selectedIndex] ? statusColor(statusOf(this.rows[this.selectedIndex]!)) : C.dim },
189
189
  ], C),
190
- buildGuidanceLine(width, '/subscription login <provider> start', 'start or repair browser login for the selected provider route', C),
190
+ buildGuidanceLine(width, '/subscription login <provider> start --yes', 'start or repair browser login for the selected provider route', C),
191
191
  ];
192
192
 
193
193
  // Empty state: render posture + base empty state
@@ -205,7 +205,7 @@ export class SubscriptionPanel extends ScrollableListPanel<SubscriptionRow> {
205
205
  intro,
206
206
  sections: [{ lines: [...summaryLines, ...emptyLines] }],
207
207
  footerLines: [
208
- buildGuidanceLine(width, '/subscription login <provider> start', 'start browser-based provider login from the packaged subscription surface', C),
208
+ buildGuidanceLine(width, '/subscription login <provider> start --yes', 'start browser-based provider login from the packaged subscription surface', C),
209
209
  buildPanelLine(width, [[' Up/Down move Enter/X sign out selected provider r refresh', C.dim]]),
210
210
  ],
211
211
  palette: C,
@@ -240,9 +240,9 @@ export class SubscriptionPanel extends ScrollableListPanel<SubscriptionRow> {
240
240
  detailRows.push(buildPanelLine(width, [[` Press Enter or X again to sign out ${selectedRow.provider}.`, C.warn]]));
241
241
  }
242
242
  } else if (selectedRow.pending) {
243
- detailRows.push(buildPanelLine(width, [[' Login is pending. Finish with /subscription login <provider> finish <code>.', C.warn]]));
243
+ detailRows.push(buildPanelLine(width, [[' Login is pending. Finish with /subscription login <provider> finish <code> --yes.', C.warn]]));
244
244
  } else if (selectedRow.hasOAuthConfig) {
245
- detailRows.push(buildPanelLine(width, [[' Ready for login. Start with /subscription login <provider> start.', C.dim]]));
245
+ detailRows.push(buildPanelLine(width, [[' Ready for login. Start with /subscription login <provider> start --yes.', C.dim]]));
246
246
  } else {
247
247
  detailRows.push(buildPanelLine(width, [[' Add a provider-specific OAuth config or enable a built-in subscription provider to use subscription login.', C.bad]]));
248
248
  }
@@ -255,7 +255,7 @@ export class SubscriptionPanel extends ScrollableListPanel<SubscriptionRow> {
255
255
  header: headerLines,
256
256
  footer: [
257
257
  ...detailRows,
258
- buildGuidanceLine(width, '/subscription login <provider> start', 'start browser-based provider login from the packaged subscription surface', C),
258
+ buildGuidanceLine(width, '/subscription login <provider> start --yes', 'start browser-based provider login from the packaged subscription surface', C),
259
259
  buildPanelLine(width, [[' Up/Down move Enter/X sign out selected provider r refresh', C.dim]]),
260
260
  ],
261
261
  });
@@ -12,7 +12,7 @@ const ALL_ACTIONS: BlockAction[] = [
12
12
  { id: 'copy', label: 'Copy', key: 'c' },
13
13
  { id: 'bookmark', label: 'Bookmark', key: 'b' },
14
14
  { id: 'toggle', label: 'Collapse/Expand',key: 'Tab' },
15
- { id: 'apply', label: 'Apply diff', key: 'a' },
15
+ { id: 'apply', label: 'Diff apply blocked', key: 'a' },
16
16
  { id: 'rerun', label: 'Re-run tool', key: 'r' },
17
17
  ];
18
18
 
@@ -215,7 +215,7 @@ export function renderShortcutsOverlay(
215
215
  row(kb('clear-prompt'), 'Clear prompt'),
216
216
  row(kb('delete-word'), 'Delete word backward'),
217
217
  row(kb('kill-line'), 'Kill to end of line'),
218
- row(kb('apply-diff-line-start'), 'Apply diff / line start'),
218
+ row(kb('apply-diff-line-start'), 'Diff apply blocked / line start'),
219
219
  row(kb('next-error-line-end'), 'Next error / line end'),
220
220
  '',
221
221
  ' Actions',
@@ -223,7 +223,7 @@ export function renderShortcutsOverlay(
223
223
  row('Tab', 'Collapse/expand block'),
224
224
  row(kb('bookmark'), 'Bookmark block'),
225
225
  row(kb('block-copy'), 'Copy block to clipboard'),
226
- row(kb('block-save'), 'Save block to file'),
226
+ row(kb('block-save'), 'Block save blocked; use /share --yes'),
227
227
  row(kb('copy-selection'), 'Copy selection'),
228
228
  row('F2', 'Process monitor'),
229
229
  row('?', 'Help overlay'),
@@ -82,16 +82,16 @@ function selectedDetailLines(workspace: McpWorkspace, width: number): WorkspaceR
82
82
  if (workspace.mode === 'form') {
83
83
  const field = workspace.formFields[workspace.formIndex];
84
84
  lines.push(
85
- workspace.editingServerName ? `Editing server: ${workspace.editingServerName}` : 'Adding an MCP server',
86
- 'Write a server through the SDK MCP config manager, then reload the live runtime without restarting the TUI.',
85
+ workspace.editingServerName ? `Previewing server: ${workspace.editingServerName}` : 'Drafting an MCP server command',
86
+ 'Workspace writes/reloads are blocked. Use the generated /mcp add ... --yes command from the prompt.',
87
87
  field ? `${field.label}: ${field.help}` : '',
88
- 'Project scope writes to this workspace. Global scope writes to your user MCP config. External Claude/Desktop config files are shown but not edited here.',
88
+ 'Project/global config locations are shown for review. This workspace does not write or reload MCP config.',
89
89
  );
90
90
  } else if (workspace.mode === 'delete-confirm') {
91
91
  lines.push(
92
- `Remove configured server: ${workspace.editingServerName ?? '(unknown)'}`,
93
- 'This removes the selected writable project/global config entry and reloads MCP runtime state.',
94
- 'Press y to remove, n or Esc to cancel.',
92
+ `Removal blocked for server: ${workspace.editingServerName ?? '(unknown)'}`,
93
+ 'Use /mcp remove <server> --scope <project|global> --yes from the prompt for explicit removal.',
94
+ 'Press n or Esc to return.',
95
95
  );
96
96
  } else {
97
97
  const selected = workspace.selectedRow;
@@ -207,19 +207,19 @@ function buildControlRows(workspace: McpWorkspace, width: number, height: number
207
207
  }
208
208
 
209
209
  function footerText(workspace: McpWorkspace): string {
210
- if (workspace.mode === 'form') return 'Focus server form · Up/Down field · Left/Right cycle · Type edit · Enter save/cancel row · Esc back';
211
- if (workspace.mode === 'delete-confirm') return 'Focus remove confirmation · y confirm · n/Esc cancel';
212
- return 'Focus MCP workspace · Up/Down choose · Enter edit/action · a add · d remove · r reload · t tools · Esc close';
210
+ if (workspace.mode === 'form') return 'Focus server command preview · Up/Down field · Left/Right cycle · Type edit · Enter show command · Esc back';
211
+ if (workspace.mode === 'delete-confirm') return 'Focus remove guidance · n/Esc cancel';
212
+ return 'Focus MCP workspace · Up/Down choose · Enter view/action · a draft · d removal command · r reload command · t tools · Esc close';
213
213
  }
214
214
 
215
215
  export function renderMcpWorkspace(workspace: McpWorkspace, width: number, height: number): Line[] {
216
216
  const metrics = getFullscreenWorkspaceMetrics({ width, height });
217
217
  const connected = workspace.servers.filter((server) => server.connected).length;
218
- const stateLabel = workspace.mode === 'browse' ? 'Browse' : workspace.mode === 'form' ? 'Edit Server' : 'Confirm Remove';
218
+ const stateLabel = workspace.mode === 'browse' ? 'Browse' : workspace.mode === 'form' ? 'Command Preview' : 'Remove Guidance';
219
219
  const mainHeader = workspace.mode === 'form'
220
- ? 'MCP server form'
220
+ ? 'MCP server command preview'
221
221
  : workspace.mode === 'delete-confirm'
222
- ? 'Remove MCP server'
222
+ ? 'MCP remove command'
223
223
  : `Servers ${connected}/${workspace.servers.length} connected · Tools ${workspace.tools.length}`;
224
224
 
225
225
  return renderFullscreenWorkspace({
@@ -45,6 +45,12 @@ export interface ProcessModalDeps {
45
45
  readonly agentManager: Pick<AgentManager, 'list' | 'getStatus'>;
46
46
  readonly processManager: Pick<ProcessManager, 'list' | 'getStatus' | 'stop'>;
47
47
  readonly wrfcController: Pick<WrfcController, 'getChain'> & Partial<Pick<WrfcController, 'listChains'>>;
48
+ /**
49
+ * GoodVibes Agent must not present local AgentManager records as an owned
50
+ * execution lane. Tests for copied TUI primitives can opt into read-only
51
+ * display, but product runtime should keep this hidden.
52
+ */
53
+ readonly agentEntries: 'hidden' | 'read-only';
48
54
  }
49
55
 
50
56
  type WrfcChainLike = {
@@ -504,14 +510,17 @@ export class ProcessModal {
504
510
  const now = Date.now();
505
511
  const result: ProcessEntry[] = [];
506
512
 
507
- // Agents only show active (pending/running), grouped by stable parent/child hierarchy.
508
- result.push(...buildAgentEntries(
509
- manager.list(),
510
- this.deps,
511
- now,
512
- (key) => this.groupOrder.get(key),
513
- (key) => this.ensureGroupOrder(key),
514
- ));
513
+ // Local AgentManager activity is hidden in the Agent product. Build/fix/review
514
+ // execution belongs to explicit GoodVibes TUI delegation, not a local lane.
515
+ if (this.deps.agentEntries === 'read-only') {
516
+ result.push(...buildAgentEntries(
517
+ manager.list(),
518
+ this.deps,
519
+ now,
520
+ (key) => this.groupOrder.get(key),
521
+ (key) => this.ensureGroupOrder(key),
522
+ ));
523
+ }
515
524
 
516
525
  // Background exec processes — only show running
517
526
  const pm = this.deps.processManager;
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Shows a list of saved profiles with:
6
6
  * - name, timestamp (formatted), settings preview
7
- * Footer hints: [Up/Down] Navigate [Enter] Load [d] Arm/Delete [s] Save current [Esc] Close
7
+ * Footer hints: [Up/Down] Navigate [Enter] Load [Esc] Close
8
8
  */
9
9
 
10
10
  import type { Line } from '../types/grid.ts';
@@ -51,7 +51,7 @@ export function renderProfilePickerModal(
51
51
  });
52
52
  sections.push({
53
53
  type: 'text',
54
- content: 'Press [s] to save the current settings as a profile.',
54
+ content: 'Use /profiles save <name> --yes to save the current settings as a profile.',
55
55
  style: { fg: '240', dim: true },
56
56
  });
57
57
  } else {
@@ -107,14 +107,6 @@ export function renderProfilePickerModal(
107
107
  style: { fg: '#00ffcc' },
108
108
  });
109
109
  }
110
- if (modal.deleteConfirmationTarget) {
111
- sections.push({
112
- type: 'text',
113
- content: `Press [d] again to permanently delete ${modal.deleteConfirmationTarget}.`,
114
- style: { fg: '#f59e0b', dim: true },
115
- });
116
- }
117
-
118
110
  return ModalFactory.createModal(
119
111
  {
120
112
  title: 'Profiles',
@@ -122,7 +114,7 @@ export function renderProfilePickerModal(
122
114
  margin: boxMargin,
123
115
  targetContentRows,
124
116
  sections,
125
- hints: ['[Up/Down] Navigate', '[Enter] Load', '[d] Arm/Delete', '[s] Save current', '[Esc] Close'],
117
+ hints: ['[Up/Down] Navigate', '[Enter] Load', '/profiles save|delete --yes', '[Esc] Close'],
126
118
  },
127
119
  width,
128
120
  );
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Shows a list of saved sessions with:
6
6
  * - name, timestamp (formatted), message count
7
- * Footer hints: [Enter] Load [d] Delete [Esc] Close
7
+ * Footer hints: [Enter] Load [Esc] Close
8
8
  */
9
9
 
10
10
  import type { Line } from '../types/grid.ts';
@@ -105,14 +105,6 @@ export function renderSessionPickerModal(
105
105
  });
106
106
  }
107
107
 
108
- if (modal.deleteConfirmationTarget) {
109
- sections.push({
110
- type: 'text',
111
- content: `Deletion is armed for ${modal.deleteConfirmationTarget}. Move selection or press Esc to cancel.`,
112
- style: { fg: '244', dim: true },
113
- });
114
- }
115
-
116
108
  return ModalFactory.createModal(
117
109
  {
118
110
  title: 'Sessions',
@@ -120,7 +112,7 @@ export function renderSessionPickerModal(
120
112
  margin: boxMargin,
121
113
  targetContentRows,
122
114
  sections,
123
- hints: ['[\u2191\u2193] Navigate', '[Enter] Load', '[d] Arm / Delete', '[Esc] Close'],
115
+ hints: ['[\u2191\u2193] Navigate', '[Enter] Load', 'Delete: /session delete <id> --yes', '[Esc] Close'],
124
116
  },
125
117
  width,
126
118
  );
@@ -7,7 +7,7 @@
7
7
 
8
8
  import type { Line } from '../types/grid.ts';
9
9
  import type { SettingsModal, SettingEntry, FlagEntry, McpEntry, SubscriptionEntry, SettingsCategory } from '../input/settings-modal.ts';
10
- import { SETTINGS_CATEGORIES, SETTINGS_CATEGORY_GROUPS } from '../input/settings-modal.ts';
10
+ import { isExternalDaemonOwnedSettingKey, SETTINGS_CATEGORIES, SETTINGS_CATEGORY_GROUPS } from '../input/settings-modal.ts';
11
11
  import { getDisplayWidth, wrapText } from '../utils/terminal-width.ts';
12
12
  import { CATEGORY_LABELS, describeUiRouting, formatValue, getSettingLabel, inferSubscriptionRouteReason, valueColor } from './settings-modal-helpers.ts';
13
13
  import { isSecretConfigKey } from '../config/secret-config.ts';
@@ -34,10 +34,10 @@ const CATEGORY_INFO: Record<SettingsCategory, string> = {
34
34
  wrfc: 'WRFC is external to normal Agent operation. Review these copied compatibility values only for explicit GoodVibes TUI build delegation.',
35
35
  helper: 'Helper model defaults used by helper subsystems when they do not use the main chat route.',
36
36
  tts: 'Text-to-speech provider, voice, and optional spoken-turn LLM overrides.',
37
- service: 'Background service posture: enabled state, autostart, restart behavior, service name, platform, and logs.',
38
- controlPlane: 'Daemon control-plane settings for local admin/API access.',
39
- httpListener: 'HTTP listener settings for webhook and integration ingress.',
40
- web: 'Browser surface settings for the local or network web UI.',
37
+ service: 'External daemon service posture. Agent shows these copied compatibility keys for inspection only and does not install, start, stop, restart, or autostart services.',
38
+ controlPlane: 'External daemon control-plane settings for local admin/API access. Agent connects to this daemon and does not mutate its bind posture.',
39
+ httpListener: 'External HTTP listener settings for webhook and integration ingress. Agent does not start or expose the listener.',
40
+ web: 'External browser surface settings. Agent does not own the web listener or network bind lifecycle.',
41
41
  batch: 'Batch execution settings, including local vs Cloudflare queue behavior.',
42
42
  automation: 'Scheduled and automated run settings, concurrency, timeout, catch-up, cooldown, and retention behavior.',
43
43
  watchers: 'File/process watcher heartbeat, polling, and recovery-window behavior.',
@@ -49,10 +49,10 @@ const CATEGORY_INFO: Record<SettingsCategory, string> = {
49
49
  surfaces: 'External app surfaces such as Slack, Discord, ntfy, Home Assistant, Telegram, webhooks, chat bridges, and messaging providers.',
50
50
  cloudflare: 'Optional Cloudflare control plane, batch queue, Worker, Tunnel, Access, DNS, KV, Durable Objects, Secrets Store, and R2 settings.',
51
51
  release: 'Release-channel preference.',
52
- danger: 'High-impact switches for daemon and HTTP listener behavior. These are operational overrides, not normal preferences.',
52
+ danger: 'High-impact daemon and listener switches. Agent renders daemon-owned switches read-only; use GoodVibes TUI or the daemon host to change them.',
53
53
  tools: 'Tool LLM and helper model routing. Empty provider/model values inherit the active chat route unless a specific helper/tool route is set.',
54
54
  flags: 'Feature flags are SDK runtime gates. They are separate from normal config keys because they enable or disable staged runtime behavior.',
55
- network: 'Combined network view for daemon control-plane, HTTP listener, browser web surface, and general outbound network settings.',
55
+ network: 'Read-only view of external daemon control-plane, HTTP listener, and browser web bind posture plus editable non-daemon network settings.',
56
56
  };
57
57
 
58
58
  const ENUM_VALUE_DESCRIPTIONS: Record<string, Record<string, string>> = {
@@ -479,6 +479,10 @@ function footerText(modal: SettingsModal): string {
479
479
  if (modal.currentCategory === 'subscriptions') return 'Focus settings · Up/Down provider · Left categories · Tab pane · Enter review/sign out · Esc close';
480
480
  if (modal.currentCategory === 'mcp') return 'Focus settings · Up/Down server · Left categories · Tab pane · Enter edit trust · Esc close';
481
481
  if (modal.currentCategory === 'flags') return 'Focus feature flags · Up/Down flag · Left categories · Tab pane · Enter/Space toggle · Esc close';
482
+ const selected = modal.getSelected();
483
+ if (selected && isExternalDaemonOwnedSettingKey(selected.setting.key)) {
484
+ return 'Read-only external daemon setting · Change from GoodVibes TUI or daemon host · Esc close';
485
+ }
482
486
  return 'Focus settings · Up/Down setting · Left categories · Tab pane · Enter/Space edit/toggle · R reset · Esc close';
483
487
  }
484
488
 
@@ -488,7 +492,7 @@ export function renderSettingsModal(
488
492
  viewportHeight = 24,
489
493
  ): Line[] {
490
494
  const notices = [
491
- ...(modal.lastSaveTriggeredRestart ? [`Restarting ${modal.lastSaveTriggeredRestart}`] : []),
495
+ ...(modal.lastSaveTriggeredRestart ? [`External daemon owner must restart ${modal.lastSaveTriggeredRestart}`] : []),
492
496
  ...(modal.lastSettingEffectMessage ? [modal.lastSettingEffectMessage] : []),
493
497
  ];
494
498
  const metrics = getFullscreenWorkspaceMetrics({ width, height: viewportHeight });
@@ -2,7 +2,6 @@ import { type Line, type Cell, createEmptyLine, createEmptyCell } from '../types
2
2
  import { LAYOUT } from './layout.ts';
3
3
  import { VERSION } from '../version.ts';
4
4
  import { fitDisplay, getDisplayWidth, truncateDisplay, wrapText, interpolateColor } from '../utils/terminal-width.ts';
5
- import type { GitHeaderInfo } from './git-status.ts';
6
5
  import { renderConversationFragment, renderConversationStatusLine, type ConversationStatusSegment } from './conversation-surface.ts';
7
6
  import { GLYPHS } from './ui-primitives.ts';
8
7
 
@@ -11,22 +10,6 @@ const GRADIENT_CYCLE_FRAMES = 50;
11
10
  /** Number of frames before rotating to the next thinking phrase (~30 seconds at 80ms/frame). */
12
11
  const PHRASE_ROTATION_FRAMES = 375;
13
12
 
14
- /** Build the git segment string and its display width. Single source of truth for header layout. */
15
- function buildGitSegment(gitInfo: GitHeaderInfo): { text: string; width: number } {
16
- const branch = ` git:${gitInfo.branch}`;
17
- if (gitInfo.dirty) {
18
- const text = `${branch} * `;
19
- return { text, width: getDisplayWidth(text) };
20
- }
21
- if (gitInfo.ahead > 0 || gitInfo.behind > 0) {
22
- const arrows = (gitInfo.ahead > 0 ? ` +${gitInfo.ahead}` : '') + (gitInfo.behind > 0 ? ` -${gitInfo.behind}` : '');
23
- const text = `${branch}${arrows} `;
24
- return { text, width: getDisplayWidth(text) };
25
- }
26
- const text = `${branch} `;
27
- return { text, width: getDisplayWidth(text) };
28
- }
29
-
30
13
  /** Format a number: up to 999, then 1.0k, 1.0M, 1.0B, 1.0T */
31
14
  function fmtNum(n: number): string {
32
15
  if (n < 1000) return String(n);
@@ -40,7 +23,7 @@ function fmtNum(n: number): string {
40
23
  * UIFactory - Generates standard UI fragments without needing Ink/React overhead.
41
24
  */
42
25
  export class UIFactory {
43
- public static createHeader(width: number, model: string, provider: string, title?: string, gitInfo?: GitHeaderInfo): Line[] {
26
+ public static createHeader(width: number, model: string, provider: string, title?: string): Line[] {
44
27
  const lines: Line[] = [];
45
28
  const CYAN = '#00ffff';
46
29
  const GREY = '244';
@@ -56,9 +39,8 @@ export class UIFactory {
56
39
  // Optional conversation title — shown after brand/ver, truncated to fit
57
40
  if (title) {
58
41
  const titleStr = `│ ${title} `;
59
- // Reserve space for git info (if present) + model/provider on the right
60
- const gitReserved = gitInfo ? buildGitSegment(gitInfo).width : 0;
61
- const rightReserved = getDisplayWidth(stats + prov) + gitReserved;
42
+ // Reserve space for model/provider on the right.
43
+ const rightReserved = getDisplayWidth(stats + prov);
62
44
  const maxTitleW = width - curX - rightReserved - 1;
63
45
  let displayTitle: string;
64
46
  if (getDisplayWidth(titleStr) <= maxTitleW) {
@@ -76,19 +58,9 @@ export class UIFactory {
76
58
  }
77
59
  for (const char of displayTitle) { if (curX < width) line[curX++] = { char, fg: TITLE_COLOR, bg: '', bold: false, dim: true, underline: false, italic: false, strikethrough: false }; }
78
60
  }
79
- // Build git info segment
80
- let gitStr = '';
81
- let gitFg = '238';
82
- if (gitInfo) {
83
- gitStr = buildGitSegment(gitInfo).text;
84
- if (gitInfo.dirty || gitInfo.ahead > 0 || gitInfo.behind > 0) {
85
- gitFg = '220'; // yellow when dirty or out-of-sync
86
- }
87
- }
88
61
  const rightSideText = stats + prov;
89
- const rightSideW = getDisplayWidth(rightSideText) + getDisplayWidth(gitStr);
62
+ const rightSideW = getDisplayWidth(rightSideText);
90
63
  let rightX = width - rightSideW;
91
- for (const char of gitStr) { if (rightX >= 0 && rightX < width) line[rightX++] = { char, fg: gitFg, bg: '', bold: false, dim: !gitInfo?.dirty && !(gitInfo?.ahead || gitInfo?.behind), underline: false, italic: false, strikethrough: false }; }
92
64
  for (const char of stats) { if (rightX < width) line[rightX++] = { char, fg: CYAN, bg: '', bold: true, dim: false, underline: false, italic: false, strikethrough: false }; }
93
65
  for (const char of prov) { if (rightX < width) line[rightX++] = { char, fg: GREY, bg: '', bold: false, dim: true, underline: false, italic: false, strikethrough: false }; }
94
66
  lines.push(line);
@@ -11,8 +11,6 @@ import type { OpsControlPlane } from '@/runtime/index.ts';
11
11
  import { CommandRegistry } from '../input/command-registry.ts';
12
12
  import { registerBuiltinCommands } from '../input/commands.ts';
13
13
  import { InputHistory } from '../input/input-history.ts';
14
- import { GitStatusProvider } from '../renderer/git-status.ts';
15
- import type { GitHeaderInfo } from '../renderer/git-status.ts';
16
14
  import type { PermissionRequestHandler } from '@pellux/goodvibes-sdk/platform/permissions';
17
15
  import { registerBuiltinPanels } from '../panels/builtin-panels.ts';
18
16
  import { SystemMessagesPanel } from '../panels/system-messages-panel.ts';
@@ -38,8 +36,6 @@ import { createKnowledgeApi } from '@pellux/goodvibes-sdk/platform/knowledge';
38
36
  export interface BootstrapShellState {
39
37
  readonly commandRegistry: CommandRegistry;
40
38
  readonly commandContext: CommandContext;
41
- readonly gitStatusProvider: GitStatusProvider;
42
- readonly lastGitInfoRef: { value: GitHeaderInfo | undefined };
43
39
  readonly inputHistory: InputHistory;
44
40
  readonly systemMessageRouter: SystemMessageRouter;
45
41
  }
@@ -260,13 +256,6 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
260
256
  });
261
257
  commandContextRef = commandContext;
262
258
 
263
- const gitStatusProvider = new GitStatusProvider(services.workingDirectory);
264
- const lastGitInfoRef = { value: undefined as GitHeaderInfo | undefined };
265
- gitStatusProvider.getStatus().then((info) => {
266
- lastGitInfoRef.value = info;
267
- requestRender();
268
- }).catch(() => { /* non-fatal */ });
269
-
270
259
  const saveHistory = configManager.get('behavior.saveHistory') as boolean;
271
260
  const inputHistory = new InputHistory({
272
261
  historyPath: services.shellPaths.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, 'input-history.json'),
@@ -276,8 +265,6 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
276
265
  return {
277
266
  commandRegistry,
278
267
  commandContext,
279
- gitStatusProvider,
280
- lastGitInfoRef,
281
268
  inputHistory,
282
269
  systemMessageRouter,
283
270
  };
@@ -16,8 +16,6 @@ import { logger } from '@pellux/goodvibes-sdk/platform/utils';
16
16
  import type { PermissionRequestHandler } from '@pellux/goodvibes-sdk/platform/permissions';
17
17
  import type { CommandContext } from '../input/command-registry.ts';
18
18
  import type { InputHistory } from '../input/input-history.ts';
19
- import type { GitStatusProvider } from '../renderer/git-status.ts';
20
- import type { GitHeaderInfo } from '../renderer/git-status.ts';
21
19
  import type { SelectionManager } from '../input/selection.ts';
22
20
  import type { Compositor } from '../renderer/compositor.ts';
23
21
 
@@ -81,10 +79,6 @@ export type BootstrapContext = RuntimeContext & {
81
79
  uiServices: UiRuntimeServices;
82
80
  /** Persists and navigates input history across sessions. */
83
81
  inputHistory: InputHistory;
84
- /** Provides git branch/dirty state for the header. */
85
- gitStatusProvider: GitStatusProvider;
86
- /** Mutable ref so async git refreshes propagate without closure capture issues. */
87
- lastGitInfoRef: { value: GitHeaderInfo | undefined };
88
82
  /** Unsubscribe functions owned by bootstrap (cleared on shutdown). */
89
83
  bootstrapUnsubs: Array<() => void>;
90
84
  /** Ref holding the periodic agent-status interval (use ref — not local var — to keep shutdown in sync). */
@@ -291,9 +285,7 @@ export async function bootstrapRuntime(
291
285
  systemMessageRouterRef.value = systemMessageRouter;
292
286
  const commandRegistry = shell.commandRegistry;
293
287
  const commandContext = shell.commandContext;
294
- const gitStatusProvider = shell.gitStatusProvider;
295
288
  const inputHistory = shell.inputHistory;
296
- const lastGitInfoRef = shell.lastGitInfoRef;
297
289
  const pluginCommandRegistry = {
298
290
  register(command: {
299
291
  readonly name: string;
@@ -507,8 +499,6 @@ export async function bootstrapRuntime(
507
499
  commandContext,
508
500
  uiServices,
509
501
  inputHistory,
510
- gitStatusProvider,
511
- lastGitInfoRef,
512
502
  bootstrapUnsubs,
513
503
  agentStatusIntervalRef,
514
504
  orchestratorRefs,
@@ -267,7 +267,7 @@ function describeLocalTuiOnly(snapshot: OnboardingSnapshotState): string {
267
267
  return 'Use GoodVibes Agent in this terminal while connecting only to an externally managed daemon. Agent does not enable service mode, HTTP listeners, external app surfaces, or network setup.';
268
268
  }
269
269
 
270
- return 'Keep Agent local-only by not enabling browser access, background services, HTTP listeners, external app surfaces, or network setup.';
270
+ return 'Keep Agent local-only by not requesting browser access, service posture changes, HTTP listeners, external app surfaces, or network setup from the daemon owner.';
271
271
  }
272
272
 
273
273
  function describeBrowserAccess(snapshot: OnboardingSnapshotState): string {
@@ -278,14 +278,14 @@ function describeBrowserAccess(snapshot: OnboardingSnapshotState): string {
278
278
 
279
279
  function describeRemoteDeviceAccess(snapshot: OnboardingSnapshotState): string {
280
280
  return hasRemoteDeviceAccess(snapshot)
281
- ? 'Keep enabled GoodVibes services reachable from other devices on your LAN. Local authentication is required.'
282
- : 'Make enabled GoodVibes services reachable from other devices on your LAN. Local authentication is required.';
281
+ ? 'Review external daemon surfaces reachable from other devices on your LAN. Local authentication is required.'
282
+ : 'Review the external daemon surfaces required for other-device LAN access. Local authentication is required.';
283
283
  }
284
284
 
285
285
  function describeWebhookIngress(snapshot: OnboardingSnapshotState): string {
286
286
  return hasWebhookOrEventIngress(snapshot)
287
- ? 'Keep the HTTP listener available for incoming webhooks, callbacks, and automation events.'
288
- : 'Turn on the HTTP listener for incoming webhooks, callbacks, and automation events.';
287
+ ? 'Review the external HTTP listener used for incoming webhooks, callbacks, and automation events.'
288
+ : 'Review the external HTTP listener required for incoming webhooks, callbacks, and automation events.';
289
289
  }
290
290
 
291
291
  function describeExternalIntegrations(snapshot: OnboardingSnapshotState): string {
@@ -306,7 +306,7 @@ function describeCloudflareBatch(snapshot: OnboardingSnapshotState): string {
306
306
  return 'Review Cloudflare Workers/Queues batch processing, token storage, and optional remote daemon provisioning settings.';
307
307
  }
308
308
 
309
- return 'Optionally configure Cloudflare Workers and Queues for explicit or eligible background batch jobs. Immediate local daemon behavior stays the default unless enabled.';
309
+ return 'Optionally configure Cloudflare Workers and Queues for explicit or eligible background batch jobs. The external daemon still owns execution.';
310
310
  }
311
311
 
312
312
  function getAcknowledgementAccepted(