@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
@@ -9,6 +9,10 @@ import { BUILTIN_SECRET_PROVIDER_SOURCES, describeSecretRef, isSecretRefInput, r
9
9
  import { requireBookmarkManager, requireProviderApi, requireSecretsManager } from './runtime-services.ts';
10
10
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
11
11
  import { requireYesFlag, stripYesFlag } from './confirmation.ts';
12
+ import {
13
+ formatAgentMediaGenerationResult,
14
+ generateAgentMedia,
15
+ } from '../../agent/media-generation.ts';
12
16
 
13
17
  function isGoodVibesSecretRefInput(value: string): boolean {
14
18
  const normalized = value.trim();
@@ -23,7 +27,7 @@ function isMalformedGoodVibesSecretRefInput(value: string): boolean {
23
27
  function toggleBlocks(typeFilter: string, collapsed: boolean, ctx: CommandContext): void {
24
28
  const VALID_TYPES = ['all', 'thinking', 'tool', 'code'] as const;
25
29
  if (!VALID_TYPES.includes(typeFilter as typeof VALID_TYPES[number])) {
26
- ctx.print(`Unknown type: ${typeFilter}\nValid types: ${VALID_TYPES.join(', ')}`);
30
+ ctx.print(`Unknown type ${typeFilter}\nValid types ${VALID_TYPES.join(', ')}`);
27
31
  return;
28
32
  }
29
33
  const blockRegistry = ctx.session.conversationManager.getBlockRegistry();
@@ -49,6 +53,60 @@ function toggleBlocks(typeFilter: string, collapsed: boolean, ctx: CommandContex
49
53
  ctx.renderRequest();
50
54
  }
51
55
 
56
+ interface MediaGenerateArgs {
57
+ readonly prompt: string;
58
+ readonly providerId?: string;
59
+ readonly modelId?: string;
60
+ readonly outputMimeType?: string;
61
+ readonly yes: boolean;
62
+ readonly errors: readonly string[];
63
+ }
64
+
65
+ function readFlagValue(args: readonly string[], index: number, flag: string, errors: string[]): string | null {
66
+ const value = args[index + 1];
67
+ if (!value || value.startsWith('--')) {
68
+ errors.push(`${flag} requires a value.`);
69
+ return null;
70
+ }
71
+ return value;
72
+ }
73
+
74
+ function parseMediaGenerateArgs(args: readonly string[]): MediaGenerateArgs {
75
+ const parsed = stripYesFlag([...args]);
76
+ const errors: string[] = [];
77
+ const promptParts: string[] = [];
78
+ let providerId: string | undefined;
79
+ let modelId: string | undefined;
80
+ let outputMimeType: string | undefined;
81
+ for (let index = 0; index < parsed.rest.length; index += 1) {
82
+ const arg = parsed.rest[index];
83
+ if (arg === '--provider') {
84
+ providerId = readFlagValue(parsed.rest, index, arg, errors) ?? providerId;
85
+ index += 1;
86
+ } else if (arg === '--model') {
87
+ modelId = readFlagValue(parsed.rest, index, arg, errors) ?? modelId;
88
+ index += 1;
89
+ } else if (arg === '--mime') {
90
+ outputMimeType = readFlagValue(parsed.rest, index, arg, errors) ?? outputMimeType;
91
+ index += 1;
92
+ } else if (arg?.startsWith('--')) {
93
+ errors.push(`Unknown media generation flag ${arg}`);
94
+ } else if (arg) {
95
+ promptParts.push(arg);
96
+ }
97
+ }
98
+ const prompt = promptParts.join(' ').trim();
99
+ if (!prompt) errors.push('Media generation prompt is required.');
100
+ return {
101
+ prompt,
102
+ ...(providerId ? { providerId } : {}),
103
+ ...(modelId ? { modelId } : {}),
104
+ ...(outputMimeType ? { outputMimeType } : {}),
105
+ yes: parsed.yes,
106
+ errors,
107
+ };
108
+ }
109
+
52
110
  export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
53
111
  registry.register({ name: 'expand', description: 'Expand blocks by type', usage: '[all|thinking|tool|code]', argsHint: '[all|thinking|tool|code]', handler(args, ctx) { toggleBlocks(args[0] || 'all', false, ctx); } });
54
112
  registry.register({ name: 'collapse', description: 'Collapse blocks by type', usage: '[all|thinking|tool|code]', argsHint: '[all|thinking|tool|code]', handler(args, ctx) { toggleBlocks(args[0] || 'all', true, ctx); } });
@@ -130,9 +188,12 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
130
188
  }
131
189
  try {
132
190
  const resolved = await resolveSecretRef(refText, { resolveLocalSecret: (key) => mgr.get(key) });
133
- ctx.print(`[secrets] ${describeSecretRef(refText)}: ${resolved.value ? 'resolved <redacted>' : 'missing'}`);
191
+ ctx.print([
192
+ `[secrets] ${describeSecretRef(refText)}`,
193
+ ` status ${resolved.value ? 'resolved <redacted>' : 'missing'}`,
194
+ ].join('\n'));
134
195
  } catch (error) {
135
- ctx.print(`[secrets] ${describeSecretRef(refText)} failed: ${summarizeError(error)}`);
196
+ ctx.print(`[secrets] ${describeSecretRef(refText)} failed ${summarizeError(error)}`);
136
197
  }
137
198
  return;
138
199
  }
@@ -190,7 +251,10 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
190
251
  scope: flags.has('--user') ? 'user' : flags.has('--project') ? 'project' : undefined,
191
252
  medium: flags.has('--plaintext') ? 'plaintext' : flags.has('--secure') ? 'secure' : undefined,
192
253
  });
