@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
@@ -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,47 +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 ? `Editing server: ${workspace.editingServerName}` : 'Drafting an MCP server',
86
- 'Workspace writes require explicit confirmation. Type yes on the Confirm field, then save from here.',
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. The workspace dispatches confirmed MCP changes through the TUI command router.',
212
+ MCP_WORKSPACE_FORM_CONFIG_LOCATIONS,
89
213
  );
90
214
  } else if (workspace.mode === 'delete-confirm') {
91
215
  lines.push(
92
216
  `Remove server: ${workspace.editingServerName ?? '(unknown)'}`,
93
- 'Press Enter or y to remove through the TUI command router.',
94
- 'Press n or Esc to return without changing config.',
217
+ MCP_WORKSPACE_REMOVE_HELP,
218
+ MCP_WORKSPACE_REMOVE_CANCEL_HELP,
95
219
  );
96
220
  } else if (workspace.mode === 'reload-confirm') {
97
221
  lines.push(
98
- 'Reload MCP runtime',
99
- 'Press Enter or y to reload MCP runtime from the current config through the TUI command router.',
100
- 'Press n or Esc to return without reloading.',
222
+ MCP_WORKSPACE_RELOAD_TITLE,
223
+ MCP_WORKSPACE_RELOAD_HELP,
224
+ MCP_WORKSPACE_RELOAD_CANCEL_HELP,
101
225
  );
102
226
  } else {
103
227
  const selected = workspace.selectedRow;
104
- if (!selected) lines.push('No MCP rows available.');
228
+ if (!selected) lines.push(MCP_WORKSPACE_NO_ROWS);
105
229
  else if (selected.type === 'action') lines.push(selected.label, selected.detail);
106
230
  else {
107
231
  const server = selected.server;
108
232
  lines.push(
109
233
  server.name,
110
- `Connected: ${server.connected ? 'yes' : 'no'} Source: ${server.source} Schema: ${server.freshness}`,
111
- `Role: ${server.role} Trust: ${server.trustMode}`,
112
- `Command: ${server.command ? `${server.command}${server.args?.length ? ` ${server.args.join(' ')}` : ''}` : '(runtime only; no launch config found)'}`,
113
- `Allowed paths: ${server.allowedPaths.length > 0 ? server.allowedPaths.join(', ') : '(none)'}`,
114
- `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}`,
115
239
  ...(server.quarantineReason ? [`Quarantine: ${server.quarantineReason}${server.quarantineDetail ? ` - ${server.quarantineDetail}` : ''}`] : []),
116
240
  );
117
241
  }
118
242
  }
119
243
 
