@pellux/goodvibes-agent 0.1.116 → 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 (285) hide show
  1. package/CHANGELOG.md +48 -661
  2. package/README.md +64 -57
  3. package/bin/goodvibes-agent.ts +16 -2
  4. package/dist/package/main.js +199180 -192185
  5. package/docs/README.md +15 -8
  6. package/docs/channels-remote-and-api.md +50 -0
  7. package/docs/connected-host.md +59 -0
  8. package/docs/getting-started.md +61 -50
  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 +48 -11
  13. package/docs/tools-and-commands.md +123 -0
  14. package/docs/voice-and-live-tts.md +51 -0
  15. package/package.json +3 -6
  16. package/src/agent/behavior-discovery-summary.ts +1 -3
  17. package/src/agent/channel-delivery.ts +201 -0
  18. package/src/agent/media-generation.ts +159 -0
  19. package/src/agent/memory-prompt.ts +0 -1
  20. package/src/agent/note-registry.ts +329 -0
  21. package/src/agent/operator-actions.ts +343 -0
  22. package/src/agent/persona-discovery.ts +1 -3
  23. package/src/agent/persona-registry.ts +19 -16
  24. package/src/agent/record-labels.ts +107 -0
  25. package/src/agent/reminder-schedule-format.ts +33 -24
  26. package/src/agent/reminder-schedule.ts +27 -26
  27. package/src/agent/routine-registry.ts +54 -14
  28. package/src/agent/routine-schedule-args.ts +2 -1
  29. package/src/agent/routine-schedule-format.ts +77 -53
  30. package/src/agent/routine-schedule-promotion.ts +35 -33
  31. package/src/agent/routine-schedule-receipts.ts +29 -27
  32. package/src/agent/runtime-profile-starters.ts +5 -5
  33. package/src/agent/runtime-profile.ts +91 -12
  34. package/src/agent/skill-registry.ts +210 -32
  35. package/src/cli/agent-knowledge-args.ts +5 -1
  36. package/src/cli/agent-knowledge-command.ts +40 -34
  37. package/src/cli/agent-knowledge-format.ts +80 -67
  38. package/src/cli/agent-knowledge-methods.ts +1 -1
  39. package/src/cli/agent-knowledge-runtime.ts +32 -26
  40. package/src/cli/bundle-command.ts +14 -9
  41. package/src/cli/config-overrides.ts +37 -36
  42. package/src/cli/entrypoint.ts +16 -7
  43. package/src/cli/external-runtime.ts +10 -4
  44. package/src/cli/help.ts +54 -29
  45. package/src/cli/local-library-command.ts +203 -83
  46. package/src/cli/management-commands.ts +105 -68
  47. package/src/cli/management.ts +55 -74
  48. package/src/cli/memory-command.ts +66 -32
  49. package/src/cli/parser.ts +37 -3
  50. package/src/cli/profiles-command.ts +166 -31
  51. package/src/cli/provider-auth-routes.ts +2 -1
  52. package/src/cli/routines-command.ts +176 -40
  53. package/src/cli/service-posture.ts +16 -16
  54. package/src/cli/status.ts +56 -135
  55. package/src/core/conversation-message-snapshot.ts +131 -0
  56. package/src/input/agent-workspace-access-command-editor-submission.ts +149 -0
  57. package/src/input/agent-workspace-access-command-editors.ts +170 -0
  58. package/src/input/agent-workspace-activation.ts +35 -8
  59. package/src/input/agent-workspace-basic-command-editor-submission.ts +351 -94
  60. package/src/input/agent-workspace-basic-command-editors.ts +502 -9
  61. package/src/input/agent-workspace-categories.ts +423 -51
  62. package/src/input/agent-workspace-channel-command-editor-submission.ts +83 -0
  63. package/src/input/agent-workspace-channel-command-editors.ts +65 -0
  64. package/src/input/agent-workspace-channels.ts +35 -2
  65. package/src/input/agent-workspace-command-editor.ts +41 -3
  66. package/src/input/agent-workspace-config-reader.ts +16 -0
  67. package/src/input/agent-workspace-delegation-editor-submission.ts +55 -0
  68. package/src/input/agent-workspace-editors.ts +166 -1
  69. package/src/input/agent-workspace-knowledge-command-editor-submission.ts +132 -0
  70. package/src/input/agent-workspace-knowledge-command-editors.ts +110 -0
  71. package/src/input/agent-workspace-knowledge-query-editor.ts +1 -1
  72. package/src/input/agent-workspace-learned-behavior.ts +67 -0
  73. package/src/input/agent-workspace-library-command-editor-submission.ts +50 -0
  74. package/src/input/agent-workspace-library-command-editors.ts +35 -0
  75. package/src/input/agent-workspace-local-operations.ts +218 -0
  76. package/src/input/agent-workspace-local-selection.ts +75 -0
  77. package/src/input/agent-workspace-mcp-command-editor-submission.ts +95 -0
  78. package/src/input/agent-workspace-media-command-editor-submission.ts +62 -0
  79. package/src/input/agent-workspace-media-command-editors.ts +27 -0
  80. package/src/input/agent-workspace-memory-command-editor-submission.ts +232 -0
  81. package/src/input/agent-workspace-memory-command-editors.ts +180 -0
  82. package/src/input/agent-workspace-memory-editor.ts +2 -2
  83. package/src/input/agent-workspace-navigation.ts +81 -0
  84. package/src/input/agent-workspace-notify-editor-submission.ts +121 -0
  85. package/src/input/agent-workspace-operations-command-editor-submission.ts +208 -0
  86. package/src/input/agent-workspace-operations-command-editors.ts +196 -0
  87. package/src/input/agent-workspace-panel-route.ts +43 -0
  88. package/src/input/agent-workspace-provider-command-editor-submission.ts +155 -0
  89. package/src/input/agent-workspace-provider-command-editors.ts +93 -0
  90. package/src/input/agent-workspace-reminder-schedule-editor.ts +1 -1
  91. package/src/input/agent-workspace-requirements.ts +11 -0
  92. package/src/input/agent-workspace-routine-schedule-editor.ts +2 -2
  93. package/src/input/agent-workspace-search.ts +169 -0
  94. package/src/input/agent-workspace-secret-editor-submission.ts +153 -0
  95. package/src/input/agent-workspace-session-command-editor-submission.ts +199 -0
  96. package/src/input/agent-workspace-session-command-editors.ts +249 -0
  97. package/src/input/agent-workspace-setup.ts +37 -16
  98. package/src/input/agent-workspace-skill-bundle-command-editor-submission.ts +101 -0
  99. package/src/input/agent-workspace-skill-bundle-command-editors.ts +110 -0
  100. package/src/input/agent-workspace-snapshot.ts +162 -21
  101. package/src/input/agent-workspace-task-command-editor-submission.ts +61 -0
  102. package/src/input/agent-workspace-task-command-editors.ts +47 -0
  103. package/src/input/agent-workspace-token.ts +18 -2
  104. package/src/input/agent-workspace-types.ts +212 -4
  105. package/src/input/agent-workspace-voice-media.ts +4 -7
  106. package/src/input/agent-workspace-web-research-editor.ts +104 -0
  107. package/src/input/agent-workspace-workplan-editor-submission.ts +153 -0
  108. package/src/input/agent-workspace.ts +178 -240
  109. package/src/input/command-registry.ts +5 -2
  110. package/src/input/commands/agent-runtime-profile-runtime.ts +126 -37
  111. package/src/input/commands/agent-skills-runtime.ts +149 -72
  112. package/src/input/commands/agent-workspace-runtime.ts +23 -6
  113. package/src/input/commands/brief-runtime.ts +55 -25
  114. package/src/input/commands/channels-runtime.ts +147 -33
  115. package/src/input/commands/compat-runtime.ts +32 -0
  116. package/src/input/commands/delegation-runtime.ts +29 -23
  117. package/src/input/commands/experience-runtime.ts +6 -4
  118. package/src/input/commands/guidance-runtime.ts +4 -4
  119. package/src/input/commands/health-runtime.ts +149 -126
  120. package/src/input/commands/knowledge.ts +82 -60
  121. package/src/input/commands/local-provider-runtime.ts +43 -21
  122. package/src/input/commands/local-runtime.ts +138 -16
  123. package/src/input/commands/local-setup-review.ts +10 -20
  124. package/src/input/commands/mcp-runtime.ts +63 -46
  125. package/src/input/commands/notify-runtime.ts +38 -9
  126. package/src/input/commands/operator-actions-runtime.ts +138 -0
  127. package/src/input/commands/operator-runtime.ts +6 -76
  128. package/src/input/commands/personas-runtime.ts +46 -31
  129. package/src/input/commands/planning-runtime.ts +1 -1
  130. package/src/input/commands/platform-access-runtime.ts +35 -98
  131. package/src/input/commands/product-runtime.ts +2 -2
  132. package/src/input/commands/provider-accounts-runtime.ts +60 -39
  133. package/src/input/commands/qrcode-runtime.ts +50 -9
  134. package/src/input/commands/recall-bundle.ts +11 -11
  135. package/src/input/commands/recall-capture.ts +13 -11
  136. package/src/input/commands/recall-query.ts +29 -21
  137. package/src/input/commands/recall-review.ts +2 -1
  138. package/src/input/commands/routines-runtime.ts +99 -45
  139. package/src/input/commands/runtime-services.ts +1 -14
  140. package/src/input/commands/schedule-runtime.ts +33 -20
  141. package/src/input/commands/security-runtime.ts +6 -11
  142. package/src/input/commands/session-content.ts +80 -78
  143. package/src/input/commands/session-workflow.ts +134 -104
  144. package/src/input/commands/session.ts +3 -174
  145. package/src/input/commands/shell-core.ts +38 -17
  146. package/src/input/commands/subscription-runtime.ts +54 -185
  147. package/src/input/commands/support-bundle-runtime.ts +53 -0
  148. package/src/input/commands/tasks-runtime.ts +21 -21
  149. package/src/input/commands/work-plan-runtime.ts +33 -8
  150. package/src/input/commands.ts +6 -2
  151. package/src/input/feed-context-factory.ts +2 -2
  152. package/src/input/file-picker.ts +3 -2
  153. package/src/input/handler-command-route.ts +4 -7
  154. package/src/input/handler-content-actions.ts +89 -26
  155. package/src/input/handler-feed-routes.ts +19 -12
  156. package/src/input/handler-feed.ts +6 -5
  157. package/src/input/handler-interactions.ts +9 -36
  158. package/src/input/handler-modal-stack.ts +3 -3
  159. package/src/input/handler-onboarding.ts +52 -91
  160. package/src/input/handler-shortcuts.ts +16 -7
  161. package/src/input/handler.ts +50 -23
  162. package/src/input/input-history.ts +5 -6
  163. package/src/input/keybindings.ts +27 -16
  164. package/src/input/mcp-workspace.ts +90 -26
  165. package/src/input/onboarding/handler-onboarding-routes.ts +3 -1
  166. package/src/input/onboarding/onboarding-wizard-apply.ts +62 -0
  167. package/src/input/onboarding/onboarding-wizard-constants.ts +1 -0
  168. package/src/input/onboarding/onboarding-wizard-helpers.ts +1 -1
  169. package/src/input/onboarding/onboarding-wizard-operator-steps.ts +223 -55
  170. package/src/input/onboarding/onboarding-wizard-steps.ts +59 -35
  171. package/src/input/onboarding/onboarding-wizard-types.ts +4 -1
  172. package/src/input/onboarding/onboarding-wizard.ts +87 -2
  173. package/src/input/profile-picker-modal.ts +31 -12
  174. package/src/input/session-picker-modal.ts +21 -4
  175. package/src/input/settings-modal-agent-policy.ts +6 -6
  176. package/src/input/settings-modal-behavior.ts +0 -3
  177. package/src/input/settings-modal-subscriptions.ts +3 -3
  178. package/src/input/settings-modal-types.ts +2 -13
  179. package/src/input/settings-modal.ts +20 -66
  180. package/src/input/submission-intent.ts +0 -1
  181. package/src/input/submission-router.ts +3 -3
  182. package/src/main.ts +18 -10
  183. package/src/panels/approval-panel.ts +8 -8
  184. package/src/panels/automation-control-panel.ts +4 -4
  185. package/src/panels/base-panel.ts +1 -1
  186. package/src/panels/builtin/agent.ts +1 -1
  187. package/src/panels/builtin/operations.ts +3 -12
  188. package/src/panels/builtin/session.ts +32 -24
  189. package/src/panels/builtin/shared.ts +7 -7
  190. package/src/panels/cost-tracker-panel.ts +1 -1
  191. package/src/panels/docs-panel.ts +5 -3
  192. package/src/panels/index.ts +0 -1
  193. package/src/panels/knowledge-panel.ts +6 -5
  194. package/src/panels/memory-panel.ts +7 -6
  195. package/src/panels/panel-list-panel.ts +36 -80
  196. package/src/panels/project-planning-panel.ts +19 -12
  197. package/src/panels/provider-account-snapshot.ts +51 -25
  198. package/src/panels/provider-accounts-panel.ts +33 -18
  199. package/src/panels/provider-health-domains.ts +48 -7
  200. package/src/panels/provider-health-panel.ts +5 -4
  201. package/src/panels/qr-panel.ts +44 -20
  202. package/src/panels/schedule-panel.ts +9 -17
  203. package/src/panels/security-panel.ts +8 -8
  204. package/src/panels/session-browser-panel.ts +10 -10
  205. package/src/panels/subscription-panel.ts +6 -6
  206. package/src/panels/system-messages-panel.ts +3 -3
  207. package/src/panels/tasks-panel.ts +21 -14
  208. package/src/panels/tool-inspector-panel.ts +19 -12
  209. package/src/panels/work-plan-panel.ts +5 -5
  210. package/src/planning/project-planning-coordinator.ts +3 -3
  211. package/src/provider-auth-route-display.ts +9 -0
  212. package/src/renderer/agent-workspace-style.ts +34 -0
  213. package/src/renderer/agent-workspace.ts +254 -57
  214. package/src/renderer/autocomplete-overlay.ts +25 -6
  215. package/src/renderer/block-actions.ts +1 -3
  216. package/src/renderer/bookmark-modal.ts +19 -4
  217. package/src/renderer/buffer.ts +4 -2
  218. package/src/renderer/context-inspector.ts +50 -13
  219. package/src/renderer/diff.ts +1 -1
  220. package/src/renderer/file-picker-overlay.ts +19 -6
  221. package/src/renderer/help-overlay.ts +112 -34
  222. package/src/renderer/history-search-overlay.ts +19 -4
  223. package/src/renderer/live-tail-modal.ts +27 -5
  224. package/src/renderer/mcp-workspace.ts +176 -43
  225. package/src/renderer/model-picker-overlay.ts +58 -3
  226. package/src/renderer/model-workspace.ts +104 -22
  227. package/src/renderer/onboarding/onboarding-wizard.ts +20 -4
  228. package/src/renderer/process-modal.ts +27 -6
  229. package/src/renderer/profile-picker-modal.ts +21 -5
  230. package/src/renderer/search-overlay.ts +25 -5
  231. package/src/renderer/selection-modal-overlay.ts +46 -14
  232. package/src/renderer/session-picker-modal.ts +18 -1
  233. package/src/renderer/settings-modal-helpers.ts +2 -40
  234. package/src/renderer/settings-modal.ts +88 -55
  235. package/src/renderer/system-message.ts +1 -1
  236. package/src/renderer/tool-call.ts +20 -11
  237. package/src/runtime/agent-runtime-events.ts +129 -0
  238. package/src/runtime/bootstrap-command-context.ts +7 -1
  239. package/src/runtime/bootstrap-command-parts.ts +11 -8
  240. package/src/runtime/bootstrap-core.ts +23 -65
  241. package/src/runtime/bootstrap-hook-bridge.ts +7 -18
  242. package/src/runtime/bootstrap-shell.ts +46 -8
  243. package/src/runtime/bootstrap.ts +36 -46
  244. package/src/runtime/connected-host-auth.ts +47 -0
  245. package/src/runtime/diagnostics/panels/index.ts +2 -2
  246. package/src/runtime/diagnostics/panels/panel-resources.ts +1 -1
  247. package/src/runtime/diagnostics/panels/policy.ts +7 -7
  248. package/src/runtime/index.ts +2 -5
  249. package/src/runtime/onboarding/apply-file-helpers.ts +66 -0
  250. package/src/runtime/onboarding/apply.ts +288 -74
  251. package/src/runtime/onboarding/derivation.ts +10 -13
  252. package/src/runtime/onboarding/snapshot.ts +2 -17
  253. package/src/runtime/onboarding/types.ts +34 -20
  254. package/src/runtime/onboarding/verify.ts +105 -4
  255. package/src/runtime/operator-token-cleanup.ts +3 -3
  256. package/src/runtime/services.ts +95 -11
  257. package/src/runtime/store/selectors/index.ts +3 -3
  258. package/src/runtime/store/state.ts +1 -4
  259. package/src/runtime/surface-feature-flags.ts +41 -6
  260. package/src/runtime/ui-read-models.ts +3 -4
  261. package/src/runtime/ui-services.ts +6 -6
  262. package/src/shell/blocking-input.ts +2 -1
  263. package/src/shell/service-settings-sync.ts +7 -7
  264. package/src/shell/ui-openers.ts +17 -38
  265. package/src/tools/agent-analysis-registry-policy.ts +0 -1
  266. package/src/tools/agent-channel-send-tool.ts +133 -0
  267. package/src/tools/agent-context-policy.ts +1 -1
  268. package/src/tools/agent-knowledge-ingest-tool.ts +405 -0
  269. package/src/tools/agent-knowledge-tool.ts +170 -0
  270. package/src/tools/agent-local-registry-tool.ts +279 -73
  271. package/src/tools/agent-media-generate-tool.ts +133 -0
  272. package/src/tools/agent-notify-tool.ts +143 -0
  273. package/src/tools/agent-operator-action-tool.ts +137 -0
  274. package/src/tools/agent-operator-briefing-tool.ts +217 -0
  275. package/src/tools/agent-reminder-schedule-tool.ts +237 -0
  276. package/src/tools/agent-tool-policy-guard.ts +13 -15
  277. package/src/tools/agent-work-plan-tool.ts +256 -0
  278. package/src/version.ts +1 -1
  279. package/docs/connected-services.md +0 -51
  280. package/src/cli/package-verification.ts +0 -274
  281. package/src/input/commands/policy-dispatch.ts +0 -339
  282. package/src/input/commands/policy.ts +0 -17
  283. package/src/panels/panel-picker.ts +0 -105
  284. package/src/panels/policy-panel.ts +0 -308
  285. package/src/renderer/panel-picker-overlay.ts +0 -202