193
- ctx.print(`[secrets] Deleted: ${key}`);
254
+ ctx.print([
255
+ '[secrets] Deleted',
256
+ ` key ${key}`,
257
+ ].join('\n'));
194
258
  return;
195
259
  }
196
260
  ctx.print('[secrets] Usage: /secrets set <KEY> <value> [--user|--project] [--secure|--plaintext] --yes | link <KEY> <secret-ref> [--user|--project] [--secure|--plaintext] --yes | get <KEY> | test <secret-ref> | providers | list | delete <KEY> [--user|--project] [--secure|--plaintext] --yes');
@@ -212,36 +276,42 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
212
276
  const promptText = args.slice(1).join(' ') || `Attached image: ${rawPath.split('/').pop() ?? rawPath}`;
213
277
  const projectRoot = ctx.workspace.shellPaths?.workingDirectory ?? ctx.platform.configManager.getWorkingDirectory();
214
278
  if (!projectRoot) {
215
- ctx.print('Error: working directory is unavailable.');
279
+ ctx.print([
280
+ 'Error',
281
+ ' message working directory is unavailable.',
282
+ ].join('\n'));
216
283
  return;
217
284
  }
218
285
  let resolvedPath: string;
219
286
  try {
220
287
  resolvedPath = resolveAndValidatePath(rawPath, projectRoot);
221
288
  } catch (err) {
222
- ctx.print(`Error: ${summarizeError(err)}`);
289
+ ctx.print([
290
+ 'Error',
291
+ ` message ${summarizeError(err)}`,
292
+ ].join('\n'));
223
293
  return;
224
294
  }
225
295
  if (!existsSync(resolvedPath)) {
226
- ctx.print(`File not found: ${rawPath}`);
296
+ ctx.print(`File not found ${rawPath}`);
227
297
  return;
228
298
  }
229
299
  const ext = resolvedPath.slice(resolvedPath.lastIndexOf('.')).toLowerCase();
230
300
  const mediaType = ({ '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.webp': 'image/webp', '.gif': 'image/gif' } as Record<string, string>)[ext];
231
301
  if (!mediaType) {
232
- ctx.print(`Unsupported image format: ${ext}\nSupported: .png, .jpg, .jpeg, .webp, .gif`);
302
+ ctx.print(`Unsupported image format ${ext}\nSupported .png, .jpg, .jpeg, .webp, .gif`);
233
303
  return;
234
304
  }
235
305
  const stat = statSync(resolvedPath);
236
306
  if (stat.size > 20 * 1024 * 1024) {
237
- ctx.print(`Image too large (${(stat.size / 1024 / 1024).toFixed(1)}MB). Maximum: 20MB`);
307
+ ctx.print(`Image too large (${(stat.size / 1024 / 1024).toFixed(1)}MB). Maximum 20MB`);
238
308
  return;
239
309
  }
240
310
  let data: string;
241
311
  try {
242
312
  data = (await readFile(resolvedPath)).toString('base64');
243
313
  } catch (err) {
244
- ctx.print(`Failed to read image: ${summarizeError(err)}`);
314
+ ctx.print(`Failed to read image ${summarizeError(err)}`);
245
315
  return;
246
316
  }
247
317
  const currentModel = await requireProviderApi(ctx).getCurrentModel();
@@ -253,6 +323,53 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
253
323
  },
254
324
  });
255
325
 
