@pellux/goodvibes-agent 0.1.117 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/CHANGELOG.md +13 -4
  2. package/README.md +35 -14
  3. package/bin/goodvibes-agent.ts +16 -2
  4. package/dist/package/main.js +176073 -170980
  5. package/docs/README.md +11 -5
  6. package/docs/channels-remote-and-api.md +50 -0
  7. package/docs/connected-host.md +3 -3
  8. package/docs/getting-started.md +29 -15
  9. package/docs/knowledge-artifacts-and-multimodal.md +91 -0
  10. package/docs/project-planning.md +79 -0
  11. package/docs/providers-and-routing.md +46 -0
  12. package/docs/release-and-publishing.md +44 -9
  13. package/docs/tools-and-commands.md +123 -0
  14. package/docs/voice-and-live-tts.md +51 -0
  15. package/package.json +2 -5
  16. package/src/agent/channel-delivery.ts +201 -0
  17. package/src/agent/media-generation.ts +159 -0
  18. package/src/agent/memory-prompt.ts +0 -1
  19. package/src/agent/note-registry.ts +329 -0
  20. package/src/agent/operator-actions.ts +343 -0
  21. package/src/agent/persona-registry.ts +15 -14
  22. package/src/agent/record-labels.ts +107 -0
  23. package/src/agent/reminder-schedule-format.ts +33 -24
  24. package/src/agent/reminder-schedule.ts +26 -25
  25. package/src/agent/routine-registry.ts +13 -12
  26. package/src/agent/routine-schedule-args.ts +2 -1
  27. package/src/agent/routine-schedule-format.ts +77 -53
  28. package/src/agent/routine-schedule-promotion.ts +34 -32
  29. package/src/agent/routine-schedule-receipts.ts +28 -26
  30. package/src/agent/runtime-profile-starters.ts +2 -2
  31. package/src/agent/runtime-profile.ts +18 -17
  32. package/src/agent/skill-registry.ts +25 -24
  33. package/src/cli/agent-knowledge-args.ts +5 -1
  34. package/src/cli/agent-knowledge-command.ts +39 -33
  35. package/src/cli/agent-knowledge-format.ts +80 -67
  36. package/src/cli/agent-knowledge-methods.ts +1 -1
  37. package/src/cli/agent-knowledge-runtime.ts +32 -26
  38. package/src/cli/bundle-command.ts +13 -8
  39. package/src/cli/config-overrides.ts +37 -36
  40. package/src/cli/external-runtime.ts +10 -4
  41. package/src/cli/help.ts +29 -11
  42. package/src/cli/local-library-command.ts +134 -68
  43. package/src/cli/management-commands.ts +98 -62
  44. package/src/cli/management.ts +52 -26
  45. package/src/cli/memory-command.ts +66 -32
  46. package/src/cli/parser.ts +37 -24
  47. package/src/cli/profiles-command.ts +52 -35
  48. package/src/cli/provider-auth-routes.ts +2 -1
  49. package/src/cli/routines-command.ts +44 -36
  50. package/src/cli/service-posture.ts +6 -6
  51. package/src/cli/status.ts +46 -121
  52. package/src/core/conversation-message-snapshot.ts +131 -0
  53. package/src/input/agent-workspace-activation.ts +33 -7
  54. package/src/input/agent-workspace-basic-command-editor-submission.ts +7 -3
  55. package/src/input/agent-workspace-basic-command-editors.ts +30 -10
  56. package/src/input/agent-workspace-categories.ts +276 -64
  57. package/src/input/agent-workspace-channel-command-editor-submission.ts +34 -0
  58. package/src/input/agent-workspace-channel-command-editors.ts +23 -5
  59. package/src/input/agent-workspace-channels.ts +35 -2
  60. package/src/input/agent-workspace-command-editor.ts +18 -2
  61. package/src/input/agent-workspace-config-reader.ts +16 -0
  62. package/src/input/agent-workspace-delegation-editor-submission.ts +1 -1
  63. package/src/input/agent-workspace-editors.ts +140 -2
  64. package/src/input/agent-workspace-knowledge-query-editor.ts +1 -1
  65. package/src/input/agent-workspace-learned-behavior.ts +2 -2
  66. package/src/input/agent-workspace-library-command-editor-submission.ts +1 -1
  67. package/src/input/agent-workspace-library-command-editors.ts +2 -2
  68. package/src/input/agent-workspace-local-operations.ts +218 -0
  69. package/src/input/agent-workspace-local-selection.ts +75 -0
  70. package/src/input/agent-workspace-media-command-editor-submission.ts +62 -0
  71. package/src/input/agent-workspace-media-command-editors.ts +27 -0
  72. package/src/input/agent-workspace-memory-command-editors.ts +1 -1
  73. package/src/input/agent-workspace-memory-editor.ts +2 -2
  74. package/src/input/agent-workspace-navigation.ts +38 -2
  75. package/src/input/agent-workspace-notify-editor-submission.ts +16 -2
  76. package/src/input/agent-workspace-operations-command-editor-submission.ts +63 -1
  77. package/src/input/agent-workspace-operations-command-editors.ts +80 -3
  78. package/src/input/agent-workspace-panel-route.ts +43 -0
  79. package/src/input/agent-workspace-reminder-schedule-editor.ts +1 -1
  80. package/src/input/agent-workspace-routine-schedule-editor.ts +2 -2
  81. package/src/input/agent-workspace-search.ts +169 -0
  82. package/src/input/agent-workspace-setup.ts +22 -11
  83. package/src/input/agent-workspace-skill-bundle-command-editor-submission.ts +6 -6
  84. package/src/input/agent-workspace-skill-bundle-command-editors.ts +10 -10
  85. package/src/input/agent-workspace-snapshot.ts +112 -13
  86. package/src/input/agent-workspace-task-command-editors.ts +4 -4
  87. package/src/input/agent-workspace-token.ts +18 -2
  88. package/src/input/agent-workspace-types.ts +92 -4
  89. package/src/input/agent-workspace-voice-media.ts +3 -6
  90. package/src/input/agent-workspace-web-research-editor.ts +104 -0
  91. package/src/input/agent-workspace.ts +136 -208
  92. package/src/input/command-registry.ts +4 -1
  93. package/src/input/commands/agent-runtime-profile-runtime.ts +45 -41
  94. package/src/input/commands/agent-skills-runtime.ts +83 -70
  95. package/src/input/commands/agent-workspace-runtime.ts +23 -7
  96. package/src/input/commands/brief-runtime.ts +25 -24
  97. package/src/input/commands/channels-runtime.ts +145 -31
  98. package/src/input/commands/delegation-runtime.ts +29 -23
  99. package/src/input/commands/experience-runtime.ts +6 -4
  100. package/src/input/commands/guidance-runtime.ts +4 -4
  101. package/src/input/commands/health-runtime.ts +140 -115
  102. package/src/input/commands/knowledge.ts +57 -56
  103. package/src/input/commands/local-provider-runtime.ts +36 -18
  104. package/src/input/commands/local-runtime.ts +138 -16
  105. package/src/input/commands/local-setup-review.ts +7 -7
  106. package/src/input/commands/mcp-runtime.ts +56 -35
  107. package/src/input/commands/notify-runtime.ts +38 -9
  108. package/src/input/commands/operator-actions-runtime.ts +138 -0
  109. package/src/input/commands/operator-runtime.ts +6 -17
  110. package/src/input/commands/personas-runtime.ts +45 -30
  111. package/src/input/commands/planning-runtime.ts +1 -1
  112. package/src/input/commands/platform-access-runtime.ts +29 -29
  113. package/src/input/commands/provider-accounts-runtime.ts +60 -39
  114. package/src/input/commands/qrcode-runtime.ts +45 -6
  115. package/src/input/commands/recall-bundle.ts +11 -11
  116. package/src/input/commands/recall-capture.ts +13 -11
  117. package/src/input/commands/recall-query.ts +29 -21
  118. package/src/input/commands/recall-review.ts +2 -1
  119. package/src/input/commands/routines-runtime.ts +59 -43
  120. package/src/input/commands/schedule-runtime.ts +33 -20
  121. package/src/input/commands/security-runtime.ts +4 -4
  122. package/src/input/commands/session-content.ts +80 -78
  123. package/src/input/commands/session-workflow.ts +132 -93
  124. package/src/input/commands/session.ts +3 -174
  125. package/src/input/commands/shell-core.ts +32 -17
  126. package/src/input/commands/subscription-runtime.ts +53 -179
  127. package/src/input/commands/tasks-runtime.ts +20 -20
  128. package/src/input/commands/work-plan-runtime.ts +33 -8
  129. package/src/input/commands.ts +2 -2
  130. package/src/input/feed-context-factory.ts +2 -1
  131. package/src/input/file-picker.ts +3 -2
  132. package/src/input/handler-command-route.ts +4 -7
  133. package/src/input/handler-content-actions.ts +89 -1
  134. package/src/input/handler-feed-routes.ts +19 -12
  135. package/src/input/handler-feed.ts +6 -3
  136. package/src/input/handler-interactions.ts +7 -5
  137. package/src/input/handler-modal-stack.ts +3 -3
  138. package/src/input/handler-onboarding.ts +24 -80
  139. package/src/input/handler-shortcuts.ts +15 -4
  140. package/src/input/handler.ts +47 -17
  141. package/src/input/input-history.ts +5 -6
  142. package/src/input/keybindings.ts +22 -11
  143. package/src/input/onboarding/onboarding-wizard-apply.ts +13 -0
  144. package/src/input/onboarding/onboarding-wizard-constants.ts +1 -0
  145. package/src/input/onboarding/onboarding-wizard-operator-steps.ts +147 -57
  146. package/src/input/onboarding/onboarding-wizard-steps.ts +46 -29
  147. package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
  148. package/src/input/onboarding/onboarding-wizard.ts +68 -0
  149. package/src/input/profile-picker-modal.ts +31 -12
  150. package/src/input/session-picker-modal.ts +21 -4
  151. package/src/input/settings-modal-behavior.ts +0 -3
  152. package/src/input/settings-modal-subscriptions.ts +3 -3
  153. package/src/input/settings-modal-types.ts +2 -13
  154. package/src/input/settings-modal.ts +6 -52
  155. package/src/input/submission-intent.ts +0 -1
  156. package/src/input/submission-router.ts +3 -3
  157. package/src/main.ts +18 -8
  158. package/src/panels/approval-panel.ts +8 -8
  159. package/src/panels/automation-control-panel.ts +2 -2
  160. package/src/panels/base-panel.ts +1 -1
  161. package/src/panels/builtin/agent.ts +1 -1
  162. package/src/panels/builtin/operations.ts +1 -10
  163. package/src/panels/builtin/session.ts +9 -9
  164. package/src/panels/builtin/shared.ts +2 -2
  165. package/src/panels/cost-tracker-panel.ts +1 -1
  166. package/src/panels/docs-panel.ts +5 -3
  167. package/src/panels/index.ts +0 -1
  168. package/src/panels/knowledge-panel.ts +6 -5
  169. package/src/panels/memory-panel.ts +7 -6
  170. package/src/panels/panel-list-panel.ts +36 -80
  171. package/src/panels/project-planning-panel.ts +18 -11
  172. package/src/panels/provider-account-snapshot.ts +51 -25
  173. package/src/panels/provider-accounts-panel.ts +33 -18
  174. package/src/panels/provider-health-domains.ts +45 -4
  175. package/src/panels/provider-health-panel.ts +5 -4
  176. package/src/panels/qr-panel.ts +1 -1
  177. package/src/panels/schedule-panel.ts +9 -17
  178. package/src/panels/security-panel.ts +8 -8
  179. package/src/panels/session-browser-panel.ts +10 -10
  180. package/src/panels/subscription-panel.ts +3 -3
  181. package/src/panels/system-messages-panel.ts +1 -1
  182. package/src/panels/tasks-panel.ts +20 -13
  183. package/src/panels/tool-inspector-panel.ts +19 -12
  184. package/src/panels/work-plan-panel.ts +5 -5
  185. package/src/planning/project-planning-coordinator.ts +1 -1
  186. package/src/provider-auth-route-display.ts +9 -0
  187. package/src/renderer/agent-workspace-style.ts +34 -0
  188. package/src/renderer/agent-workspace.ts +228 -52
  189. package/src/renderer/autocomplete-overlay.ts +25 -6
  190. package/src/renderer/bookmark-modal.ts +19 -4
  191. package/src/renderer/buffer.ts +4 -2
  192. package/src/renderer/context-inspector.ts +50 -13
  193. package/src/renderer/diff.ts +1 -1
  194. package/src/renderer/file-picker-overlay.ts +19 -6
  195. package/src/renderer/help-overlay.ts +106 -33
  196. package/src/renderer/history-search-overlay.ts +19 -4
  197. package/src/renderer/live-tail-modal.ts +27 -5
  198. package/src/renderer/mcp-workspace.ts +164 -50
  199. package/src/renderer/model-picker-overlay.ts +58 -2
  200. package/src/renderer/model-workspace.ts +104 -20
  201. package/src/renderer/process-modal.ts +27 -6
  202. package/src/renderer/profile-picker-modal.ts +20 -4
  203. package/src/renderer/search-overlay.ts +25 -5
  204. package/src/renderer/selection-modal-overlay.ts +46 -14
  205. package/src/renderer/session-picker-modal.ts +18 -1
  206. package/src/renderer/settings-modal-helpers.ts +2 -40
  207. package/src/renderer/settings-modal.ts +83 -50
  208. package/src/renderer/tool-call.ts +20 -11
  209. package/src/runtime/agent-runtime-events.ts +6 -6
  210. package/src/runtime/bootstrap-command-context.ts +7 -1
  211. package/src/runtime/bootstrap-command-parts.ts +9 -7
  212. package/src/runtime/bootstrap-core.ts +19 -1
  213. package/src/runtime/bootstrap-hook-bridge.ts +7 -18
  214. package/src/runtime/bootstrap-shell.ts +4 -4
  215. package/src/runtime/bootstrap.ts +31 -22
  216. package/src/runtime/connected-host-auth.ts +7 -2
  217. package/src/runtime/diagnostics/panels/index.ts +2 -2
  218. package/src/runtime/diagnostics/panels/policy.ts +7 -7
  219. package/src/runtime/index.ts +2 -1
  220. package/src/runtime/onboarding/apply-file-helpers.ts +66 -0
  221. package/src/runtime/onboarding/apply.ts +80 -79
  222. package/src/runtime/onboarding/derivation.ts +5 -8
  223. package/src/runtime/onboarding/snapshot.ts +0 -15
  224. package/src/runtime/onboarding/types.ts +8 -19
  225. package/src/runtime/onboarding/verify.ts +32 -10
  226. package/src/runtime/operator-token-cleanup.ts +1 -1
  227. package/src/runtime/services.ts +91 -22
  228. package/src/runtime/store/selectors/index.ts +3 -3
  229. package/src/runtime/store/state.ts +1 -4
  230. package/src/runtime/surface-feature-flags.ts +41 -6
  231. package/src/runtime/ui-read-models.ts +3 -4
  232. package/src/runtime/ui-services.ts +6 -6
  233. package/src/shell/blocking-input.ts +2 -1
  234. package/src/shell/ui-openers.ts +3 -13
  235. package/src/tools/agent-analysis-registry-policy.ts +0 -1
  236. package/src/tools/agent-channel-send-tool.ts +133 -0
  237. package/src/tools/agent-context-policy.ts +1 -1
  238. package/src/tools/agent-knowledge-ingest-tool.ts +405 -0
  239. package/src/tools/agent-knowledge-tool.ts +170 -0
  240. package/src/tools/agent-local-registry-tool.ts +269 -69
  241. package/src/tools/agent-media-generate-tool.ts +133 -0
  242. package/src/tools/agent-notify-tool.ts +143 -0
  243. package/src/tools/agent-operator-action-tool.ts +137 -0
  244. package/src/tools/agent-operator-briefing-tool.ts +217 -0
  245. package/src/tools/agent-reminder-schedule-tool.ts +237 -0
  246. package/src/tools/agent-tool-policy-guard.ts +8 -8
  247. package/src/tools/agent-work-plan-tool.ts +256 -0
  248. package/src/version.ts +1 -1
  249. package/src/input/commands/policy-dispatch.ts +0 -339
  250. package/src/input/commands/policy.ts +0 -13
  251. package/src/panels/panel-picker.ts +0 -105
  252. package/src/panels/policy-panel.ts +0 -308
  253. package/src/renderer/panel-picker-overlay.ts +0 -202