120
- lines.push('', `Status: ${workspace.status}`);
244
+ lines.push('', `${MCP_WORKSPACE_STATUS_PREFIX} ${workspace.status}`);
121
245
  return lines.flatMap((text, index): WorkspaceRow[] => {
122
246
  if (text === '') return [{ text: '', dim: true }];
123
247
  return wrapText(text, Math.max(1, width)).map((wrapped, wrapIndex): WorkspaceRow => ({
124
248
  text: wrapped,
125
- 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,
126
250
  bold: index === 0 && wrapIndex === 0,
127
251
  dim: text.length === 0,
128
252
  }));
@@ -135,7 +259,7 @@ function buildFormRows(workspace: McpWorkspace, width: number, height: number):
135
259
  const labelWidth = Math.min(24, Math.max(14, Math.floor(width * 0.24)));
136
260
  const valueWidth = Math.max(12, width - labelWidth - 20);
137
261
  rows.push({
138
- 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)}`,
139
263
  fg: PALETTE.muted,
140
264
  bold: true,
141
265
  });
@@ -149,9 +273,9 @@ function buildFormRows(workspace: McpWorkspace, width: number, height: number):
149
273
  const marker = selected ? GLYPHS.navigation.selected : ' ';
150
274
  const value = field.id === 'save' || field.id === 'cancel'
151
275
  ? field.help
152
- : field.value.length > 0 ? field.value : '(empty)';
276
+ : field.value.length > 0 ? field.value : MCP_WORKSPACE_EMPTY_VALUE;
153
277
  rows.push({
154
- 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)}`,
155
279
  selected,
156
280
  fg: field.id === 'save' ? PALETTE.good : field.id === 'cancel' ? PALETTE.warn : field.editable ? PALETTE.text : PALETTE.info,
157
281
  bold: selected,
@@ -168,19 +292,15 @@ function buildToolRows(workspace: McpWorkspace, width: number, height: number):
168
292
  const toolWidth = Math.min(36, Math.max(18, Math.floor(width * 0.34)));
169
293
  const serverWidth = Math.min(24, Math.max(12, Math.floor(width * 0.20)));
170
294
  const descriptionWidth = Math.max(12, width - toolWidth - serverWidth - 8);
171
- const label = workspace.loadingTools
172
- ? 'Tools: loading...'
173
- : server
174
- ? `Tools for ${server}: ${tools.length}`
175
- : `Tools: ${tools.length}`;
295
+ const label = mcpWorkspaceToolsLabel(workspace.loadingTools, server, tools.length);
176
296
  const rows: WorkspaceRow[] = [
177
297
  { text: label, fg: PALETTE.subtitle, bold: true },
178
- { 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 },
179
299
  ];
180
300
 
181
301
  if (tools.length === 0) {
182
302
  rows.push({
183
- 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,
184
304
  fg: PALETTE.muted,
185
305
  dim: true,
186
306
  });
@@ -200,7 +320,7 @@ function buildToolRows(workspace: McpWorkspace, width: number, height: number):
200
320
  function buildDeleteRows(workspace: McpWorkspace, height: number): WorkspaceRow[] {
201
321
  const rows: WorkspaceRow[] = [
202
322
  { text: `${GLYPHS.navigation.selected} Confirm remove ${workspace.editingServerName ?? '(unknown)'}`, selected: true, fg: PALETTE.bad, bold: true },
203
- { text: ' Cancel and return to MCP server browser', fg: PALETTE.muted },
323
+ { text: MCP_WORKSPACE_CONFIRM_CANCEL_ROW, fg: PALETTE.muted },
204
324
  ];
205
325
  while (rows.length < height) rows.push({ text: '', kind: 'empty' });
206
326
  return rows.slice(0, height);
@@ -208,8 +328,8 @@ function buildDeleteRows(workspace: McpWorkspace, height: number): WorkspaceRow[
208
328
 
209
329
  function buildReloadRows(height: number): WorkspaceRow[] {
210
330
  const rows: WorkspaceRow[] = [
211
- { text: `${GLYPHS.navigation.selected} Confirm MCP runtime reload`, selected: true, fg: PALETTE.warn, bold: true },
212
- { text: ' Cancel and return to MCP server browser', fg: PALETTE.muted },
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 },
213
333
  ];
214
334
  while (rows.length < height) rows.push({ text: '', kind: 'empty' });
215
335
  return rows.slice(0, height);
@@ -223,30 +343,24 @@ function buildControlRows(workspace: McpWorkspace, width: number, height: number
223
343
  }
224
344
 
225
345
  function footerText(workspace: McpWorkspace): string {
226
- if (workspace.mode === 'form') return 'Focus MCP server form · Up/Down field · Left/Right cycle · Type edit · Enter save/cancel · Esc back';
227
- if (workspace.mode === 'delete-confirm') return 'Focus remove confirmation · Enter/y remove · n/Esc cancel';
228
- if (workspace.mode === 'reload-confirm') return 'Focus reload confirmation · Enter/y reload · n/Esc cancel';
229
- return 'Focus MCP workspace · Up/Down choose · Enter view/action · a add · d remove · r reload · 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;
230
350
  }
231
351
 
232
352
  export function renderMcpWorkspace(workspace: McpWorkspace, width: number, height: number): Line[] {
233
353
  const metrics = getFullscreenWorkspaceMetrics({ width, height });
234
354
  const connected = workspace.servers.filter((server) => server.connected).length;
235
- const stateLabel = workspace.mode === 'browse' ? 'Browse' : workspace.mode === 'form' ? 'Server Form' : workspace.mode === 'delete-confirm' ? 'Remove Confirm' : 'Reload Confirm';
236
- const mainHeader = workspace.mode === 'form'
237
- ? 'MCP server form'
238
- : workspace.mode === 'delete-confirm'
239
- ? 'MCP remove confirmation'
240
- : workspace.mode === 'reload-confirm'
241
- ? 'MCP reload confirmation'
242
- : `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);
243
357
 
244
358
  return renderFullscreenWorkspace({
245
359
  width,
246
360
  height,
247
- title: 'MCP Workspace / Servers',
361
+ title: MCP_WORKSPACE_TITLE,
248
362
  stateLabel,
249
- leftHeader: 'Servers',
363
+ leftHeader: MCP_WORKSPACE_LEFT_HEADER,
250
364
  mainHeader,
251
365
  leftRows: buildLeftRows(workspace, metrics.bodyRows),
252
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.
@@ -338,7 +394,7 @@ export function renderModelPickerOverlay(
338
394
  const capModel = picker.contextCapPendingModel;
339
395
  const modelName = capModel ? capModel.displayName : 'unknown';
340
396
  const currentCtx = capModel ? fmtContext(capModel.contextWindow) : '?';
341
- const provenance = capModel?.contextWindowProvenance ?? 'configured_cap';
397
+ const sourceLabel = formatContextWindowSource(capModel?.contextWindowProvenance);
342
398
 
343
399
  const promptLabel = 'Context window (tokens):';
344
400
  const cursorChar = OVERLAY_GLYPHS.cursor;
@@ -349,7 +405,7 @@ export function renderModelPickerOverlay(
349
405
 
350
406
  lines.push(createOverlayContentLine(width, layout, borderFg, DEFAULT_OVERLAY_PALETTE.bodyBg));
351
407
 
352
- const hintText = `Leave blank to use default (current: ${currentCtx}, source: ${provenance})`;
408
+ const hintText = `Leave blank to use default (current: ${currentCtx}, from ${sourceLabel})`;
353
409
  const hintTrunc = getDisplayWidth(hintText) > contentW
354
410
  ? hintText.slice(0, contentW - 3) + '...'
355
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,10 +258,10 @@ 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 = [
@@ -188,11 +272,11 @@ function detailLines(picker: ModelPickerModal, width: number): string[] {
188
272
  lines.push(`Selected: ${modelKey(selected)} | ${selected.displayName} | context ${formatContext(selected.contextWindow)} | ${capText}`);
189
273
  }
190
274
  } else if (picker.mode === 'effort') {
191
- 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);
192
276
  } else {
193
- lines.push(`Context cap overrides the detected local-model context window for this selection.`);
277
+ lines.push(MODEL_WORKSPACE_CONTEXT_CAP_DETAIL);
194
278
  }
195
- 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');
196
280
  lines.push(filterText);
197
281
  return lines.flatMap((line) => wrapText(line, Math.max(1, width)));
198
282
  }
@@ -293,7 +377,7 @@ function renderContextCapRows(picker: ModelPickerModal, lines: Line[], rows: num
293
377
  `Model: ${model ? modelKey(model) : '(none)'}`,
294
378
  `Detected context: ${formatContext(model?.contextWindow)}`,
295
379
  `Override: ${input}`,
296
- 'Type digits to set a cap. Enter confirms; Esc returns to the model list.',
380
+ MODEL_WORKSPACE_CONTEXT_CAP_INPUT_HELP,
297
381
  ];
298
382
  for (let row = 0; row < Math.min(rows, copy.length); row += 1) {
299
383
  const line = lines[row]!;
@@ -306,15 +390,15 @@ function writeTableHeader(line: Line, picker: ModelPickerModal, startX: number,
306
390
  fillRange(line, startX, startX + width - 1, PALETTE.footerBg);
307
391
  const style = { fg: PALETTE.muted, bg: PALETTE.footerBg, bold: true };
308
392
  if (picker.mode === 'provider') {
309
- writeText(line, startX + 1, width - 2, 'Provider Configuration Catalog', style);
393
+ writeText(line, startX + 1, width - 2, MODEL_WORKSPACE_TABLE_HEADERS.provider, style);
310
394
  return;
311
395
  }
312
396
  if (picker.mode === 'effort') {
313
- writeText(line, startX + 1, width - 2, 'Reasoning effort Meaning', style);
397
+ writeText(line, startX + 1, width - 2, MODEL_WORKSPACE_TABLE_HEADERS.effort, style);
314
398
  return;
315
399
  }
316
400
  if (picker.mode === 'contextCap') {
317
- writeText(line, startX + 1, width - 2, 'Context cap input', style);
401
+ writeText(line, startX + 1, width - 2, MODEL_WORKSPACE_TABLE_HEADERS.contextCap, style);
318
402
  return;
319
403
  }
320
404
  const providerW = clamp(Math.floor(width * 0.14), 10, 18);
@@ -324,12 +408,12 @@ function writeTableHeader(line: Line, picker: ModelPickerModal, startX: number,
324
408
  const nameW = clamp(Math.floor(width * 0.28), 16, 36);
325
409
  const keyW = Math.max(10, width - providerW - ctxW - tierW - capsW - nameW - 10);
326
410
  let x = startX + 3;
327
- writeText(line, x, keyW, padDisplay('Model key', keyW), style); x += keyW + 1;
328
- writeText(line, x, nameW, padDisplay('Display name', nameW), style); x += nameW + 1;
329
- writeText(line, x, providerW, padDisplay('Provider', providerW), style); x += providerW + 1;
330
- writeText(line, x, ctxW, padDisplay('Context', ctxW), style); x += ctxW + 1;
331
- writeText(line, x, tierW, padDisplay('Tier', tierW), style); x += tierW + 1;
332
- 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);
333
417
  }
334
418
 
335
419
  export function renderModelWorkspace(picker: ModelPickerModal, width: number, viewportHeight: number): Line[] {
@@ -345,13 +429,13 @@ export function renderModelWorkspace(picker: ModelPickerModal, width: number, vi
345
429
  const contentW = Math.max(1, safeWidth - contentX - 1);
346
430
 
347
431
  const top = borderLine(safeWidth, GLYPHS.frame.topLeft, GLYPHS.frame.horizontal, GLYPHS.frame.topRight);
348
- 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 });
349
433
  lines.push(top);
350
434
 
351
435
  const header = contentLine(safeWidth, PALETTE.footerBg);
352
- 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 });
353
437
  drawVertical(header, targetW, PALETTE.footerBg);
354
- 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];
355
439
  writeText(header, contentX + 1, contentW - 2, `${modeLabel} • ${picker.getItemCount()} item(s)`, { fg: PALETTE.subtitle, bold: true, bg: PALETTE.footerBg });
356
440
  lines.push(header);
357
441
 
@@ -400,8 +484,8 @@ export function renderModelWorkspace(picker: ModelPickerModal, width: number, vi
400
484
 
401
485
  const footer = contentLine(safeWidth, PALETTE.footerBg);
402
486
  const targetHint = picker.focusPane === 'targets' ? 'Focus targets' : 'Focus list';
403
- const searchHint = picker.searchFocused ? 'Typing filters search; Esc clears search' : '/ search';
404
- 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)}`;
405
489
  writeText(footer, 2, safeWidth - 4, hints, { fg: PALETTE.muted, bg: PALETTE.footerBg });
406
490
  lines.push(footer);
407
491
  lines.push(borderLine(safeWidth, GLYPHS.frame.bottomLeft, GLYPHS.frame.horizontal, GLYPHS.frame.bottomRight));