326
+ registry.register({
327
+ name: 'media',
328
+ description: 'Inspect media providers or generate media through configured providers',
329
+ usage: 'providers | generate [--provider <id>] [--model <id>] [--mime <mime>] <prompt> --yes',
330
+ argsHint: '<providers|generate>',
331
+ async handler(args, ctx) {
332
+ const subcommand = args[0]?.toLowerCase() || 'providers';
333
+ const registry = ctx.platform.mediaProviderRegistry;
334
+ if (!registry) {
335
+ ctx.print('[media] Media providers are not available in this runtime.');
336
+ return;
337
+ }
338
+ if (subcommand === 'providers' || subcommand === 'status' || subcommand === 'list') {
339
+ const statuses = await registry.status();
340
+ ctx.print([
341
+ '[media] Providers:',
342
+ ...statuses.map((provider) => ` ${provider.id}: ${provider.state}${provider.configured ? '' : ' (setup needed)'} - ${provider.capabilities.join(', ')}${provider.detail ? ` - ${provider.detail}` : ''}`),
343
+ ].join('\n'));
344
+ return;
345
+ }
346
+ if (subcommand !== 'generate') {
347
+ ctx.print('[media] Usage: /media providers | /media generate [--provider <id>] [--model <id>] [--mime <mime>] <prompt> --yes');
348
+ return;
349
+ }
350
+ const artifactStore = ctx.platform.artifactStore;
351
+ if (!artifactStore) {
352
+ ctx.print('[media] Artifact storage is not available in this runtime.');
353
+ return;
354
+ }
355
+ const parsed = parseMediaGenerateArgs(args.slice(1));
356
+ if (parsed.errors.length > 0) {
357
+ ctx.print(`[media] ${parsed.errors.join('\n[media] ')}`);
358
+ return;
359
+ }
360
+ if (!parsed.yes) {
361
+ requireYesFlag(ctx, `generate media for "${parsed.prompt}"`, '/media generate [--provider <id>] [--model <id>] [--mime <mime>] <prompt> --yes');
362
+ return;
363
+ }
364
+ try {
365
+ const result = await generateAgentMedia(registry, artifactStore, parsed);
366
+ ctx.print(formatAgentMediaGenerationResult(result));
367
+ } catch (error) {
368
+ ctx.print(`[media] Generation failed ${summarizeError(error)}`);
369
+ }
370
+ },
371
+ });
372
+
256
373
  registry.register({
257
374
  name: 'refresh-models',
258
375
  description: 'Refresh model catalog, benchmarks, and token limits',
@@ -267,15 +384,17 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
267
384
  catalogOk = true;
268
385
  ctx.print(`Model catalog refreshed: ${catalog.modelCount} models from ${catalog.providerCount} providers`);
269
386
  } catch (e) {
270
- ctx.print(`Catalog refresh failed: ${summarizeError(e)}`);
387
+ ctx.print(`Catalog refresh failed ${summarizeError(e)}`);
271
388
  }
272
389
  ctx.print('Refreshing benchmarks...');
273
390
  try {
274
391
  const benchmarkCount = await providerApi.refreshBenchmarks();
275
392
  benchmarksOk = true;
276
- ctx.print(`Benchmarks refreshed${benchmarkCount > 0 ? `: ${benchmarkCount} model records available.` : '.'}`);
393
+ ctx.print(benchmarkCount > 0
394
+ ? `Benchmarks refreshed: ${benchmarkCount} model records available.`
395
+ : 'Benchmarks refreshed.');
277
396
  } catch (e) {
278
- ctx.print(`Benchmarks refresh failed: ${summarizeError(e)}`);
397
+ ctx.print(`Benchmarks refresh failed ${summarizeError(e)}`);
279
398
  }
280
399
  ctx.print('Refreshing token limits...');
281
400
  try {
@@ -283,7 +402,7 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
283
402
  limitsOk = true;
284
403
  ctx.print(`Token limits refreshed: ${count} models updated.`);
285
404
  } catch (e) {
286
- ctx.print(`Token limits refresh failed: ${summarizeError(e)}`);
405
+ ctx.print(`Token limits refresh failed ${summarizeError(e)}`);
287
406
  }
288
407
  if (!catalogOk || !benchmarksOk || !limitsOk) ctx.print('Some refreshes failed — see messages above.');
289
408
  },
@@ -308,14 +427,17 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
308
427
  }
309
428
  const favorites = await providerApi.getFavorites();
310
429
  if (favorites.pinned.some((entry) => entry.modelId === modelId || entry.registryKey === modelId)) {
311
- ctx.print(`Model already pinned: ${modelId}`);
430
+ ctx.print(`Model already pinned ${modelId}`);
312
431
  return;
313
432
  }
314
433
  try {
315
434
  await providerApi.pinModel(modelId);
316
435
  ctx.print(`Pinned: ${modelId}`);
317
436
  } catch (e) {
318
- ctx.print(`Error: ${summarizeError(e)}`);
437
+ ctx.print([
438
+ 'Error',
439
+ ` message ${summarizeError(e)}`,
440
+ ].join('\n'));
319
441
  }
320
442
  },
321
443
  });