@@ -15,6 +15,11 @@ export interface ProcessEntry {
15
15
 
16
16
  const MAX_LABEL_LENGTH = 80;
17
17
  const MODAL_BORDER_WIDTH = 8;
18
+ const PROCESS_MODAL_TITLE = 'Runtime Activity';
19
+ const PROCESS_MODAL_EMPTY_MESSAGE = 'No running shell processes.';
20
+ const PROCESS_MODAL_EMPTY_HINTS = ['[Esc] Close'];
21
+ const PROCESS_MODAL_ACTIVE_HINTS = ['[Up/Down] Navigate', '[Enter] Output', '[k] Stop process', '[Esc] Close'];
22
+ const PROCESS_MODAL_TYPE_TAG = '[exec]';
18
23
 
19
24
  export interface ProcessModalDeps {
20
25
  readonly processManager: Pick<ProcessManager, 'list' | 'getStatus' | 'stop'>;
@@ -101,6 +106,22 @@ export class ProcessModal {
101
106
  }
102
107
  }
103
108
 
109
+ export function renderProcessModalPackageText(): string {
110
+ return [
111
+ PROCESS_MODAL_TITLE,
112
+ PROCESS_MODAL_EMPTY_MESSAGE,
113
+ PROCESS_MODAL_TYPE_TAG,
114
+ '*',
115
+ '!',
116
+ '-',
117
+ 'running',
118
+ 'failed',
119
+ '<duration>',
120
+ ...PROCESS_MODAL_EMPTY_HINTS,
121
+ ...PROCESS_MODAL_ACTIVE_HINTS,
122
+ ].join('\n');
123
+ }
124
+
104
125
  export function renderProcessModal(modal: ProcessModal, width: number, viewportHeight = 24): Line[] {
105
126
  modal.refresh();
106
127
 
@@ -118,14 +139,14 @@ export function renderProcessModal(modal: ProcessModal, width: number, viewportH
118
139
 
119
140
  if (modal.entries.length === 0) {
120
141
  return ModalFactory.createModal({
121
- title: 'Runtime Activity',
142
+ title: PROCESS_MODAL_TITLE,
122
143
  width: boxW,
123
144
  margin: boxMargin,
124
145
  targetContentRows,
125
146
  sections: [
126
- { type: 'text', content: 'No running shell processes.' },
147
+ { type: 'text', content: PROCESS_MODAL_EMPTY_MESSAGE },
127
148
  ],
128
- hints: ['[Esc] Close'],
149
+ hints: PROCESS_MODAL_EMPTY_HINTS,
129
150
  }, width);
130
151
  }
131
152
 
@@ -138,7 +159,7 @@ export function renderProcessModal(modal: ProcessModal, width: number, viewportH
138
159
  const statusIcon = entry.status === 'running' ? '*' : entry.status === 'failed' ? '!' : '-';
139
160
  const dur = formatDuration(entry.elapsedMs);
140
161
  const suffix = ` ${entry.status} ${dur}`;
141
- const typeTag = '[exec]';
162
+ const typeTag = PROCESS_MODAL_TYPE_TAG;
142
163
  const maxDescW = Math.max(0, maxLabelW - typeTag.length - suffix.length - 4);
143
164
  const desc = entry.label.length > maxDescW ? `${entry.label.slice(0, Math.max(0, maxDescW - 3))}...` : entry.label;
144
165
  return {
@@ -150,7 +171,7 @@ export function renderProcessModal(modal: ProcessModal, width: number, viewportH
150
171
  if (modal.entries.length > maxVisibleRows) sections.push({ type: 'separator' });
151
172
 
152
173
  return ModalFactory.createModal({
153
- title: 'Runtime Activity',
174
+ title: PROCESS_MODAL_TITLE,
154
175
  width: boxW,
155
176
  margin: boxMargin,
156
177
  targetContentRows,
@@ -158,6 +179,6 @@ export function renderProcessModal(modal: ProcessModal, width: number, viewportH
158
179
  helpers: modal.entries.length > maxVisibleRows
159
180
  ? [{ content: `[${window.start + 1}-${window.end} of ${modal.entries.length}]` }]
160
181
  : undefined,
161
- hints: ['[Up/Down] Navigate', '[Enter] Output', '[k] Stop process', '[Esc] Close'],
182
+ hints: PROCESS_MODAL_ACTIVE_HINTS,
162
183
  }, width);
163
184
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * renderProfilePickerModal — renders the /profiles picker modal as Line[]
2
+ * renderProfilePickerModal — renders the Agent profile picker modal as Line[]
3
3
  * using ModalFactory.
4
4
  *
5
5
  * Shows a list of saved profiles with:
@@ -10,6 +10,7 @@
10
10
  import type { Line } from '../types/grid.ts';
11
11
  import { ModalFactory } from './modal-factory.ts';
12
12
  import type { ProfilePickerModal } from '../input/profile-picker-modal.ts';
13
+ import { renderProfilePickerStatePackageText } from '../input/profile-picker-modal.ts';
13
14
  import { formatTimestamp } from './modal-utils.ts';
14
15
  import { fitDisplay } from '../utils/terminal-width.ts';
15
16
  import { getOverlaySurfaceMetrics, getStableOverlayContentRows } from './overlay-viewport.ts';
@@ -18,6 +19,23 @@ import { getOverlaySurfaceMetrics, getStableOverlayContentRows } from './overlay
18
19
  // Renderer
19
20
  // ---------------------------------------------------------------------------
20
21
 
22
+ export function renderProfilePickerPackageText(): string {
23
+ return [
24
+ 'Profiles',
25
+ 'No saved profiles.',
26
+ 'Open Agent Workspace -> Profiles to create and manage isolated Agent profile homes.',
27
+ 'Name',
28
+ 'Saved',
29
+ 'Settings',
30
+ '(display/provider/behavior)',
31
+ '[Up/Down] Navigate',
32
+ '[Enter] Load',
33
+ 'Agent profiles: /agent profiles',
34
+ '[Esc] Close',
35
+ renderProfilePickerStatePackageText(),
36
+ ].join('\n');
37
+ }
38
+
21
39
  /**
22
40
  * Render the profile picker modal as Line[] for overlay in the viewport.
23
41
  *
@@ -51,7 +69,7 @@ export function renderProfilePickerModal(
51
69
  });
52
70
  sections.push({
53
71
  type: 'text',
54
- content: 'Use /agent profiles to create and manage isolated Agent profile homes.',
72
+ content: 'Open Agent Workspace -> Profiles to create and manage isolated Agent profile homes.',
55
73
  style: { fg: '240', dim: true },
56
74
  });
57
75
  } else {
@@ -79,8 +97,6 @@ export function renderProfilePickerModal(
79
97
 
80
98
  const tsStr = fitDisplay(formatTimestamp(prof.timestamp), tsW);
81
99
 
82
- // Read the profile file to get a preview of settings
83
- // (We only have name/timestamp in ProfileInfo, so show a placeholder)
84
100
  const preview = fitDisplay('(display/provider/behavior)', previewW);
85
101
 
86
102
  const label = `${nameStr} ${tsStr} ${preview}`;
@@ -3,6 +3,26 @@ import { fitDisplay, getDisplayWidth, truncateDisplay } from '../utils/terminal-
3
3
  import type { SearchManager } from '../input/search.ts';
4
4
  import { createBottomBarLine, writeBottomBarText } from './bottom-bar.ts';
5
5
 
6
+ const SEARCH_OVERLAY_LABEL = ' Find: ';
7
+ const SEARCH_OVERLAY_NO_MATCHES = 'No matches';
8
+ const SEARCH_OVERLAY_COUNT_SUFFIX = 'up/down';
9
+ const SEARCH_OVERLAY_LOCKED_HINTS = ' [Up/Down] or [jk] navigate [Bksp] edit [Esc] close';
10
+ const SEARCH_OVERLAY_UNLOCKED_HINTS = ' [Enter/Tab] lock [Esc] close';
11
+
12
+ function searchOverlayMatchCount(current: string | number, total: string | number): string {
13
+ return `${current}/${total} ${SEARCH_OVERLAY_COUNT_SUFFIX}`;
14
+ }
15
+
16
+ export function renderSearchOverlayPackageText(): string {
17
+ return [
18
+ SEARCH_OVERLAY_LABEL.trim(),
19
+ searchOverlayMatchCount('<current>', '<total>'),
20
+ SEARCH_OVERLAY_NO_MATCHES,
21
+ SEARCH_OVERLAY_LOCKED_HINTS.trim(),
22
+ SEARCH_OVERLAY_UNLOCKED_HINTS.trim(),
23
+ ].join('\n');
24
+ }
25
+
6
26
  /**
7
27
  * Render the search bar as a single Line[] overlay at the bottom of the viewport.
8
28
  * Format: [ Find: <query> 3/17 up/down [n] next [N] prev [Esc] close ]
@@ -14,18 +34,18 @@ export function renderSearchOverlay(
14
34
  ): Line[] {
15
35
  // Match count text — displayed in dim grey, right of query, left of hints
16
36
  const matchCount = manager.matches?.length > 0
17
- ? `${manager.currentMatch + 1}/${manager.matches.length} up/down`
37
+ ? searchOverlayMatchCount(manager.currentMatch + 1, manager.matches.length)
18
38
  : manager.query.length > 0
19
- ? 'No matches'
39
+ ? SEARCH_OVERLAY_NO_MATCHES
20
40
  : '';
21
41
 
22
42
  const locked = manager.locked;
23
43
  const cursor = locked ? '' : '█';
24
44
  const queryDisplay = manager.query + cursor;
25
45
  const hints = locked
26
- ? ' [Up/Down] or [jk] navigate [Bksp] edit [Esc] close'
27
- : ' [Enter/Tab] lock [Esc] close';
28
- const label = ' Find: ';
46
+ ? SEARCH_OVERLAY_LOCKED_HINTS
47
+ : SEARCH_OVERLAY_UNLOCKED_HINTS;
48
+ const label = SEARCH_OVERLAY_LABEL;
29
49
  const matchStr = matchCount ? ` ${matchCount}` : '';
30
50
 
31
51
  // Build left portion: label + query (no match count — that gets separate styling)
@@ -1,6 +1,6 @@
1
1
  import { type Line } from '../types/grid.ts';
2
2
  import { fitDisplay, getDisplayWidth, truncateDisplay } from '../utils/terminal-width.ts';
3
- import type { SelectionModal } from '../input/selection-modal.ts';
3
+ import type { SelectionAction, SelectionModal } from '../input/selection-modal.ts';
4
4
  import {
5
5
  createOverlayBoxLayout,
6
6
  createOverlayContentLine,
@@ -18,6 +18,14 @@ const BODY_FG = DEFAULT_OVERLAY_PALETTE.bodyFg;
18
18
  const MUTED_FG = DEFAULT_OVERLAY_PALETTE.mutedFg;
19
19
  const CATEGORY_FG = '#4488cc';
20
20
  const SELECTED_BG = DEFAULT_OVERLAY_PALETTE.selectedBg;
21
+ const SELECTION_MODAL_SEARCH_LABEL = ' Search';
22
+ const SELECTION_MODAL_RESULTS_LABEL = ' Results';
23
+ const SELECTION_MODAL_NO_MATCHING_ITEMS = 'No matching items';
24
+ const SELECTION_MODAL_NO_ITEMS = 'No items';
25
+ const SELECTION_MODAL_NAVIGATION_HINT = '[Up/Down] Navigate';
26
+ const SELECTION_MODAL_CLOSE_HINT = '[Esc] Close';
27
+ const SELECTION_MODAL_SEARCH_HINT = '[/] Search';
28
+ const SELECTION_MODAL_SPACE_TOGGLE_HINT = '[Space] Toggle';
21
29
 
22
30
  interface CellStyle {
23
31
  fg: string;
@@ -30,6 +38,36 @@ function putText(line: Line, startX: number, maxWidth: number, text: string, sty
30
38
  putOverlayText(line, startX, maxWidth, text, style);
31
39
  }
32
40
 
41
+ function primaryVerbForAction(primaryAction: SelectionAction | undefined): string {
42
+ return primaryAction === 'toggle'
43
+ ? '[Enter] Toggle'
44
+ : primaryAction === 'edit'
45
+ ? '[Enter] Edit'
46
+ : primaryAction === 'delete'
47
+ ? '[Enter] Delete'
48
+ : '[Enter] Select';
49
+ }
50
+
51
+ export function renderSelectionModalPackageText(): string {
52
+ return [
53
+ SELECTION_MODAL_SEARCH_LABEL.trim(),
54
+ SELECTION_MODAL_RESULTS_LABEL.trim(),
55
+ SELECTION_MODAL_NO_MATCHING_ITEMS,
56
+ SELECTION_MODAL_NO_ITEMS,
57
+ '(<above> above, <below> below)',
58
+ '(<below> below)',
59
+ '(<above> above)',
60
+ SELECTION_MODAL_NAVIGATION_HINT,
61
+ primaryVerbForAction(undefined),
62
+ primaryVerbForAction('toggle'),
63
+ primaryVerbForAction('edit'),
64
+ primaryVerbForAction('delete'),
65
+ SELECTION_MODAL_CLOSE_HINT,
66
+ SELECTION_MODAL_SEARCH_HINT,
67
+ SELECTION_MODAL_SPACE_TOGGLE_HINT,
68
+ ].join('\n');
69
+ }
70
+
33
71
  /**
34
72
  * Render the selection modal as Line[] for overlay in the viewport.
35
73
  */
@@ -62,7 +100,7 @@ export function renderSelectionModalOverlay(
62
100
 
63
101
  if (modal.allowSearch) {
64
102
  const labelLine = createOverlayContentLine(width, layout, BORDER_FG, DEFAULT_OVERLAY_PALETTE.sectionBg);
65
- putText(labelLine, layout.margin + 2, layout.innerWidth, fitDisplay(' Search', layout.innerWidth), {
103
+ putText(labelLine, layout.margin + 2, layout.innerWidth, fitDisplay(SELECTION_MODAL_SEARCH_LABEL, layout.innerWidth), {
66
104
  fg: CATEGORY_FG,
67
105
  dim: true,
68
106
  });
@@ -86,7 +124,7 @@ export function renderSelectionModalOverlay(
86
124
  }
87
125
 
88
126
  const listTitle = createOverlayContentLine(width, layout, BORDER_FG, DEFAULT_OVERLAY_PALETTE.sectionBg);
89
- putText(listTitle, layout.margin + 2, layout.innerWidth, fitDisplay(' Results', layout.innerWidth), {
127
+ putText(listTitle, layout.margin + 2, layout.innerWidth, fitDisplay(SELECTION_MODAL_RESULTS_LABEL, layout.innerWidth), {
90
128
  fg: CATEGORY_FG,
91
129
  dim: true,
92
130
  });
@@ -95,7 +133,7 @@ export function renderSelectionModalOverlay(
95
133
  const items = modal.filteredItems;
96
134
  if (items.length === 0) {
97
135
  const line = createOverlayContentLine(width, layout, BORDER_FG, DEFAULT_OVERLAY_PALETTE.bodyBg);
98
- const message = modal.query ? 'No matching items' : 'No items';
136
+ const message = modal.query ? SELECTION_MODAL_NO_MATCHING_ITEMS : SELECTION_MODAL_NO_ITEMS;
99
137
  putText(line, layout.margin + 2, layout.innerWidth, fitDisplay(message, layout.innerWidth), { fg: MUTED_FG, dim: true });
100
138
  lines.push(line);
101
139
  } else {
@@ -189,16 +227,10 @@ export function renderSelectionModalOverlay(
189
227
 
190
228
  const footerLine = createOverlayContentLine(width, layout, BORDER_FG, DEFAULT_OVERLAY_PALETTE.sectionBg);
191
229
  const selectedItem = modal.getSelected();
192
- const primaryVerb = selectedItem?.primaryAction === 'toggle'
193
- ? '[Enter] Toggle'
194
- : selectedItem?.primaryAction === 'edit'
195
- ? '[Enter] Edit'
196
- : selectedItem?.primaryAction === 'delete'
197
- ? '[Enter] Delete'
198
- : '[Enter] Select';
199
- let hints = `[Up/Down] Navigate ${primaryVerb} [Esc] Close`;
200
- if (modal.allowSearch) hints += ' [/] Search';
201
- if (selectedItem?.primaryAction === 'toggle' && !selectedItem.actions) hints += ' [Space] Toggle';
230
+ const primaryVerb = primaryVerbForAction(selectedItem?.primaryAction);
231
+ let hints = `${SELECTION_MODAL_NAVIGATION_HINT} ${primaryVerb} ${SELECTION_MODAL_CLOSE_HINT}`;
232
+ if (modal.allowSearch) hints += ` ${SELECTION_MODAL_SEARCH_HINT}`;
233
+ if (selectedItem?.primaryAction === 'toggle' && !selectedItem.actions) hints += ` ${SELECTION_MODAL_SPACE_TOGGLE_HINT}`;
202
234
  if (selectedItem?.actions) hints += ` ${selectedItem.actions}`;
203
235
  putText(
204
236
  footerLine,
@@ -10,6 +10,7 @@
10
10
  import type { Line } from '../types/grid.ts';
11
11
  import { ModalFactory } from './modal-factory.ts';
12
12
  import type { SessionPickerModal } from '../input/session-picker-modal.ts';
13
+ import { renderSessionPickerStatePackageText } from '../input/session-picker-modal.ts';
13
14
  import { formatTimestamp } from './modal-utils.ts';
14
15
  import { fitDisplay } from '../utils/terminal-width.ts';
15
16
  import { getOverlaySurfaceMetrics, getStableOverlayContentRows } from './overlay-viewport.ts';
@@ -18,6 +19,22 @@ import { getOverlaySurfaceMetrics, getStableOverlayContentRows } from './overlay
18
19
  // Renderer
19
20
  // ---------------------------------------------------------------------------
20
21
 
22
+ export function renderSessionPickerPackageText(): string {
23
+ return [
24
+ 'Sessions',
25
+ 'No saved sessions.',
26
+ 'Open Agent Workspace -> Conversation -> Save current session.',
27
+ 'Name',
28
+ 'Saved',
29
+ 'Msgs',
30
+ '[\u2191\u2193] Navigate',
31
+ '[Enter] Load',
32
+ 'Delete: /session delete <id> --yes',
33
+ '[Esc] Close',
34
+ renderSessionPickerStatePackageText(),
35
+ ].join('\n');
36
+ }
37
+
21
38
  /**
22
39
  * Render the session picker modal as Line[] for overlay in the viewport.
23
40
  *
@@ -51,7 +68,7 @@ export function renderSessionPickerModal(
51
68
  });
52
69
  sections.push({
53
70
  type: 'text',
54
- content: 'Use /save [name] to save the current session.',
71
+ content: 'Open Agent Workspace -> Conversation -> Save current session.',
55
72
  style: { fg: '240', dim: true },
56
73
  });
57
74
  } else {
@@ -82,33 +82,21 @@ export const CATEGORY_LABELS: Record<(typeof SETTINGS_CATEGORIES)[number], strin
82
82
  behavior: 'Behavior',
83
83
  storage: 'Storage',
84
84
  permissions: 'Permissions',
85
- orchestration: 'Orchestration',
86
- wrfc: 'Build Delegation',
87
85
  helper: 'Helper',
88
86
  tts: 'TTS',
89
- service: 'Connected Host',
90
- controlPlane: 'Connected API',
91
- httpListener: 'Inbound Delivery',
92
- web: 'Browser Companion',
93
- batch: 'Batch',
94
87
  automation: 'Automation',
95
- watchers: 'Watchers',
96
- runtime: 'Runtime',
97
88
  telemetry: 'Telemetry',
98
89
  cache: 'Cache',
99
90
  mcp: 'MCP',
100
91
  surfaces: 'Channels',
101
- release: 'Release',
102
- danger: 'Danger',
92
+ release: 'Update Channel',
103
93
  tools: 'Tools',
104
- flags: 'Feature Flags',
105
- network: 'Network',
94
+ flags: 'Feature Controls',
106
95
  };
107
96
 
108
97
  export const SETTING_LABELS: Partial<Record<string, string>> = {
109
98
  'ui.systemMessages': 'System Message Target',
110
99
  'ui.operationalMessages': 'Operational Message Target',
111
- 'ui.wrfcMessages': 'Delegated Build Message Target',
112
100
  'ui.voiceEnabled': 'Voice Interaction',
113
101
  'behavior.autoCompactThreshold': 'Auto-Compact %',
114
102
  'behavior.staleContextWarnings': 'Context Warnings',
@@ -123,32 +111,6 @@ export const SETTING_LABELS: Partial<Record<string, string>> = {
123
111
  'helper.enabled': 'Helper Enabled',
124
112
  'helper.globalProvider': 'Helper Provider',
125
113
  'helper.globalModel': 'Helper Model',
126
- // Runtime API
127
- 'controlPlane.enabled': 'Connected API Enabled',
128
- 'controlPlane.hostMode': 'Connected API Host Mode',
129
- 'controlPlane.host': 'Connected API Host',
130
- 'controlPlane.port': 'Connected API Port',
131
- 'controlPlane.baseUrl': 'Connected API Base URL',
132
- 'controlPlane.streamMode': 'Connected API Stream Mode',
133
- 'controlPlane.allowRemote': 'Connected API Allow Remote',
134
- 'controlPlane.trustProxy': 'Connected API Trust Proxy',
135
- 'controlPlane.tls.mode': 'Connected API TLS Mode',
136
- 'controlPlane.tls.certFile': 'Connected API TLS Cert',
137
- 'controlPlane.tls.keyFile': 'Connected API TLS Key',
138
- // Inbound delivery endpoint
139
- 'httpListener.hostMode': 'Inbound Delivery Host Mode',
140
- 'httpListener.host': 'Inbound Delivery Host',
141
- 'httpListener.port': 'Inbound Delivery Port',
142
- 'httpListener.trustProxy': 'Inbound Delivery Trust Proxy',
143
- 'httpListener.tls.mode': 'Inbound Delivery TLS Mode',
144
- 'httpListener.tls.certFile': 'Inbound Delivery TLS Cert',
145
- // Browser companion
146
- 'web.enabled': 'Web Enabled',
147
- 'web.hostMode': 'Web Host Mode',
148
- 'web.host': 'Web Host',
149
- 'web.port': 'Web Port',
150
- 'web.publicBaseUrl': 'Web Public Base URL',
151
- 'web.staticAssetsDir': 'Web Static Assets Dir',
152
114
  'surfaces.ntfy.enabled': 'ntfy Enabled',
153
115
  'surfaces.ntfy.baseUrl': 'ntfy Base URL',
154
116
  'surfaces.ntfy.topic': 'ntfy Default Delivery Topic',
@@ -11,6 +11,7 @@ import { isExternalHostOwnedSettingKey, SETTINGS_CATEGORIES, SETTINGS_CATEGORY_G
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';
14
+ import { formatProviderAuthRouteId } from '../provider-auth-route-display.ts';
14
15
  import { GLYPHS } from './ui-primitives.ts';
15
16
  import {
16
17
  clamp,
@@ -30,27 +31,16 @@ const CATEGORY_INFO: Record<SettingsCategory, string> = {
30
31
  behavior: 'Day-to-day shell behavior: approval posture, compaction, history, guidance, notifications, stale-context warnings, return context, and Human-in-the-Loop mode.',
31
32
  storage: 'Local storage posture, including secret storage policy and maximum artifact size for Agent Knowledge, artifacts, and document ingestion.',
32
33
  permissions: 'Permission mode and tool-class policy. These settings decide whether the shell prompts before read/write/exec/network/agent actions.',
33
- orchestration: 'Hidden compatibility settings. Normal Agent operation stays serial and does not expose local spawn fanout.',
34
- wrfc: 'Build-review routing is external to normal Agent operation. Review these compatibility values only for explicit delegated build work.',
35
34
  helper: 'Helper model defaults used by helper subsystems when they do not use the main chat route.',
36
35
  tts: 'Text-to-speech provider, voice, and optional spoken-turn LLM overrides.',
37
- service: 'Connected-host status posture. Agent inspects these values only and does not install, start, stop, restart, or autostart anything.',
38
- controlPlane: 'Connected API settings. Agent uses these values for access and does not mutate bind posture.',
39
- httpListener: 'Inbound delivery endpoint settings owned by the connected GoodVibes host. Agent inspects readiness and does not expose endpoints.',
40
- web: 'External browser companion settings. Agent does not own browser hosting or network bind lifecycle.',
41
- batch: 'Queued execution settings reported from the connected GoodVibes host. Agent does not own remote queue provisioning.',
42
36
  automation: 'Scheduled and automated run settings, concurrency, timeout, catch-up, cooldown, and retention behavior.',
43
- watchers: 'File/process watcher heartbeat, polling, and recovery-window behavior.',
44
- runtime: 'Connected-host guardrails such as companion chat limits and event-stream caps.',
45
37
  telemetry: 'Telemetry payload policy.',
46
38
  cache: 'Provider and model cache behavior, TTL, and hit-rate monitoring.',
47
39
  mcp: 'MCP server trust and scope review. Trust changes can expose local files, tools, databases, browsers, or remote automation depending on the server.',
48
40
  surfaces: 'Messaging and notification channel accounts such as Slack, Discord, ntfy, Telegram, chat bridges, and delivery providers.',
49
- release: 'Release-channel preference.',
50
- danger: 'High-impact host switches. Agent renders host-owned switches read-only; change them outside Agent.',
41
+ release: 'Update-channel preference.',
51
42
  tools: 'Tool LLM and helper model routing. Empty provider/model values inherit the active chat route unless a specific helper/tool route is set.',
52
- flags: 'Feature flags are SDK gates. They are separate from normal config keys because they enable or disable staged behavior.',
53
- network: 'Read-only view of connected GoodVibes API, inbound delivery, and browser companion bind posture plus editable Agent network settings.',
43
+ flags: 'Feature controls for optional behavior that can be enabled or disabled separately from normal configuration.',
54
44
  };
55
45
 
56
46
  const ENUM_VALUE_DESCRIPTIONS: Record<string, Record<string, string>> = {
@@ -74,26 +64,6 @@ const ENUM_VALUE_DESCRIPTIONS: Record<string, Record<string, string>> = {
74
64
  require_secure: 'Require secure secret storage and reject plaintext fallback.',
75
65
  plaintext_allowed: 'Allow plaintext fallback when secure storage is unavailable.',
76
66
  },
77
- 'batch.mode': {
78
- off: 'Keep runtime work on the immediate local path.',
79
- explicit: 'Use batch only when callers explicitly request batch execution.',
80
- 'eligible-by-default': 'Allow eligible runtime work to use the batch path unless callers opt out.',
81
- },
82
- 'controlPlane.hostMode': {
83
- localhost: 'Bind only to this computer.',
84
- network: 'Bind for LAN access using the default network host.',
85
- custom: 'Use the explicit host value in the related host setting.',
86
- },
87
- 'httpListener.hostMode': {
88
- localhost: 'Bind only to this computer.',
89
- network: 'Bind for LAN event delivery using the default network host.',
90
- custom: 'Use the explicit host value in the related host setting.',
91
- },
92
- 'web.hostMode': {
93
- localhost: 'Serve the browser UI only on this computer.',
94
- network: 'Serve the browser UI on the LAN.',
95
- custom: 'Use the explicit host value in the related host setting.',
96
- },
97
67
  'ui.systemMessages': {
98
68
  panel: 'Show system messages in panels only.',
99
69
  conversation: 'Show system messages inline in the transcript.',
@@ -104,11 +74,6 @@ const ENUM_VALUE_DESCRIPTIONS: Record<string, Record<string, string>> = {
104
74
  conversation: 'Show operational messages inline in the transcript.',
105
75
  both: 'Show operational messages in both panels and the transcript.',
106
76
  },
107
- 'ui.wrfcMessages': {
108
- panel: 'Show explicit delegated build messages in panels only.',
109
- conversation: 'Show explicit delegated build messages inline in the transcript.',
110
- both: 'Show explicit delegated build messages in both panels and the transcript.',
111
- },
112
77
  'surfaces.telegram.mode': {
113
78
  webhook: 'Receive Telegram updates through externally hosted delivery.',
114
79
  polling: 'Poll Telegram for updates from the configured account.',
@@ -155,7 +120,6 @@ function buildSettingContext(modal: SettingsModal, entry: SettingEntry): string[
155
120
  if (
156
121
  entry.setting.key === 'ui.systemMessages'
157
122
  || entry.setting.key === 'ui.operationalMessages'
158
- || entry.setting.key === 'ui.wrfcMessages'
159
123
  ) {
160
124
  lines.push(`Routing meaning: ${describeUiRouting(String(entry.currentValue))}.`);
161
125
  }
@@ -194,8 +158,12 @@ function buildSettingContext(modal: SettingsModal, entry: SettingEntry): string[
194
158
  return lines;
195
159
  }
196
160
 
161
+ function formatSubscriptionRoute(route: SubscriptionEntry['activeRoute'] | SubscriptionEntry['preferredRoute']): string {
162
+ return route ? formatProviderAuthRouteId(route) : 'n/a';
163
+ }
164
+
197
165
  function buildFlagContext(entry: FlagEntry | null): string[] {
198
- if (!entry) return ['Feature flags', 'No feature flag is selected.'];
166
+ if (!entry) return ['Feature Controls', 'No feature control is selected.'];
199
167
  return [
200
168
  entry.flag.name,
201
169
  `ID: ${entry.flag.id}`,
@@ -209,7 +177,7 @@ function buildFlagContext(entry: FlagEntry | null): string[] {
209
177
  '',
210
178
  entry.flag.runtimeToggleable
211
179
  ? 'Impact: changes apply immediately and are also persisted as an override when they differ from the default.'
212
- : 'Impact: this flag is persisted as an override and requires restart before startup-only code sees the new state.',
180
+ : 'Impact: this override is saved for the next Agent launch or owning-host reload, depending on who owns the flag.',
213
181
  ];
214
182
  }
215
183
 
@@ -248,14 +216,14 @@ function buildSubscriptionContext(modal: SettingsModal, entry: SubscriptionEntry
248
216
  ? modal.subscriptionLogoutConfirmationTarget === entry.provider
249
217
  ? `Press Enter again to sign out ${entry.provider}. Move selection or close config to cancel.`
250
218
  : 'Press Enter to review sign-out for this provider session.'
251
- : `Open /agent setup and choose Start subscription login for ${entry.provider}.`;
219
+ : `Open Agent Workspace -> Setup and choose Start subscription login for ${entry.provider}.`;
252
220
  return [
253
221
  entry.provider,
254
222
  `State: ${entry.state}`,
255
223
  ...(routeReason ? [routeReason] : []),
256
224
  logout,
257
- `Active route: ${entry.activeRoute ?? 'n/a'}`,
258
- `Preferred route: ${entry.preferredRoute ?? 'n/a'}`,
225
+ `Active route: ${formatSubscriptionRoute(entry.activeRoute)}`,
226
+ `Preferred route: ${formatSubscriptionRoute(entry.preferredRoute)}`,
259
227
  `OAuth configured: ${entry.oauthConfigured ? 'yes' : 'no'}`,
260
228
  `Freshness: ${entry.authFreshness ?? 'n/a'}`,
261
229
  `Expires: ${expires}`,
@@ -390,10 +358,10 @@ function renderFlagRows(modal: SettingsModal, width: number, height: number): st
390
358
  const nameWidth = clamp(Math.floor(width * 0.40), 24, 58);
391
359
  const stateWidth = 10;
392
360
  const tierWidth = 6;
393
- const runtimeWidth = 9;
361
+ const runtimeWidth = 10;
394
362
  const defaultWidth = 9;
395
363
  const idWidth = Math.max(12, width - nameWidth - stateWidth - tierWidth - runtimeWidth - defaultWidth - 14);
396
- rows.push(` ${padDisplay('Feature Flag', nameWidth)} ${padDisplay('State', stateWidth)} ${padDisplay('Tier', tierWidth)} ${padDisplay('Runtime', runtimeWidth)} ${padDisplay('Default', defaultWidth)} ${padDisplay('ID', idWidth)}`);
364
+ rows.push(` ${padDisplay('Feature Flag', nameWidth)} ${padDisplay('State', stateWidth)} ${padDisplay('Tier', tierWidth)} ${padDisplay('Applies', runtimeWidth)} ${padDisplay('Default', defaultWidth)} ${padDisplay('ID', idWidth)}`);
397
365
  const visibleCount = Math.max(1, height - 2);
398
366
  const window = stableWindow(items.length, selectedIndex, visibleCount);
399
367
  if (window.start > 0) rows.push(`${GLYPHS.navigation.moreAbove} ${window.start} more flag(s) above`);
@@ -401,7 +369,7 @@ function renderFlagRows(modal: SettingsModal, width: number, height: number): st
401
369
  const entry = items[index]!;
402
370
  const selected = index === selectedIndex;
403
371
  const marker = selected ? (modal.focusPane === 'settings' ? GLYPHS.navigation.selected : '•') : ' ';
404
- rows.push(`${marker} ${padDisplay(entry.flag.name, nameWidth)} ${padDisplay(entry.state, stateWidth)} ${padDisplay(String(entry.flag.tier), tierWidth)} ${padDisplay(entry.flag.runtimeToggleable ? 'yes' : 'restart', runtimeWidth)} ${padDisplay(entry.flag.defaultState, defaultWidth)} ${padDisplay(entry.flag.id, idWidth)}`);
372
+ rows.push(`${marker} ${padDisplay(entry.flag.name, nameWidth)} ${padDisplay(entry.state, stateWidth)} ${padDisplay(String(entry.flag.tier), tierWidth)} ${padDisplay(entry.flag.runtimeToggleable ? 'now' : 'next run', runtimeWidth)} ${padDisplay(entry.flag.defaultState, defaultWidth)} ${padDisplay(entry.flag.id, idWidth)}`);
405
373
  }
406
374
  if (window.end < items.length) rows.push(`${GLYPHS.navigation.moreBelow} ${items.length - window.end} more flag(s) below`);
407
375
  return rows.slice(0, height);
@@ -450,7 +418,7 @@ function renderSubscriptionRows(modal: SettingsModal, width: number, height: num
450
418
  const entry = items[index]!;
451
419
  const selected = index === selectedIndex;
452
420
  const marker = selected ? (modal.focusPane === 'settings' ? GLYPHS.navigation.selected : '•') : ' ';
453
- rows.push(`${marker} ${padDisplay(entry.provider, providerWidth)} ${padDisplay(entry.state, stateWidth)} ${padDisplay(entry.activeRoute ?? 'n/a', routeWidth)} ${padDisplay(entry.authFreshness ?? 'n/a', freshnessWidth)} ${padDisplay(entry.oauthConfigured ? 'yes' : 'no', oauthWidth)} ${padDisplay(inferSubscriptionRouteReason(entry) ?? '', noteWidth)}`);
421
+ rows.push(`${marker} ${padDisplay(entry.provider, providerWidth)} ${padDisplay(entry.state, stateWidth)} ${padDisplay(formatSubscriptionRoute(entry.activeRoute), routeWidth)} ${padDisplay(entry.authFreshness ?? 'n/a', freshnessWidth)} ${padDisplay(entry.oauthConfigured ? 'yes' : 'no', oauthWidth)} ${padDisplay(inferSubscriptionRouteReason(entry) ?? '', noteWidth)}`);
454
422
  }
455
423
  if (window.end < items.length) rows.push(`${GLYPHS.navigation.moreBelow} ${items.length - window.end} more subscription provider(s) below`);
456
424
  return rows.slice(0, height);
@@ -464,7 +432,6 @@ function renderControlRows(modal: SettingsModal, width: number, height: number):
464
432
  }
465
433
 
466
434
  function rowColorForSetting(modal: SettingsModal, rowText: string): string {
467
- if (modal.currentCategory === 'danger') return PALETTE.bad;
468
435
  if (rowText.startsWith(GLYPHS.navigation.selected)) return PALETTE.text;
469
436
  const selected = modal.getSelected();
470
437
  if (!selected) return PALETTE.text;
@@ -484,13 +451,79 @@ function footerText(modal: SettingsModal): string {
484
451
  return 'Focus settings · Up/Down setting · Left categories · Tab pane · Enter/Space edit/toggle · R reset · Esc close';
485
452
  }
486
453
 
454
+ export function renderSettingsModalPackageText(): string {
455
+ const lines: string[] = [
456
+ 'Configuration Workspace / Settings',
457
+ 'Categories',
458
+ 'Setting',
459
+ 'Value',
460
+ 'Type',
461
+ 'Source',
462
+ 'Default',
463
+ 'Feature Flag',
464
+ 'State',
465
+ 'Tier',
466
+ 'Applies',
467
+ 'Server',
468
+ 'Trust',
469
+ 'Status',
470
+ 'Scope',
471
+ 'Provider',
472
+ 'Route',
473
+ 'Freshness',
474
+ 'OAuth',
475
+ 'Note',
476
+ 'No settings in this category.',
477
+ 'No feature flags registered.',
478
+ 'No MCP servers registered.',
479
+ 'No provider subscriptions available or configured.',
480
+ 'No setting is selected in this category.',
481
+ 'No feature control is selected.',
482
+ 'No MCP server is selected.',
483
+ 'No subscription provider is selected.',
484
+ 'Trust meanings:',
485
+ 'constrained: keep MCP activity inside declared paths/hosts and prompt on risk.',
486
+ 'ask-on-risk: allow routine MCP operations but ask before risky behavior.',
487
+ 'allow-all: allow unrestricted MCP operations for this server after explicit confirmation.',
488
+ 'blocked: prevent this MCP server from being used.',
489
+ 'Possible values:',
490
+ 'true: enabled or allowed for this setting.',
491
+ 'false: disabled or not allowed for this setting.',
492
+ 'Secret handling: raw values entered here are stored through the secret manager and the config receives a goodvibes:// secret reference. Empty input clears the config value.',
493
+ 'Editing: Enter opens inline edit, then type the value and press Enter to save. Arrow keys only navigate.',
494
+ 'Editing: Enter opens inline edit. Delete the current text to save an empty value when that is valid for the setting.',
495
+ 'Enter Confirm edit · Esc Cancel edit · text keys edit the selected field',
496
+ 'Focus categories · Up/Down choose · Right/Enter settings · Tab pane · Esc close',
497
+ 'Focus settings · Up/Down provider · Left categories · Tab pane · Enter review/sign out · Esc close',
498
+ 'Focus settings · Up/Down server · Left categories · Tab pane · Enter edit trust · Esc close',
499
+ 'Focus feature flags · Up/Down flag · Left categories · Tab pane · Enter/Space toggle · Esc close',
500
+ 'Read-only connected-host setting · Change from GoodVibes TUI or the owning host · Esc close',
501
+ 'Focus settings · Up/Down setting · Left categories · Tab pane · Enter/Space edit/toggle · R reset · Esc close',
502
+ ];
503
+
504
+ for (const group of SETTINGS_CATEGORY_GROUPS) {
505
+ lines.push(group.label);
506
+ for (const category of group.categories) {
507
+ lines.push(category, CATEGORY_LABELS[category], CATEGORY_INFO[category]);
508
+ }
509
+ }
510
+
511
+ for (const [key, values] of Object.entries(ENUM_VALUE_DESCRIPTIONS)) {
512
+ lines.push(key);
513
+ for (const [value, description] of Object.entries(values)) {
514
+ lines.push(value, description);
515
+ }
516
+ }
517
+
518
+ return lines.join('\n');
519
+ }
520
+
487
521
  export function renderSettingsModal(
488
522
  modal: SettingsModal,
489
523
  width: number,
490
524
  viewportHeight = 24,
491
525
  ): Line[] {
492
526
  const notices = [
493
- ...(modal.lastSaveTriggeredRestart ? [`Owning host must restart ${modal.lastSaveTriggeredRestart}`] : []),
494
527
  ...(modal.lastSettingEffectMessage ? [modal.lastSettingEffectMessage] : []),
495
528
  ];
496
529
  const metrics = getFullscreenWorkspaceMetrics({ width, height: viewportHeight });