@@ -1,4 +1,4 @@
1
- import type { McpWorkspace, McpWorkspaceRow } from '../input/mcp-workspace.ts';
1
+ import type { McpWorkspace, McpWorkspaceRow, McpWorkspaceServerRow } from '../input/mcp-workspace.ts';
2
2
  import type { Line } from '../types/grid.ts';
3
3
  import { wrapText } from '../utils/terminal-width.ts';
4
4
  import { GLYPHS } from './ui-primitives.ts';
@@ -11,6 +11,117 @@ import {
11
11
  type WorkspaceRow,
12
12
  } from './fullscreen-workspace.ts';
13
13
 
14
+ const MCP_WORKSPACE_TITLE = 'MCP Workspace / Servers';
15
+ const MCP_WORKSPACE_LEFT_HEADER = 'Servers';
16
+ const MCP_WORKSPACE_GROUP_SERVERS = 'SERVERS';
17
+ const MCP_WORKSPACE_GROUP_ACTIONS = 'ACTIONS';
18
+ const MCP_WORKSPACE_NO_CONFIGURED_SERVERS = ' No configured servers';
19
+ const MCP_WORKSPACE_FORM_WRITE_CONFIRMATION = 'Workspace writes require explicit confirmation. Type yes on the Confirm field, then save from here.';
20
+ const MCP_WORKSPACE_FORM_CONFIG_LOCATIONS = 'Project/global config locations are shown for review. The workspace dispatches confirmed MCP changes through the TUI command router.';
21
+ const MCP_WORKSPACE_REMOVE_HELP = 'Press Enter or y to remove through the TUI command router.';
22
+ const MCP_WORKSPACE_REMOVE_CANCEL_HELP = 'Press n or Esc to return without changing config.';
23
+ const MCP_WORKSPACE_RELOAD_TITLE = 'Reload MCP runtime';
24
+ const MCP_WORKSPACE_RELOAD_HELP = 'Press Enter or y to reload MCP runtime from the current config through the TUI command router.';
25
+ const MCP_WORKSPACE_RELOAD_CANCEL_HELP = 'Press n or Esc to return without reloading.';
26
+ const MCP_WORKSPACE_NO_ROWS = 'No MCP rows available.';
27
+ const MCP_WORKSPACE_RUNTIME_ONLY_COMMAND = '(runtime only; no launch config found)';
28
+ const MCP_WORKSPACE_NONE = '(none)';
29
+ const MCP_WORKSPACE_STATUS_PREFIX = 'Status:';
30
+ const MCP_WORKSPACE_FORM_HEADER_FIELD = 'Field';
31
+ const MCP_WORKSPACE_FORM_HEADER_VALUE = 'Value';
32
+ const MCP_WORKSPACE_FORM_HEADER_EDIT = 'Edit';
33
+ const MCP_WORKSPACE_FORM_TEXT_EDIT = 'text';
34
+ const MCP_WORKSPACE_FORM_ACTION_EDIT = 'cycle/action';
35
+ const MCP_WORKSPACE_EMPTY_VALUE = '(empty)';
36
+ const MCP_WORKSPACE_TOOL_HEADER_TOOL = 'Tool';
37
+ const MCP_WORKSPACE_TOOL_HEADER_SERVER = 'Server';
38
+ const MCP_WORKSPACE_TOOL_HEADER_DESCRIPTION = 'Description';
39
+ const MCP_WORKSPACE_TOOLS_LOADING = 'Tools: loading...';
40
+ const MCP_WORKSPACE_TOOLS_LOADING_DETAIL = 'Loading tool list from connected MCP servers.';
41
+ const MCP_WORKSPACE_TOOLS_EMPTY = 'No tools cached for the selected server. Press t to refresh.';
42
+ const MCP_WORKSPACE_CONFIRM_CANCEL_ROW = ' Cancel and return to MCP server browser';
43
+ const MCP_WORKSPACE_CONFIRM_RELOAD = 'Confirm MCP runtime reload';
44
+ const MCP_WORKSPACE_FOOTER_FORM = 'Focus MCP server form · Up/Down field · Left/Right cycle · Type edit · Enter save/cancel · Esc back';
45
+ const MCP_WORKSPACE_FOOTER_DELETE = 'Focus remove confirmation · Enter/y remove · n/Esc cancel';
46
+ const MCP_WORKSPACE_FOOTER_RELOAD = 'Focus reload confirmation · Enter/y reload · n/Esc cancel';
47
+ const MCP_WORKSPACE_FOOTER_BROWSE = 'Focus MCP workspace · Up/Down choose · Enter view/action · a add · d remove · r reload · t tools · Esc close';
48
+
49
+ function mcpWorkspaceStateLabel(mode: McpWorkspace['mode']): string {
50
+ return mode === 'browse' ? 'Browse' : mode === 'form' ? 'Server Form' : mode === 'delete-confirm' ? 'Remove Confirm' : 'Reload Confirm';
51
+ }
52
+
53
+ function mcpWorkspaceMainHeader(mode: McpWorkspace['mode'], connected: string | number, servers: string | number, tools: string | number): string {
54
+ if (mode === 'form') return 'MCP server form';
55
+ if (mode === 'delete-confirm') return 'MCP remove confirmation';
56
+ if (mode === 'reload-confirm') return 'MCP reload confirmation';
57
+ return `Servers ${connected}/${servers} connected · Tools ${tools}`;
58
+ }
59
+
60
+ function mcpWorkspaceToolsLabel(loading: boolean, server: string | undefined, count: string | number): string {
61
+ if (loading) return MCP_WORKSPACE_TOOLS_LOADING;
62
+ return server ? `Tools for ${server}: ${count}` : `Tools: ${count}`;
63
+ }
64
+
65
+ export function renderMcpWorkspacePackageText(): string {
66
+ return [
67
+ MCP_WORKSPACE_TITLE,
68
+ MCP_WORKSPACE_LEFT_HEADER,
69
+ mcpWorkspaceStateLabel('browse'),
70
+ mcpWorkspaceStateLabel('form'),
71
+ mcpWorkspaceStateLabel('delete-confirm'),
72
+ mcpWorkspaceStateLabel('reload-confirm'),
73
+ mcpWorkspaceMainHeader('browse', '<connected>', '<servers>', '<tools>'),
74
+ mcpWorkspaceMainHeader('form', '<connected>', '<servers>', '<tools>'),
75
+ mcpWorkspaceMainHeader('delete-confirm', '<connected>', '<servers>', '<tools>'),
76
+ mcpWorkspaceMainHeader('reload-confirm', '<connected>', '<servers>', '<tools>'),
77
+ MCP_WORKSPACE_GROUP_SERVERS,
78
+ MCP_WORKSPACE_GROUP_ACTIONS,
79
+ MCP_WORKSPACE_NO_CONFIGURED_SERVERS.trim(),
80
+ 'project config',
81
+ 'global config',
82
+ 'external config',
83
+ 'runtime',
84
+ 'Drafting an MCP server',
85
+ 'Editing server <server>',
86
+ MCP_WORKSPACE_FORM_WRITE_CONFIRMATION,
87
+ MCP_WORKSPACE_FORM_CONFIG_LOCATIONS,
88
+ 'Remove server: <server>',
89
+ MCP_WORKSPACE_REMOVE_HELP,
90
+ MCP_WORKSPACE_REMOVE_CANCEL_HELP,
91
+ MCP_WORKSPACE_RELOAD_TITLE,
92
+ MCP_WORKSPACE_RELOAD_HELP,
93
+ MCP_WORKSPACE_RELOAD_CANCEL_HELP,
94
+ MCP_WORKSPACE_NO_ROWS,
95
+ 'Connected: <yes-no> Origin: <origin> Config: <freshness>',
96
+ 'Role: <role> Review policy: <trust-mode>',
97
+ `Command: ${MCP_WORKSPACE_RUNTIME_ONLY_COMMAND}`,
98
+ `Allowed paths: ${MCP_WORKSPACE_NONE}`,
99
+ `Allowed hosts: ${MCP_WORKSPACE_NONE}`,
100
+ `${MCP_WORKSPACE_STATUS_PREFIX} <status>`,
101
+ MCP_WORKSPACE_FORM_HEADER_FIELD,
102
+ MCP_WORKSPACE_FORM_HEADER_VALUE,
103
+ MCP_WORKSPACE_FORM_HEADER_EDIT,
104
+ MCP_WORKSPACE_FORM_TEXT_EDIT,
105
+ MCP_WORKSPACE_FORM_ACTION_EDIT,
106
+ MCP_WORKSPACE_EMPTY_VALUE,
107
+ mcpWorkspaceToolsLabel(true, undefined, '<count>'),
108
+ mcpWorkspaceToolsLabel(false, '<server>', '<count>'),
109
+ mcpWorkspaceToolsLabel(false, undefined, '<count>'),
110
+ MCP_WORKSPACE_TOOL_HEADER_TOOL,
111
+ MCP_WORKSPACE_TOOL_HEADER_SERVER,
112
+ MCP_WORKSPACE_TOOL_HEADER_DESCRIPTION,
113
+ MCP_WORKSPACE_TOOLS_LOADING_DETAIL,
114
+ MCP_WORKSPACE_TOOLS_EMPTY,
115
+ 'Confirm remove <server>',
116
+ MCP_WORKSPACE_CONFIRM_RELOAD,
117
+ MCP_WORKSPACE_CONFIRM_CANCEL_ROW.trim(),
118
+ MCP_WORKSPACE_FOOTER_FORM,
119
+ MCP_WORKSPACE_FOOTER_DELETE,
120
+ MCP_WORKSPACE_FOOTER_RELOAD,
121
+ MCP_WORKSPACE_FOOTER_BROWSE,
122
+ ].join('\n');
123
+ }
124
+
14
125
  function statusColor(text: string): string {
15
126
  if (text.includes('failed') || text.includes('Save failed') || text.includes('Remove failed')) return PALETTE.bad;
16
127
  if (text.includes('attention') || text.includes('quarantine')) return PALETTE.warn;
@@ -21,15 +132,28 @@ function connectedColor(connected: boolean): string {
21
132
  return connected ? PALETTE.good : PALETTE.warn;
22
133
  }
23
134
 
135
+ function serverOriginLabel(source: McpWorkspaceServerRow['source']): string {
136
+ switch (source) {
137
+ case 'project':
138
+ return 'project config';
139
+ case 'global':
140
+ return 'global config';
141
+ case 'external':
142
+ return 'external config';
143
+ case 'runtime':
144
+ return 'runtime';
145
+ }
146
+ }
147
+
24
148
  function rowLabel(row: McpWorkspaceRow): string {
25
- if (row.type === 'server') return `${row.server.name} (${row.server.source})`;
149
+ if (row.type === 'server') return `${row.server.name} (${serverOriginLabel(row.server.source)})`;
26
150
  return row.label;
27
151
  }
28
152
 
29
153
  function rowDetail(row: McpWorkspaceRow): string {
30
154
  if (row.type === 'server') {
31
155
  const server = row.server;
32
- return `${server.connected ? 'connected' : 'offline'} · ${server.role} · ${server.trustMode} · schema ${server.freshness}`;
156
+ return `${server.connected ? 'connected' : 'offline'} · ${server.role} · ${server.trustMode} · config ${server.freshness}`;
33
157
  }
34
158
  return row.detail;
35
159
  }
@@ -42,13 +166,13 @@ function buildLeftRows(workspace: McpWorkspace, height: number): WorkspaceRow[]
42
166
 
43
167
  workspace.rows.forEach((row, rowIndex) => {
44
168
  if (row.type === 'server' && !sawServerGroup) {
45
- rendered.push({ text: 'SERVERS', kind: 'group', bold: true });
169
+ rendered.push({ text: MCP_WORKSPACE_GROUP_SERVERS, kind: 'group', bold: true });
46
170
  sawServerGroup = true;
47
171
  }
48
172
  if (row.type === 'action' && !sawActionGroup) {
49
- if (!sawServerGroup) rendered.push({ text: 'SERVERS', kind: 'group', bold: true });
50
- if (workspace.servers.length === 0) rendered.push({ text: ' No configured servers', kind: 'item', fg: PALETTE.dim, dim: true });
51
- rendered.push({ text: 'ACTIONS', kind: 'group', bold: true });
173
+ if (!sawServerGroup) rendered.push({ text: MCP_WORKSPACE_GROUP_SERVERS, kind: 'group', bold: true });
174
+ if (workspace.servers.length === 0) rendered.push({ text: MCP_WORKSPACE_NO_CONFIGURED_SERVERS, kind: 'item', fg: PALETTE.dim, dim: true });
175
+ rendered.push({ text: MCP_WORKSPACE_GROUP_ACTIONS, kind: 'group', bold: true });
52
176
  sawActionGroup = true;
53
177
  }
54
178
 
@@ -82,41 +206,47 @@ function selectedDetailLines(workspace: McpWorkspace, width: number): WorkspaceR
82
206
  if (workspace.mode === 'form') {
83
207
  const field = workspace.formFields[workspace.formIndex];
84
208
  lines.push(
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.',
209
+ workspace.editingServerName ? `Editing server ${workspace.editingServerName}` : 'Drafting an MCP server',
210
+ MCP_WORKSPACE_FORM_WRITE_CONFIRMATION,
87
211
  field ? `${field.label}: ${field.help}` : '',
88
- 'Project/global config locations are shown for review. This workspace does not write or reload MCP config.',
212
+ MCP_WORKSPACE_FORM_CONFIG_LOCATIONS,
89
213
  );
90
214
  } else if (workspace.mode === 'delete-confirm') {
91
215
  lines.push(
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.',
216
+ `Remove server: ${workspace.editingServerName ?? '(unknown)'}`,
217
+ MCP_WORKSPACE_REMOVE_HELP,
218
+ MCP_WORKSPACE_REMOVE_CANCEL_HELP,
219
+ );
220
+ } else if (workspace.mode === 'reload-confirm') {
221
+ lines.push(
222
+ MCP_WORKSPACE_RELOAD_TITLE,
223
+ MCP_WORKSPACE_RELOAD_HELP,
224
+ MCP_WORKSPACE_RELOAD_CANCEL_HELP,
95
225
  );
96
226
  } else {
97
227
  const selected = workspace.selectedRow;
98
- if (!selected) lines.push('No MCP rows available.');
228
+ if (!selected) lines.push(MCP_WORKSPACE_NO_ROWS);
99
229
  else if (selected.type === 'action') lines.push(selected.label, selected.detail);
100
230
  else {
101
231
  const server = selected.server;
102
232
  lines.push(
103
233
  server.name,
104
- `Connected: ${server.connected ? 'yes' : 'no'} Source: ${server.source} Schema: ${server.freshness}`,
105
- `Role: ${server.role} Trust: ${server.trustMode}`,
106
- `Command: ${server.command ? `${server.command}${server.args?.length ? ` ${server.args.join(' ')}` : ''}` : '(runtime only; no launch config found)'}`,
107
- `Allowed paths: ${server.allowedPaths.length > 0 ? server.allowedPaths.join(', ') : '(none)'}`,
108
- `Allowed hosts: ${server.allowedHosts.length > 0 ? server.allowedHosts.join(', ') : '(none)'}`,
234
+ `Connected: ${server.connected ? 'yes' : 'no'} Origin: ${serverOriginLabel(server.source)} Config: ${server.freshness}`,
235
+ `Role: ${server.role} Review policy: ${server.trustMode}`,
236
+ `Command: ${server.command ? `${server.command}${server.args?.length ? ` ${server.args.join(' ')}` : ''}` : MCP_WORKSPACE_RUNTIME_ONLY_COMMAND}`,
237
+ `Allowed paths: ${server.allowedPaths.length > 0 ? server.allowedPaths.join(', ') : MCP_WORKSPACE_NONE}`,
238
+ `Allowed hosts: ${server.allowedHosts.length > 0 ? server.allowedHosts.join(', ') : MCP_WORKSPACE_NONE}`,
109
239
  ...(server.quarantineReason ? [`Quarantine: ${server.quarantineReason}${server.quarantineDetail ? ` - ${server.quarantineDetail}` : ''}`] : []),
110
240
  );
111
241
  }
112
242
  }
113
243
 
114
- lines.push('', `Status: ${workspace.status}`);
244
+ lines.push('', `${MCP_WORKSPACE_STATUS_PREFIX} ${workspace.status}`);
115
245
  return lines.flatMap((text, index): WorkspaceRow[] => {
116
246
  if (text === '') return [{ text: '', dim: true }];
117
247
  return wrapText(text, Math.max(1, width)).map((wrapped, wrapIndex): WorkspaceRow => ({
118
248
  text: wrapped,
119
- fg: index === 0 ? PALETTE.title : text.startsWith('Status:') ? statusColor(workspace.status) : PALETTE.text,
249
+ fg: index === 0 ? PALETTE.title : text.startsWith(MCP_WORKSPACE_STATUS_PREFIX) ? statusColor(workspace.status) : PALETTE.text,
120
250
  bold: index === 0 && wrapIndex === 0,
121
251
  dim: text.length === 0,
122
252
  }));
@@ -129,7 +259,7 @@ function buildFormRows(workspace: McpWorkspace, width: number, height: number):
129
259
  const labelWidth = Math.min(24, Math.max(14, Math.floor(width * 0.24)));
130
260
  const valueWidth = Math.max(12, width - labelWidth - 20);
131
261
  rows.push({
132
- text: ` ${padDisplay('Field', labelWidth)} ${padDisplay('Value', valueWidth)} ${padDisplay('Edit', 10)}`,
262
+ text: ` ${padDisplay(MCP_WORKSPACE_FORM_HEADER_FIELD, labelWidth)} ${padDisplay(MCP_WORKSPACE_FORM_HEADER_VALUE, valueWidth)} ${padDisplay(MCP_WORKSPACE_FORM_HEADER_EDIT, 10)}`,
133
263
  fg: PALETTE.muted,
134
264
  bold: true,
135
265
  });
@@ -143,9 +273,9 @@ function buildFormRows(workspace: McpWorkspace, width: number, height: number):
143
273
  const marker = selected ? GLYPHS.navigation.selected : ' ';
144
274
  const value = field.id === 'save' || field.id === 'cancel'
145
275
  ? field.help
146
- : field.value.length > 0 ? field.value : '(empty)';
276
+ : field.value.length > 0 ? field.value : MCP_WORKSPACE_EMPTY_VALUE;
147
277
  rows.push({
148
- text: `${marker} ${padDisplay(field.label, labelWidth)} ${padDisplay(value, valueWidth)} ${padDisplay(field.editable ? 'text' : 'cycle/action', 12)}`,
278
+ text: `${marker} ${padDisplay(field.label, labelWidth)} ${padDisplay(value, valueWidth)} ${padDisplay(field.editable ? MCP_WORKSPACE_FORM_TEXT_EDIT : MCP_WORKSPACE_FORM_ACTION_EDIT, 12)}`,
149
279
  selected,
150
280
  fg: field.id === 'save' ? PALETTE.good : field.id === 'cancel' ? PALETTE.warn : field.editable ? PALETTE.text : PALETTE.info,
151
281
  bold: selected,
@@ -162,19 +292,15 @@ function buildToolRows(workspace: McpWorkspace, width: number, height: number):
162
292
  const toolWidth = Math.min(36, Math.max(18, Math.floor(width * 0.34)));
163
293
  const serverWidth = Math.min(24, Math.max(12, Math.floor(width * 0.20)));
164
294
  const descriptionWidth = Math.max(12, width - toolWidth - serverWidth - 8);
165
- const label = workspace.loadingTools
166
- ? 'Tools: loading...'
167
- : server
168
- ? `Tools for ${server}: ${tools.length}`
169
- : `Tools: ${tools.length}`;
295
+ const label = mcpWorkspaceToolsLabel(workspace.loadingTools, server, tools.length);
170
296
  const rows: WorkspaceRow[] = [
171
297
  { text: label, fg: PALETTE.subtitle, bold: true },
172
- { text: ` ${padDisplay('Tool', toolWidth)} ${padDisplay('Server', serverWidth)} ${padDisplay('Description', descriptionWidth)}`, fg: PALETTE.muted, bold: true },
298
+ { text: ` ${padDisplay(MCP_WORKSPACE_TOOL_HEADER_TOOL, toolWidth)} ${padDisplay(MCP_WORKSPACE_TOOL_HEADER_SERVER, serverWidth)} ${padDisplay(MCP_WORKSPACE_TOOL_HEADER_DESCRIPTION, descriptionWidth)}`, fg: PALETTE.muted, bold: true },
173
299
  ];
174
300
 
175
301
  if (tools.length === 0) {
176
302
  rows.push({
177
- text: workspace.loadingTools ? 'Loading tool list from connected MCP servers.' : 'No tools cached for the selected server. Press t to refresh.',
303
+ text: workspace.loadingTools ? MCP_WORKSPACE_TOOLS_LOADING_DETAIL : MCP_WORKSPACE_TOOLS_EMPTY,
178
304
  fg: PALETTE.muted,
179
305
  dim: true,
180
306
  });
@@ -194,7 +320,16 @@ function buildToolRows(workspace: McpWorkspace, width: number, height: number):
194
320
  function buildDeleteRows(workspace: McpWorkspace, height: number): WorkspaceRow[] {
195
321
  const rows: WorkspaceRow[] = [
196
322
  { text: `${GLYPHS.navigation.selected} Confirm remove ${workspace.editingServerName ?? '(unknown)'}`, selected: true, fg: PALETTE.bad, bold: true },
197
- { text: ' Cancel and return to MCP server browser', fg: PALETTE.muted },
323
+ { text: MCP_WORKSPACE_CONFIRM_CANCEL_ROW, fg: PALETTE.muted },
324
+ ];
325
+ while (rows.length < height) rows.push({ text: '', kind: 'empty' });
326
+ return rows.slice(0, height);
327
+ }
328
+
329
+ function buildReloadRows(height: number): WorkspaceRow[] {
330
+ const rows: WorkspaceRow[] = [
331
+ { text: `${GLYPHS.navigation.selected} ${MCP_WORKSPACE_CONFIRM_RELOAD}`, selected: true, fg: PALETTE.warn, bold: true },
332
+ { text: MCP_WORKSPACE_CONFIRM_CANCEL_ROW, fg: PALETTE.muted },
198
333
  ];
199
334
  while (rows.length < height) rows.push({ text: '', kind: 'empty' });
200
335
  return rows.slice(0, height);
@@ -203,31 +338,29 @@ function buildDeleteRows(workspace: McpWorkspace, height: number): WorkspaceRow[
203
338
  function buildControlRows(workspace: McpWorkspace, width: number, height: number): WorkspaceRow[] {
204
339
  if (workspace.mode === 'form') return buildFormRows(workspace, width, height);
205
340
  if (workspace.mode === 'delete-confirm') return buildDeleteRows(workspace, height);
341
+ if (workspace.mode === 'reload-confirm') return buildReloadRows(height);
206
342
  return buildToolRows(workspace, width, height);
207
343
  }
208
344
 
209
345
  function footerText(workspace: McpWorkspace): string {
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';
346
+ if (workspace.mode === 'form') return MCP_WORKSPACE_FOOTER_FORM;
347
+ if (workspace.mode === 'delete-confirm') return MCP_WORKSPACE_FOOTER_DELETE;
348
+ if (workspace.mode === 'reload-confirm') return MCP_WORKSPACE_FOOTER_RELOAD;
349
+ return MCP_WORKSPACE_FOOTER_BROWSE;
213
350
  }
214
351
 
215
352
  export function renderMcpWorkspace(workspace: McpWorkspace, width: number, height: number): Line[] {
216
353
  const metrics = getFullscreenWorkspaceMetrics({ width, height });
217
354
  const connected = workspace.servers.filter((server) => server.connected).length;
218
- const stateLabel = workspace.mode === 'browse' ? 'Browse' : workspace.mode === 'form' ? 'Command Preview' : 'Remove Guidance';
219
- const mainHeader = workspace.mode === 'form'
220
- ? 'MCP server command preview'
221
- : workspace.mode === 'delete-confirm'
222
- ? 'MCP remove command'
223
- : `Servers ${connected}/${workspace.servers.length} connected · Tools ${workspace.tools.length}`;
355
+ const stateLabel = mcpWorkspaceStateLabel(workspace.mode);
356
+ const mainHeader = mcpWorkspaceMainHeader(workspace.mode, connected, workspace.servers.length, workspace.tools.length);
224
357
 
225
358
  return renderFullscreenWorkspace({
226
359
  width,
227
360
  height,
228
- title: 'MCP Workspace / Servers',
361
+ title: MCP_WORKSPACE_TITLE,
229
362
  stateLabel,
230
- leftHeader: 'Servers',
363
+ leftHeader: MCP_WORKSPACE_LEFT_HEADER,
231
364
  mainHeader,
232
365
  leftRows: buildLeftRows(workspace, metrics.bodyRows),
233
366
  contextRows: selectedDetailLines(workspace, metrics.contextWidth),
@@ -28,6 +28,22 @@ const MODE_TITLES: Record<string, string> = {
28
28
  contextCap: 'Set Context Window',
29
29
  };
30
30
 
31
+ const CONTEXT_WINDOW_SOURCE_LABELS = {
32
+ provider_api: 'provider catalog',
33
+ configured_cap: 'configured limit',
34
+ fallback: 'default limit',
35
+ registry: 'model registry',
36
+ } as const satisfies Readonly<Record<string, string>>;
37
+
38
+ type ContextWindowSourceKey = keyof typeof CONTEXT_WINDOW_SOURCE_LABELS;
39
+
40
+ function formatContextWindowSource(value: string | undefined): string {
41
+ const normalized = (value ?? '').trim().toLowerCase();
42
+ if (!normalized) return 'configured limit';
43
+ if (normalized in CONTEXT_WINDOW_SOURCE_LABELS) return CONTEXT_WINDOW_SOURCE_LABELS[normalized as ContextWindowSourceKey];
44
+ return normalized.replace(/[_-]+/g, ' ').replace(/\s+/g, ' ');
45
+ }
46
+
31
47
  /**
32
48
  * Number of fixed chrome lines in the model-picker overlay (title + search + divider + detail×2 + footer).
33
49
  * Used by callers to compute maxVisible item rows.
@@ -42,6 +58,46 @@ function putRowText(line: Line, startX: number, maxWidth: number, text: string,
42
58
  putOverlayText(line, startX, maxWidth, text, { fg, bg, bold, dim });
43
59
  }
44
60
 
61
+ export function renderModelPickerPackageText(): string {
62
+ return [
63
+ ...Object.values(MODE_TITLES),
64
+ 'Search',
65
+ 'Filter: All',
66
+ 'Filter: Free',
67
+ 'Filter: Paid',
68
+ 'Filter: Sub',
69
+ 'Group: provider',
70
+ 'Group: family',
71
+ 'Group: pricingTier',
72
+ 'Group: qualityTier',
73
+ 'No models available',
74
+ 'No models match query',
75
+ 'No providers available',
76
+ 'No providers match query',
77
+ 'Provider:',
78
+ 'Context:',
79
+ 'Reasoning:',
80
+ 'Vision:',
81
+ 'Tools:',
82
+ 'Select a provider to browse its models',
83
+ 'Context window (tokens):',
84
+ 'Leave blank to use default',
85
+ 'Model:',
86
+ 'provider catalog',
87
+ 'configured limit',
88
+ 'default limit',
89
+ 'model registry',
90
+ 'configured via environment',
91
+ 'configured via secret reference',
92
+ 'configured via subscription',
93
+ 'configured anonymously',
94
+ '[Up/Down] [Enter] [/] Search [Space] Ctx [Esc] [Tab] Filter [G] Group',
95
+ '[Enter] Confirm [Esc] Cancel',
96
+ '[Up/Down] Nav [Enter] Select [Esc] Cancel',
97
+ ...Object.entries(EFFORT_DESCRIPTIONS).flatMap(([level, description]) => [level, description]),
98
+ ].join('\n');
99
+ }
100
+
45
101
  /**
46
102
  * Render the model picker modal as Line[] for overlay in the viewport.
47
103
  * Handles model, provider, and effort modes.
@@ -240,7 +296,6 @@ export function renderModelPickerOverlay(
240
296
  if (caps.reasoning) capParts.push('Reasoning: \u2713');
241
297
  if (caps.multimodal) capParts.push('Vision: \u2713');
242
298
  if (caps.toolCalling) capParts.push('Tools: \u2713');
243
- if (caps.codeEditing) capParts.push('Code: \u2713');
244
299
  const capText = capParts.join(' ');
245
300
  const capLine = createOverlayContentLine(width, layout, borderFg, DEFAULT_OVERLAY_PALETTE.bodyBg);
246
301
  putRowText(capLine, layout.margin + 2, contentW, fitDisplay(truncateDisplay(capText, contentW), contentW), '244');
@@ -339,7 +394,7 @@ export function renderModelPickerOverlay(
339
394
  const capModel = picker.contextCapPendingModel;
340
395
  const modelName = capModel ? capModel.displayName : 'unknown';
341
396
  const currentCtx = capModel ? fmtContext(capModel.contextWindow) : '?';
342
- const provenance = capModel?.contextWindowProvenance ?? 'configured_cap';
397
+ const sourceLabel = formatContextWindowSource(capModel?.contextWindowProvenance);
343
398
 
344
399
  const promptLabel = 'Context window (tokens):';
345
400
  const cursorChar = OVERLAY_GLYPHS.cursor;
@@ -350,7 +405,7 @@ export function renderModelPickerOverlay(
350
405
 
351
406
  lines.push(createOverlayContentLine(width, layout, borderFg, DEFAULT_OVERLAY_PALETTE.bodyBg));
352
407
 
353
- const hintText = `Leave blank to use default (current: ${currentCtx}, source: ${provenance})`;
408
+ const hintText = `Leave blank to use default (current: ${currentCtx}, from ${sourceLabel})`;
354
409
  const hintTrunc = getDisplayWidth(hintText) > contentW
355
410
  ? hintText.slice(0, contentW - 3) + '...'
356
411
  : hintText;
@@ -33,6 +33,31 @@ const PALETTE = {
33
33
  const renderCache = new WeakMap<ModelPickerModal, { key: string; lines: Line[] }>();
34
34
  const objectIds = new WeakMap<object, number>();
35
35
  let nextObjectId = 1;
36
+ const MODEL_WORKSPACE_TITLE = ' Model Workspace / Providers And Models ';
37
+ const MODEL_WORKSPACE_TARGETS_TITLE = 'Targets';
38
+ const MODEL_WORKSPACE_MODE_LABELS = {
39
+ provider: 'Provider list',
40
+ model: 'Model list',
41
+ effort: 'Reasoning effort',
42
+ contextCap: 'Context cap',
43
+ } as const;
44
+ const MODEL_WORKSPACE_TABLE_HEADERS = {
45
+ provider: 'Provider Configuration Catalog',
46
+ effort: 'Reasoning effort Meaning',
47
+ contextCap: 'Context cap input',
48
+ modelKey: 'Model key',
49
+ displayName: 'Display name',
50
+ providerColumn: 'Provider',
51
+ context: 'Context',
52
+ tier: 'Tier',
53
+ caps: 'Caps',
54
+ } as const;
55
+ const MODEL_WORKSPACE_REASONING_EFFORT_DETAIL = 'Reasoning effort applies to the main chat model. Select the default effort for this model.';
56
+ const MODEL_WORKSPACE_CONTEXT_CAP_DETAIL = 'Context cap overrides the detected local-model context window for this selection.';
57
+ const MODEL_WORKSPACE_CONTEXT_CAP_INPUT_HELP = 'Type digits to set a cap. Enter confirms; Esc returns to the model list.';
58
+ const MODEL_WORKSPACE_FOOTER_SEARCH_ACTIVE = 'Typing filters search; Esc clears search';
59
+ const MODEL_WORKSPACE_FOOTER_SEARCH_INACTIVE = '/ search';
60
+ const MODEL_WORKSPACE_FOOTER_CONTROLS = 'Up/Down navigate • Left/Right pane • Enter select • <search> • Tab price • C caps • A available • B benchmark • G group • Esc close';
36
61
 
37
62
  function clamp(value: number, min: number, max: number): number {
38
63
  return Math.max(min, Math.min(max, value));
@@ -164,6 +189,65 @@ function targetLabelFor(target: string): string {
164
189
  return 'Main Chat';
165
190
  }
166
191
 
192
+ function providerSelectionDetail(targetLabel: string): string {
193
+ return `Provider selection: choose a provider, then choose a model for ${targetLabel}.`;
194
+ }
195
+
196
+ function modelSelectionDetail(targetLabel: string): string {
197
+ return `Model selection: choose the model to store for ${targetLabel}. Use filters to narrow large catalogs.`;
198
+ }
199
+
200
+ function modelWorkspaceFilterText(
201
+ query: string,
202
+ price: string,
203
+ capability: string,
204
+ group: string,
205
+ availableOnly: string,
206
+ ): string {
207
+ return `Search: ${query} | Price: ${price} | Capability: ${capability} | Group: ${group} | Available only: ${availableOnly}`;
208
+ }
209
+
210
+ function modelWorkspaceFooterControls(searchHint: string): string {
211
+ return MODEL_WORKSPACE_FOOTER_CONTROLS.replace('<search>', searchHint);
212
+ }
213
+
214
+ export function renderModelWorkspacePackageText(): string {
215
+ return [
216
+ MODEL_WORKSPACE_TITLE.trim(),
217
+ MODEL_WORKSPACE_TARGETS_TITLE,
218
+ MODEL_WORKSPACE_MODE_LABELS.provider,
219
+ MODEL_WORKSPACE_MODE_LABELS.model,
220
+ MODEL_WORKSPACE_MODE_LABELS.effort,
221
+ MODEL_WORKSPACE_MODE_LABELS.contextCap,
222
+ 'Target: <target> (<state>)',
223
+ 'Current: <route>',
224
+ providerSelectionDetail('<target>'),
225
+ 'Selected provider: <provider>',
226
+ modelSelectionDetail('<target>'),
227
+ 'Selected: <model> | <display> | context <context> | <capabilities>',
228
+ MODEL_WORKSPACE_REASONING_EFFORT_DETAIL,
229
+ MODEL_WORKSPACE_CONTEXT_CAP_DETAIL,
230
+ modelWorkspaceFilterText('<query>', '<price>', '<capability>', '<group>', '<available-only>'),
231
+ MODEL_WORKSPACE_TABLE_HEADERS.provider,
232
+ MODEL_WORKSPACE_TABLE_HEADERS.effort,
233
+ MODEL_WORKSPACE_TABLE_HEADERS.contextCap,
234
+ MODEL_WORKSPACE_TABLE_HEADERS.modelKey,
235
+ MODEL_WORKSPACE_TABLE_HEADERS.displayName,
236
+ MODEL_WORKSPACE_TABLE_HEADERS.providerColumn,
237
+ MODEL_WORKSPACE_TABLE_HEADERS.context,
238
+ MODEL_WORKSPACE_TABLE_HEADERS.tier,
239
+ MODEL_WORKSPACE_TABLE_HEADERS.caps,
240
+ 'Model: <model>',
241
+ 'Detected context: <context>',
242
+ 'Override: <cap>',
243
+ MODEL_WORKSPACE_CONTEXT_CAP_INPUT_HELP,
244
+ 'Focus targets',
245
+ 'Focus list',
246
+ modelWorkspaceFooterControls(MODEL_WORKSPACE_FOOTER_SEARCH_ACTIVE),
247
+ modelWorkspaceFooterControls(MODEL_WORKSPACE_FOOTER_SEARCH_INACTIVE),
248
+ ].join('\n');
249
+ }
250
+
167
251
  function detailLines(picker: ModelPickerModal, width: number): string[] {
168
252
  const target = picker.getSelectedTargetInfo();
169
253
  const targetLabel = target?.label ?? targetLabelFor(picker.target);
@@ -174,26 +258,25 @@ function detailLines(picker: ModelPickerModal, width: number): string[] {
174
258
  if (target) lines.push(`Current: ${targetSummary(target)}`);
175
259
  if (picker.mode === 'provider') {
176
260
  const provider = picker.getFilteredProviders()[picker.selectedIndex] ?? '';
177
- lines.push(`Provider selection: choose a provider, then choose a model for ${targetLabel}.`);
261
+ lines.push(providerSelectionDetail(targetLabel));
178
262
  if (provider) lines.push(`Selected provider: ${provider}`);
179
263
  } else if (picker.mode === 'model') {
180
- lines.push(`Model selection: choose the model to store for ${targetLabel}. Use filters to narrow large catalogs.`);
264
+ lines.push(modelSelectionDetail(targetLabel));
181
265
  if (selected) {
182
266
  const caps = selected.capabilities ?? {};
183
267
  const capText = [
184
268
  caps.reasoning ? 'reasoning' : '',
185
269
  caps.multimodal ? 'vision' : '',
186
270
  caps.toolCalling ? 'tools' : '',
187
- caps.codeEditing ? 'code' : '',
188
271
  ].filter(Boolean).join(', ') || 'standard';
189
272
  lines.push(`Selected: ${modelKey(selected)} | ${selected.displayName} | context ${formatContext(selected.contextWindow)} | ${capText}`);
190
273
  }
191
274
  } else if (picker.mode === 'effort') {
192
- lines.push(`Reasoning effort applies to the main chat model. Select the default effort for this model.`);
275
+ lines.push(MODEL_WORKSPACE_REASONING_EFFORT_DETAIL);
193
276
  } else {
194
- lines.push(`Context cap overrides the detected local-model context window for this selection.`);
277
+ lines.push(MODEL_WORKSPACE_CONTEXT_CAP_DETAIL);
195
278
  }
196
- const filterText = `Search: ${picker.query || '(none)'} | Price: ${picker.categoryFilter} | Capability: ${picker.capabilityFilter} | Group: ${picker.groupBy} | Available only: ${picker.availableOnly ? 'yes' : 'no'}`;
279
+ const filterText = modelWorkspaceFilterText(picker.query || '(none)', picker.categoryFilter, picker.capabilityFilter, picker.groupBy, picker.availableOnly ? 'yes' : 'no');
197
280
  lines.push(filterText);
198
281
  return lines.flatMap((line) => wrapText(line, Math.max(1, width)));
199
282
  }
@@ -261,7 +344,6 @@ function renderModelRows(picker: ModelPickerModal, lines: Line[], rows: number,
261
344
  caps.reasoning ? 'R' : '-',
262
345
  caps.multimodal ? 'V' : '-',
263
346
  caps.toolCalling ? 'T' : '-',
264
- caps.codeEditing ? 'C' : '-',
265
347
  ].join('');
266
348
  let x = startX + 1;
267
349
  writeText(line, x, 2, padDisplay(marker, 2), { fg: PALETTE.text, bg, bold: selected }); x += 2;
@@ -295,7 +377,7 @@ function renderContextCapRows(picker: ModelPickerModal, lines: Line[], rows: num
295
377
  `Model: ${model ? modelKey(model) : '(none)'}`,
296
378
  `Detected context: ${formatContext(model?.contextWindow)}`,
297
379
  `Override: ${input}`,
298
- 'Type digits to set a cap. Enter confirms; Esc returns to the model list.',
380
+ MODEL_WORKSPACE_CONTEXT_CAP_INPUT_HELP,
299
381
  ];
300
382
  for (let row = 0; row < Math.min(rows, copy.length); row += 1) {
301
383
  const line = lines[row]!;
@@ -308,15 +390,15 @@ function writeTableHeader(line: Line, picker: ModelPickerModal, startX: number,
308
390
  fillRange(line, startX, startX + width - 1, PALETTE.footerBg);
309
391
  const style = { fg: PALETTE.muted, bg: PALETTE.footerBg, bold: true };
310
392
  if (picker.mode === 'provider') {
311
- writeText(line, startX + 1, width - 2, 'Provider Configuration Catalog', style);
393
+ writeText(line, startX + 1, width - 2, MODEL_WORKSPACE_TABLE_HEADERS.provider, style);
312
394
  return;
313
395
  }
314
396
  if (picker.mode === 'effort') {
315
- writeText(line, startX + 1, width - 2, 'Reasoning effort Meaning', style);
397
+ writeText(line, startX + 1, width - 2, MODEL_WORKSPACE_TABLE_HEADERS.effort, style);
316
398
  return;
317
399
  }
318
400
  if (picker.mode === 'contextCap') {
319
- writeText(line, startX + 1, width - 2, 'Context cap input', style);
401
+ writeText(line, startX + 1, width - 2, MODEL_WORKSPACE_TABLE_HEADERS.contextCap, style);
320
402
  return;
321
403
  }
322
404
  const providerW = clamp(Math.floor(width * 0.14), 10, 18);
@@ -326,12 +408,12 @@ function writeTableHeader(line: Line, picker: ModelPickerModal, startX: number,
326
408
  const nameW = clamp(Math.floor(width * 0.28), 16, 36);
327
409
  const keyW = Math.max(10, width - providerW - ctxW - tierW - capsW - nameW - 10);
328
410
  let x = startX + 3;
329
- writeText(line, x, keyW, padDisplay('Model key', keyW), style); x += keyW + 1;
330
- writeText(line, x, nameW, padDisplay('Display name', nameW), style); x += nameW + 1;
331
- writeText(line, x, providerW, padDisplay('Provider', providerW), style); x += providerW + 1;
332
- writeText(line, x, ctxW, padDisplay('Context', ctxW), style); x += ctxW + 1;
333
- writeText(line, x, tierW, padDisplay('Tier', tierW), style); x += tierW + 1;
334
- writeText(line, x, capsW, padDisplay('Caps', capsW), style);
411
+ writeText(line, x, keyW, padDisplay(MODEL_WORKSPACE_TABLE_HEADERS.modelKey, keyW), style); x += keyW + 1;
412
+ writeText(line, x, nameW, padDisplay(MODEL_WORKSPACE_TABLE_HEADERS.displayName, nameW), style); x += nameW + 1;
413
+ writeText(line, x, providerW, padDisplay(MODEL_WORKSPACE_TABLE_HEADERS.providerColumn, providerW), style); x += providerW + 1;
414
+ writeText(line, x, ctxW, padDisplay(MODEL_WORKSPACE_TABLE_HEADERS.context, ctxW), style); x += ctxW + 1;
415
+ writeText(line, x, tierW, padDisplay(MODEL_WORKSPACE_TABLE_HEADERS.tier, tierW), style); x += tierW + 1;
416
+ writeText(line, x, capsW, padDisplay(MODEL_WORKSPACE_TABLE_HEADERS.caps, capsW), style);
335
417
  }
336
418
 
337
419
  export function renderModelWorkspace(picker: ModelPickerModal, width: number, viewportHeight: number): Line[] {
@@ -347,13 +429,13 @@ export function renderModelWorkspace(picker: ModelPickerModal, width: number, vi
347
429
  const contentW = Math.max(1, safeWidth - contentX - 1);
348
430
 
349
431
  const top = borderLine(safeWidth, GLYPHS.frame.topLeft, GLYPHS.frame.horizontal, GLYPHS.frame.topRight);
350
- writeText(top, 2, safeWidth - 4, ' Model Workspace / Providers And Models ', { fg: PALETTE.title, bold: true });
432
+ writeText(top, 2, safeWidth - 4, MODEL_WORKSPACE_TITLE, { fg: PALETTE.title, bold: true });
351
433
  lines.push(top);
352
434
 
353
435
  const header = contentLine(safeWidth, PALETTE.footerBg);
354
- writeText(header, 2, targetW - 2, 'Targets', { fg: PALETTE.subtitle, bold: true, bg: PALETTE.footerBg });
436
+ writeText(header, 2, targetW - 2, MODEL_WORKSPACE_TARGETS_TITLE, { fg: PALETTE.subtitle, bold: true, bg: PALETTE.footerBg });
355
437
  drawVertical(header, targetW, PALETTE.footerBg);
356
- const modeLabel = picker.mode === 'provider' ? 'Provider list' : picker.mode === 'model' ? 'Model list' : picker.mode === 'effort' ? 'Reasoning effort' : 'Context cap';
438
+ const modeLabel = MODEL_WORKSPACE_MODE_LABELS[picker.mode];
357
439
  writeText(header, contentX + 1, contentW - 2, `${modeLabel} • ${picker.getItemCount()} item(s)`, { fg: PALETTE.subtitle, bold: true, bg: PALETTE.footerBg });
358
440
  lines.push(header);
359
441
 
@@ -402,8 +484,8 @@ export function renderModelWorkspace(picker: ModelPickerModal, width: number, vi
402
484
 
403
485
  const footer = contentLine(safeWidth, PALETTE.footerBg);
404
486
  const targetHint = picker.focusPane === 'targets' ? 'Focus targets' : 'Focus list';
405
- const searchHint = picker.searchFocused ? 'Typing filters search; Esc clears search' : '/ search';
406
- const hints = `${targetHint} • Up/Down navigate • Left/Right pane • Enter select • ${searchHint} • Tab price • C caps • A available • B benchmark • G group • Esc close`;
487
+ const searchHint = picker.searchFocused ? MODEL_WORKSPACE_FOOTER_SEARCH_ACTIVE : MODEL_WORKSPACE_FOOTER_SEARCH_INACTIVE;
488
+ const hints = `${targetHint} • ${modelWorkspaceFooterControls(searchHint)}`;
407
489
  writeText(footer, 2, safeWidth - 4, hints, { fg: PALETTE.muted, bg: PALETTE.footerBg });
408
490
  lines.push(footer);
409
491
  lines.push(borderLine(safeWidth, GLYPHS.frame.bottomLeft, GLYPHS.frame.horizontal, GLYPHS.frame.bottomRight));