@@ -13,7 +13,7 @@ export interface SetupReviewSnapshot {
13
13
  readonly builtinSubscriptionProviderCount: number;
14
14
  readonly activeSubscriptionCount: number;
15
15
  readonly pendingSubscriptionCount: number;
16
- readonly serviceIssues: string[];
16
+ readonly hostIntegrationIssues: string[];
17
17
  readonly skillCount: number;
18
18
  readonly pluginCount: number;
19
19
  readonly quarantinedPluginCount: number;
@@ -33,11 +33,11 @@ export async function buildSetupReviewSnapshot(ctx: CommandContext): Promise<Set
33
33
  const serviceRegistry = requireServiceRegistry(ctx);
34
34
  const services = Object.keys(serviceRegistry.getAll()).sort((a, b) => a.localeCompare(b));
35
35
  const serviceConfigs = serviceRegistry.getAll();
36
- const serviceIssues: string[] = [];
36
+ const hostIntegrationIssues: string[] = [];
37
37
  for (const name of services) {
38
38
  const inspection = await serviceRegistry.inspect(name);
39
39
  if (!inspection?.hasPrimaryCredential) {
40
- serviceIssues.push(`${name}: missing primary credential`);
40
+ hostIntegrationIssues.push(`${name}: missing primary credential`);
41
41
  }
42
42
  }
43
43
 
@@ -79,13 +79,13 @@ export async function buildSetupReviewSnapshot(ctx: CommandContext): Promise<Set
79
79
  message: providerCount > 0 ? `${providerCount} model(s) available` : 'no models available',
80
80
  },
81
81
  {
82
- severity: (services.length === 0 && oauthProviderCount === 0 && builtinSubscriptionProviderCount === 0) ? 'warn' : serviceIssues.length === 0 ? 'pass' : 'warn',
83
- area: 'services',
82
+ severity: (services.length === 0 && oauthProviderCount === 0 && builtinSubscriptionProviderCount === 0) ? 'warn' : hostIntegrationIssues.length === 0 ? 'pass' : 'warn',
83
+ area: 'host',
84
84
  message: (services.length === 0 && oauthProviderCount === 0 && builtinSubscriptionProviderCount === 0)
85
- ? 'no services configured'
86
- : serviceIssues.length === 0
87
- ? `${services.length} service(s), ${oauthProviderCount + builtinSubscriptionProviderCount} oauth provider(s), ${activeSubscriptionCount} active subscription override(s)`
88
- : `${serviceIssues.length} service configuration issue(s)`,
85
+ ? 'no connected host integrations configured'
86
+ : hostIntegrationIssues.length === 0
87
+ ? `${services.length} host integration(s), ${oauthProviderCount + builtinSubscriptionProviderCount} oauth provider(s), ${activeSubscriptionCount} active subscription override(s)`
88
+ : `${hostIntegrationIssues.length} host integration configuration issue(s)`,
89
89
  },
90
90
  {
91
91
  severity: quarantinedPluginCount === 0 ? 'pass' : 'warn',
@@ -101,16 +101,6 @@ export async function buildSetupReviewSnapshot(ctx: CommandContext): Promise<Set
101
101
  ? `${quarantinedMcpCount} quarantined, ${elevatedMcpCount} elevated`
102
102
  : `${mcpServers.length} server(s) known`,
103
103
  },
104
- {
105
- severity: managedHookCount > 0 || managedHookChainCount > 0 ? 'pass' : 'warn',
106
- area: 'hooks',
107
- message: `${managedHookCount} managed hook(s), ${managedHookChainCount} chain(s)`,
108
- },
109
- {
110
- severity: remoteRunnerCount > 0 ? 'pass' : 'warn',
111
- area: 'remote',
112
- message: remoteRunnerCount > 0 ? `${remoteRunnerCount} remote worker contract(s)` : 'no remote worker contracts registered',
113
- },
114
104
  ];
115
105
 
116
106
  return {
@@ -121,7 +111,7 @@ export async function buildSetupReviewSnapshot(ctx: CommandContext): Promise<Set
121
111
  builtinSubscriptionProviderCount,
122
112
  activeSubscriptionCount,
123
113
  pendingSubscriptionCount,
124
- serviceIssues,
114
+ hostIntegrationIssues,
125
115
  skillCount: skills.length,
126
116
  pluginCount: plugins.length,
127
117
  quarantinedPluginCount,
@@ -12,6 +12,11 @@ interface ParsedMcpAddArgs {
12
12
  readonly server: McpServerConfig;
13
13
  }
14
14
 
15
+ type McpSecurityServer = ReturnType<NonNullable<NonNullable<CommandContext['clients']>['mcpApi']>['listServerSecurity']>[number];
16
+ type McpDisplayTrustMode = NonNullable<McpServerConfig['trustMode']> | McpSecurityServer['trustMode'];
17
+ type McpDisplayRole = NonNullable<McpServerConfig['role']> | McpSecurityServer['role'];
18
+ type McpDisplayFreshness = McpSecurityServer['schemaFreshness'];
19
+
15
20
  function isMcpRole(value: string): value is NonNullable<McpServerConfig['role']> {
16
21
  return MCP_ROLES.includes(value as NonNullable<McpServerConfig['role']>);
17
22
  }
@@ -24,6 +29,21 @@ function isMcpScope(value: string): value is McpConfigScope {
24
29
  return value === 'project' || value === 'global';
25
30
  }
26
31
 
32
+ function formatMcpTrustMode(mode: McpDisplayTrustMode): string {
33
+ if (mode === 'ask-on-risk') return 'ask on risky actions';
34
+ if (mode === 'allow-all') return 'allow all actions';
35
+ return mode.replace(/[_-]+/g, ' ');
36
+ }
37
+
38
+ function formatMcpFreshness(value: McpDisplayFreshness): string {
39
+ if (value === 'quarantined') return 'needs review';
40
+ return value.replace(/[_-]+/g, ' ');
41
+ }
42
+
43
+ function formatMcpRole(value: McpDisplayRole): string {
44
+ return value.replace(/[_-]+/g, ' ');
45
+ }
46
+
27
47
  function validateServerName(name: string): string | null {
28
48
  if (!name.trim()) return 'MCP server name is required.';
29
49
  if (!/^[a-zA-Z0-9._-]+$/.test(name)) {
@@ -71,7 +91,7 @@ function parseAddServerArgs(args: string[]): ParsedMcpAddArgs {
71
91
  }
72
92
  if (token === '--role') {
73
93
  const value = readFlagValue(tokens, index, token);
74
- if (!isMcpRole(value)) throw new Error(`Invalid MCP role "${value}". Expected one of: ${MCP_ROLES.join(', ')}`);
94
+ if (!isMcpRole(value)) throw new Error(`Invalid MCP role "${value}". Expected one of ${MCP_ROLES.join(', ')}.`);
75
95
  role = value;
76
96
  index += 1;
77
97
  continue;
@@ -85,7 +105,7 @@ function parseAddServerArgs(args: string[]): ParsedMcpAddArgs {
85
105
  }
86
106
  if (token === '--trust') {
87
107
  const value = readFlagValue(tokens, index, token);
88
- if (!isMcpTrustMode(value)) throw new Error(`Invalid MCP trust mode "${value}". Expected one of: ${MCP_TRUST_MODES.join(', ')}`);
108
+ if (!isMcpTrustMode(value)) throw new Error(`Invalid MCP trust mode "${value}". Expected one of ${MCP_TRUST_MODES.join(', ')}.`);
89
109
  trustMode = value;
90
110
  index += 1;
91
111
  continue;
@@ -157,7 +177,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
157
177
  ` servers: ${servers.length}`,
158
178
  ` connected: ${servers.filter((server) => server.connected).length}`,
159
179
  ` auth or repair attention: ${authRequired.length}`,
160
- ...servers.map((server) => ` ${server.name} trust=${server.trustMode} role=${server.role} freshness=${server.schemaFreshness} connected=${server.connected ? 'yes' : 'no'}`),
180
+ ...servers.map((server) => ` ${server.name} trust=${formatMcpTrustMode(server.trustMode)} role=${formatMcpRole(server.role)} status=${formatMcpFreshness(server.schemaFreshness)} connected=${server.connected ? 'yes' : 'no'}`),
161
181
  ' next: /mcp auth-review',
162
182
  ' next: /mcp repair <server>',
163
183
  ].join('\n'));
@@ -174,8 +194,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
174
194
  + ' ~/.config/claude/claude_desktop_config.json (Claude Desktop)\n'
175
195
  + ' .mcp/mcp.json (project-local)\n'
176
196
  + ' .goodvibes/mcp.json (goodvibes project)\n'
177
- + '\nAdd one from inside Agent with explicit confirmation:\n'
178
- + ' /mcp add filesystem npx -y @modelcontextprotocol/server-filesystem . --scope project --role filesystem --yes'
197
+ + '\nOpen /mcp and choose Add or update server, or use Agent Workspace -> Tools & MCP -> Add MCP server.'
179
198
  );
180
199
  return;
181
200
  }
@@ -219,7 +238,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
219
238
  ? [
220
239
  'MCP Auth Review',
221
240
  ...needingAttention.map((server) => (
222
- ` ${server.name} connected=${server.connected ? 'yes' : 'no'} freshness=${server.schemaFreshness} trust=${server.trustMode}`
241
+ ` ${server.name} connected ${server.connected ? 'yes' : 'no'} status ${formatMcpFreshness(server.schemaFreshness)} trust ${formatMcpTrustMode(server.trustMode)}`
223
242
  )),
224
243
  ' next: /auth review',
225
244
  ' next: /mcp repair <server>',
@@ -234,7 +253,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
234
253
  const selected = serverName ? servers.find((server) => server.name === serverName) : servers.find((server) => !server.connected || server.schemaFreshness === 'quarantined');
235
254
  if (!selected) {
236
255
  ctx.print(serverName
237
- ? `Unknown MCP server: ${serverName}`
256
+ ? `Unknown MCP server ${serverName}`
238
257
  : 'MCP Repair\n No MCP server currently needs repair.');
239
258
  return;
240
259
  }
@@ -249,9 +268,9 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
249
268
  ctx.print([
250
269
  `MCP Repair: ${selected.name}`,
251
270
  ` connected: ${selected.connected ? 'yes' : 'no'}`,
252
- ` trust: ${selected.trustMode}`,
253
- ` role: ${selected.role}`,
254
- ` freshness: ${selected.schemaFreshness}`,
271
+ ` trust: ${formatMcpTrustMode(selected.trustMode)}`,
272
+ ` role: ${formatMcpRole(selected.role)}`,
273
+ ` status: ${formatMcpFreshness(selected.schemaFreshness)}`,
255
274
  ...(selected.quarantineReason ? [` quarantine: ${selected.quarantineReason}`] : []),
256
275
  ...(selected.quarantineDetail ? [` detail: ${selected.quarantineDetail}`] : []),
257
276
  ' next:',
@@ -265,7 +284,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
265
284
  const mode = commandArgs[2] as 'constrained' | 'ask-on-risk' | 'allow-all' | 'blocked' | undefined;
266
285
  if (serverName && mode) {
267
286
  if (mode === 'allow-all') {
268
- ctx.print(`Use /settings → MCP to explicitly enable allow-all for ${serverName}. Direct CLI escalation is blocked.`);
287
+ ctx.print(`Use /settings → MCP to explicitly enable allow-all for ${serverName}. Direct command escalation is blocked.`);
269
288
  ctx.openSettingsModal?.();
270
289
  return;
271
290
  }
@@ -274,7 +293,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
274
293
  return;
275
294
  }
276
295
  mcpApi.setServerTrustMode(serverName, mode);
277
- ctx.print(`Updated MCP trust mode for ${serverName} to ${mode}.`);
296
+ ctx.print(`Updated MCP trust mode for ${serverName} to ${formatMcpTrustMode(mode)}.`);
278
297
  return;
279
298
  }
280
299
  if (serverName || mode) {
@@ -292,7 +311,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
292
311
  return;
293
312
  }
294
313
  mcpApi.setServerRole(serverName, role);
295
- ctx.print(`Updated MCP role for ${serverName} to ${role}.`);
314
+ ctx.print(`Updated MCP role for ${serverName} to ${formatMcpRole(role)}.`);
296
315
  return;
297
316
  }
298
317
  if (serverName || role) {
@@ -318,13 +337,16 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
318
337
  const result = await mcpApi.upsertServerConfig(shellPaths, parsedAdd.scope, parsedAdd.server);
319
338
  const connected = listServerSecurity().find((entry) => entry.name === parsedAdd.server.name)?.connected ?? false;
320
339
  ctx.print([
321
- `MCP server "${parsedAdd.server.name}" saved to ${parsedAdd.scope} config: ${result.path}.`,
322
- `Runtime reload: ${connected ? 'connected' : 'server saved; connection needs attention'} (+${result.reload.added} ~${result.reload.changed} -${result.reload.removed}, unchanged ${result.reload.unchanged}).`,
323
- `Command: ${parsedAdd.server.command}${parsedAdd.server.args?.length ? ` ${parsedAdd.server.args.join(' ')}` : ''}`,
324
- 'Next: /mcp tools',
340
+ `MCP server "${parsedAdd.server.name}" saved`,
341
+ ` scope ${parsedAdd.scope}`,
342
+ ` path ${result.path}`,
343
+ ...(parsedAdd.scope === 'global' ? [' global config'] : []),
344
+ ` Runtime reload: ${connected ? 'connected' : 'server saved; connection needs attention'} (+${result.reload.added} ~${result.reload.changed} -${result.reload.removed}, unchanged ${result.reload.unchanged})`,
345
+ ` command ${parsedAdd.server.command}${parsedAdd.server.args?.length ? ` ${parsedAdd.server.args.join(' ')}` : ''}`,
346
+ ' next /mcp tools',
325
347
  ].join('\n'));
326
348
  } catch (error) {
327
- ctx.print(`MCP add failed: ${summarizeError(error)}`);
349
+ ctx.print(`MCP add failed ${summarizeError(error)}`);
328
350
  }
329
351
  return;
330
352
  }
@@ -360,10 +382,10 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
360
382
  try {
361
383
  const result = await mcpApi.removeServerConfig(shellPaths, scope, serverName);
362
384
  ctx.print(result.removed
363
- ? `Removed MCP server "${serverName}" from ${scope} config ${result.path}. Reload: +${result.reload.added} ~${result.reload.changed} -${result.reload.removed}, unchanged ${result.reload.unchanged}.`
385
+ ? `Removed MCP server "${serverName}" from ${scope} config ${result.path}. Reload +${result.reload.added} ~${result.reload.changed} -${result.reload.removed}, unchanged ${result.reload.unchanged}.`
364
386
  : `No ${scope} MCP server named "${serverName}" exists in ${result.path}.\nIf it still appears, it is coming from another config scope or external MCP config.`);
365
387
  } catch (error) {
366
- ctx.print(`MCP remove failed: ${summarizeError(error)}`);
388
+ ctx.print(`MCP remove failed ${summarizeError(error)}`);
367
389
  }
368
390
  return;
369
391
  }
@@ -376,9 +398,9 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
376
398
  try {
377
399
  const result = await reloadMcpRuntime(ctx);
378
400
  const servers = listServerSecurity();
379
- ctx.print(`Reloaded MCP runtime from config. ${servers.filter((server) => server.connected).length}/${servers.length} server(s) connected. Result: +${result.added} ~${result.changed} -${result.removed}, unchanged ${result.unchanged}.`);
401
+ ctx.print(`Reloaded MCP runtime from config. ${servers.filter((server) => server.connected).length}/${servers.length} server(s) connected. Result +${result.added} ~${result.changed} -${result.removed}, unchanged ${result.unchanged}.`);
380
402
  } catch (error) {
381
- ctx.print(`MCP reload failed: ${summarizeError(error)}`);
403
+ ctx.print(`MCP reload failed ${summarizeError(error)}`);
382
404
  }
383
405
  return;
384
406
  }
@@ -389,22 +411,22 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
389
411
  const effective = mcpApi.getEffectiveConfig(shellPaths);
390
412
  ctx.print([
391
413
  'MCP Config',
392
- ' locations:',
414
+ ' locations',
393
415
  ...effective.locations.map((location) => ` ${location.scope}/${location.kind}${location.writable ? ' writable' : ' read-only'} ${location.path}`),
394
- ` effective servers: ${effective.servers.length}`,
416
+ ` effective servers ${effective.servers.length}`,
395
417
  ...effective.servers.map((entry) => {
396
418
  const server = entry.server;
397
419
  const envKeys = Object.keys(server.env ?? {});
398
- return ` - ${server.name}: ${server.command}${server.args?.length ? ` ${server.args.join(' ')}` : ''} source=${entry.source.scope}/${entry.source.kind}${envKeys.length ? ` envKeys=${envKeys.join(',')}` : ''}`;
420
+ return ` - ${server.name}: ${server.command}${server.args?.length ? ` ${server.args.join(' ')}` : ''} origin ${entry.source.scope}/${entry.source.kind}${envKeys.length ? ` envKeys=${envKeys.join(',')}` : ''}`;
399
421
  }),
400
422
  '',
401
- 'Add or update from inside Agent with explicit confirmation:',
423
+ 'Add or update from inside Agent with explicit confirmation',
424
+ ' Open /mcp and choose Add or update server, or use Agent Workspace -> Tools & MCP -> Add MCP server.',
425
+ 'Automation equivalent',
402
426
  ' /mcp add <name> <command> [args...] [--scope project|global] [--role <role>] [--trust <mode>] --yes',
403
- 'Example:',
404
- ' /mcp add filesystem npx -y @modelcontextprotocol/server-filesystem . --scope project --role filesystem --trust constrained --yes',
405
427
  ].join('\n'));
406
428
  } catch (error) {
407
- ctx.print(`MCP config read failed: ${summarizeError(error)}`);
429
+ ctx.print(`MCP config read failed ${summarizeError(error)}`);
408
430
  }
409
431
  return;
410
432
  }
@@ -418,12 +440,12 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
418
440
  }
419
441
  if (action === 'approve') {
420
442
  if (!confirmation.yes) {
421
- requireYesFlag(ctx, `approve MCP schema quarantine override for ${serverName}`, '/mcp quarantine <server> approve [operatorId] --yes');
443
+ requireYesFlag(ctx, `approve MCP tool-definition quarantine override for ${serverName}`, '/mcp quarantine <server> approve [operatorId] --yes');
422
444
  return;
423
445
  }
424
446
  const operatorId = commandArgs[3] || 'operator';
425
447
  mcpApi.approveSchemaQuarantine(serverName, operatorId);
426
- ctx.print(`Approved MCP schema quarantine override for ${serverName} as ${operatorId}. Refresh is still recommended.`);
448
+ ctx.print(`Approved MCP tool-definition quarantine override for ${serverName} as ${operatorId}. Refresh is still recommended.`);
427
449
  return;
428
450
  }
429
451
  if (!confirmation.yes) {
@@ -432,7 +454,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
432
454
  }
433
455
  const detail = commandArgs.slice(2).join(' ') || 'quarantined by operator';
434
456
  mcpApi.quarantineSchema(serverName, 'operator_flagged', detail);
435
- ctx.print(`Quarantined MCP schema for ${serverName}.\nReason: ${detail}`);
457
+ ctx.print(`Quarantined MCP tool definitions for ${serverName}.\nReason: ${detail}`);
436
458
  return;
437
459
  }
438
460
 
@@ -446,8 +468,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
446
468
  + ' ~/.config/claude/claude_desktop_config.json (Claude Desktop)\n'
447
469
  + ' .mcp/mcp.json (project-local)\n'
448
470
  + ' .goodvibes/mcp.json (goodvibes project)\n'
449
- + '\nAdd one from inside Agent with explicit confirmation:\n'
450
- + ' /mcp add filesystem npx -y @modelcontextprotocol/server-filesystem . --scope project --role filesystem --yes\n'
471
+ + '\nOpen /mcp and choose Add or update server, or use Agent Workspace -> Tools & MCP -> Add MCP server.\n'
451
472
  + '\nFormat: { "servers": [{ "name": "my-server", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"] }] }'
452
473
  );
453
474
  return;
@@ -458,26 +479,22 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
458
479
  });
459
480
  }
460
481
 
461
- type McpSecurityServer = ReturnType<NonNullable<NonNullable<CommandContext['clients']>['mcpApi']>['listServerSecurity']>[number];
462
-
463
482
  function formatMcpServerList(servers: readonly McpSecurityServer[]): string {
464
483
  const connected = servers.filter(s => s.connected);
465
484
  const disconnected = servers.filter(s => !s.connected);
466
485
  const lines: string[] = [`MCP Servers (${connected.length}/${servers.length} connected):`];
467
486
  for (const s of servers) {
468
- const pathScope = s.allowedPaths.length > 0 ? ` paths=${s.allowedPaths.length}` : '';
469
- const hostScope = s.allowedHosts.length > 0 ? ` hosts=${s.allowedHosts.length}` : '';
470
- const freshness = ` freshness=${s.schemaFreshness}`;
471
- const quarantine = s.schemaFreshness === 'quarantined' ? ` quarantine=${s.quarantineReason ?? 'unknown'}` : '';
472
- lines.push(` ${s.connected ? '[connected] ' : '[disconnected]'} ${s.name} trust=${s.trustMode} role=${s.role}${freshness}${quarantine}${pathScope}${hostScope}`);
487
+ const pathScope = s.allowedPaths.length > 0 ? ` paths ${s.allowedPaths.length}` : '';
488
+ const hostScope = s.allowedHosts.length > 0 ? ` hosts ${s.allowedHosts.length}` : '';
489
+ const freshness = ` status ${formatMcpFreshness(s.schemaFreshness)}`;
490
+ const quarantine = s.schemaFreshness === 'quarantined' ? ` review reason ${s.quarantineReason ?? 'unknown'}` : '';
491
+ lines.push(` ${s.connected ? '[connected] ' : '[disconnected]'} ${s.name} trust=${formatMcpTrustMode(s.trustMode)} role=${formatMcpRole(s.role)}${freshness}${quarantine}${pathScope}${hostScope}`);
473
492
  }
474
493
  if (connected.length > 0) {
475
494
  lines.push('');
476
- lines.push('Run "/mcp tools" to list all tools, or "/mcp tools <server>" for a specific server.');
477
- lines.push('Run "/mcp" to open the fullscreen MCP workspace, or "/mcp add <name> <command> [args...] [--scope project|global] --yes" to add/update.');
478
- lines.push('Run "/mcp reload --yes" after editing MCP config outside Agent.');
479
- lines.push('Run "/mcp trust <server> <mode> --yes" to change trust mode, or "/mcp role <server> <role> --yes" to change its coherence role.');
480
- lines.push('Run "/mcp quarantine <server> [detail] --yes" to block a server, or "/mcp quarantine <server> approve [operatorId] --yes" to approve a temporary override.');
495
+ lines.push('Open "/mcp" for the fullscreen MCP workspace with server status, tools, config paths, and confirmed add/remove/reload actions.');
496
+ lines.push('Use "/mcp tools" for a compact tool list, or "/mcp tools <server>" for one server.');
497
+ lines.push('Use Settings -> MCP for allow-all decisions; trust, role, and quarantine command actions still require explicit --yes confirmation.');
481
498
  lines.push('Use /settings -> MCP to explicitly enable allow-all for a server.');
482
499
  }
483
500
  if (disconnected.length > 0) {