@pellux/goodvibes-agent 1.0.31 → 1.0.34

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 (74) hide show
  1. package/CHANGELOG.md +59 -2
  2. package/README.md +72 -58
  3. package/dist/package/main.js +7572 -2430
  4. package/docs/README.md +25 -21
  5. package/docs/channels-remote-and-api.md +6 -2
  6. package/docs/connected-host.md +27 -6
  7. package/docs/getting-started.md +87 -66
  8. package/docs/knowledge-artifacts-and-multimodal.md +16 -4
  9. package/docs/project-planning.md +2 -2
  10. package/docs/providers-and-routing.md +3 -2
  11. package/docs/release-and-publishing.md +15 -11
  12. package/docs/tools-and-commands.md +150 -128
  13. package/docs/voice-and-live-tts.md +1 -1
  14. package/package.json +8 -3
  15. package/release/live-verification/live-verification.json +148 -0
  16. package/release/live-verification/live-verification.md +187 -0
  17. package/release/performance-snapshot.json +57 -0
  18. package/release/release-notes.md +19 -0
  19. package/release/release-readiness.json +581 -0
  20. package/src/agent/harness-control.ts +42 -3
  21. package/src/cli/agent-knowledge-command.ts +5 -5
  22. package/src/cli/agent-knowledge-format.ts +11 -0
  23. package/src/cli/agent-knowledge-runtime.ts +92 -13
  24. package/src/cli/bundle-command.ts +5 -4
  25. package/src/cli/entrypoint.ts +5 -2
  26. package/src/cli/external-runtime.ts +2 -15
  27. package/src/cli/management.ts +4 -3
  28. package/src/input/commands/guidance-runtime.ts +1 -1
  29. package/src/input/commands/knowledge.ts +2 -2
  30. package/src/runtime/bootstrap.ts +10 -18
  31. package/src/runtime/connected-host-auth.ts +16 -0
  32. package/src/tools/agent-analysis-registry-policy.ts +2 -9
  33. package/src/tools/agent-channel-send-tool.ts +3 -9
  34. package/src/tools/agent-context-policy.ts +1 -5
  35. package/src/tools/agent-find-policy.ts +1 -4
  36. package/src/tools/agent-harness-channel-metadata.ts +177 -0
  37. package/src/tools/agent-harness-cli-metadata.ts +4 -1
  38. package/src/tools/agent-harness-command-catalog.ts +10 -3
  39. package/src/tools/agent-harness-connected-host-status.ts +9 -3
  40. package/src/tools/agent-harness-delegation-posture.ts +216 -0
  41. package/src/tools/agent-harness-keybinding-metadata.ts +57 -22
  42. package/src/tools/agent-harness-mcp-metadata.ts +248 -0
  43. package/src/tools/agent-harness-media-posture.ts +282 -0
  44. package/src/tools/agent-harness-metadata.ts +44 -9
  45. package/src/tools/agent-harness-model-routing.ts +501 -0
  46. package/src/tools/agent-harness-model-tool-catalog.ts +7 -2
  47. package/src/tools/agent-harness-notification-metadata.ts +217 -0
  48. package/src/tools/agent-harness-operator-methods.ts +285 -0
  49. package/src/tools/agent-harness-pairing-posture.ts +265 -0
  50. package/src/tools/agent-harness-panel-metadata.ts +26 -12
  51. package/src/tools/agent-harness-provider-account-metadata.ts +205 -0
  52. package/src/tools/agent-harness-release-evidence.ts +364 -0
  53. package/src/tools/agent-harness-release-readiness.ts +298 -0
  54. package/src/tools/agent-harness-security-posture.ts +648 -0
  55. package/src/tools/agent-harness-service-posture.ts +207 -0
  56. package/src/tools/agent-harness-session-metadata.ts +284 -0
  57. package/src/tools/agent-harness-setup-posture.ts +295 -0
  58. package/src/tools/agent-harness-tool-schema.ts +104 -27
  59. package/src/tools/agent-harness-tool.ts +251 -235
  60. package/src/tools/agent-harness-ui-surface-metadata.ts +20 -12
  61. package/src/tools/agent-harness-workspace-actions.ts +260 -0
  62. package/src/tools/agent-knowledge-ingest-tool.ts +4 -10
  63. package/src/tools/agent-knowledge-tool.ts +120 -25
  64. package/src/tools/agent-local-registry-tool.ts +3 -7
  65. package/src/tools/agent-media-generate-tool.ts +2 -8
  66. package/src/tools/agent-notify-tool.ts +3 -8
  67. package/src/tools/agent-operator-action-tool.ts +4 -10
  68. package/src/tools/agent-operator-briefing-tool.ts +1 -6
  69. package/src/tools/agent-read-policy.ts +1 -4
  70. package/src/tools/agent-reminder-schedule-tool.ts +4 -9
  71. package/src/tools/agent-tool-policy-guard.ts +15 -51
  72. package/src/tools/agent-web-search-policy.ts +1 -4
  73. package/src/tools/agent-work-plan-tool.ts +1 -6
  74. package/src/version.ts +2 -2
@@ -53,6 +53,7 @@ interface KeybindingOperationRoute {
53
53
  }
54
54
 
55
55
  type KeybindingsOverrideFile = Record<string, unknown>;
56
+ type KeybindingEntry = { readonly action: KeyAction; readonly description: string; readonly combos: KeyCombo[] };
56
57
 
57
58
  interface KeybindingLookup {
58
59
  readonly source: 'actionId' | 'target' | 'key' | 'query';
@@ -93,6 +94,19 @@ function requireKeybindingsManager(context: CommandContext): KeybindingsManager
93
94
  return manager;
94
95
  }
95
96
 
97
+ function readKeybindingsManager(context: CommandContext): KeybindingsManager | null {
98
+ return context.workspace.keybindingsManager ?? null;
99
+ }
100
+
101
+ function defaultKeybindingEntries(): KeybindingEntry[] {
102
+ return (Object.entries(DEFAULT_KEYBINDINGS) as [KeyAction, KeyCombo[]][])
103
+ .map(([action, combos]) => ({
104
+ action,
105
+ description: ACTION_DESCRIPTIONS[action],
106
+ combos: combos.map((combo) => ({ ...combo })),
107
+ }));
108
+ }
109
+
96
110
  function isKeyAction(action: string): action is KeyAction {
97
111
  return Object.hasOwn(ACTION_DESCRIPTIONS, action);
98
112
  }
@@ -162,13 +176,23 @@ function combosEqual(left: readonly KeyCombo[], right: readonly KeyCombo[]): boo
162
176
  return left.every((combo, index) => comboFingerprint(combo) === comboFingerprint(right[index]));
163
177
  }
164
178
 
165
- function describeCombo(manager: KeybindingsManager, combo: KeyCombo): Record<string, unknown> {
179
+ function formatCombo(manager: KeybindingsManager | null, combo: KeyCombo): string {
180
+ if (manager) return manager.formatCombo(combo);
181
+ const parts: string[] = [];
182
+ if (combo.ctrl) parts.push('Ctrl');
183
+ if (combo.alt) parts.push('Alt');
184
+ if (combo.shift) parts.push('Shift');
185
+ parts.push(combo.key.length === 1 ? combo.key.toUpperCase() : combo.key);
186
+ return parts.join('+');
187
+ }
188
+
189
+ function describeCombo(manager: KeybindingsManager | null, combo: KeyCombo): Record<string, unknown> {
166
190
  return {
167
191
  key: combo.key,
168
192
  ctrl: combo.ctrl === true,
169
193
  shift: combo.shift === true,
170
194
  alt: combo.alt === true,
171
- label: manager.formatCombo(combo),
195
+ label: formatCombo(manager, combo),
172
196
  };
173
197
  }
174
198
 
@@ -184,33 +208,33 @@ function keybindingLookupFromArgs(args: HarnessKeybindingArgs): { readonly sourc
184
208
  return null;
185
209
  }
186
210
 
187
- function bindingSearchText(manager: KeybindingsManager, entry: { readonly action: KeyAction; readonly description: string; readonly combos: KeyCombo[] }): string {
211
+ function bindingSearchText(manager: KeybindingsManager | null, entry: KeybindingEntry): string {
188
212
  return [
189
213
  entry.action,
190
214
  entry.description,
191
215
  ...entry.combos.map((combo) => comboFingerprint(combo)),
192
- ...entry.combos.map((combo) => manager.formatCombo(combo)),
216
+ ...entry.combos.map((combo) => formatCombo(manager, combo)),
193
217
  ].join('\n').toLowerCase();
194
218
  }
195
219
 
196
- function bindingCandidate(manager: KeybindingsManager, entry: { readonly action: KeyAction; readonly description: string; readonly combos: KeyCombo[] }): Record<string, unknown> {
220
+ function bindingCandidate(manager: KeybindingsManager | null, entry: KeybindingEntry): Record<string, unknown> {
197
221
  return {
198
222
  action: entry.action,
199
223
  description: entry.description,
200
- labels: entry.combos.map((combo) => manager.formatCombo(combo)),
224
+ labels: entry.combos.map((combo) => formatCombo(manager, combo)),
201
225
  };
202
226
  }
203
227
 
204
- function bindingMatches(manager: KeybindingsManager, entry: { readonly action: KeyAction; readonly description: string; readonly combos: KeyCombo[] }, query: string): boolean {
228
+ function bindingMatches(manager: KeybindingsManager | null, entry: KeybindingEntry, query: string): boolean {
205
229
  if (!query) return true;
206
230
  return bindingSearchText(manager, entry).includes(query.toLowerCase());
207
231
  }
208
232
 
209
233
  function resolveHarnessKeybinding(context: CommandContext, args: HarnessKeybindingArgs): KeybindingResolution | null {
210
- const manager = requireKeybindingsManager(context);
234
+ const manager = readKeybindingsManager(context);
211
235
  const lookup = keybindingLookupFromArgs(args);
212
236
  if (!lookup) return null;
213
- const entries = manager.getAll();
237
+ const entries = manager?.getAll() ?? defaultKeybindingEntries();
214
238
  if (isKeyAction(lookup.input)) return { status: 'found', action: lookup.input, lookup: { ...lookup, resolvedBy: 'action' } };
215
239
  const inputLower = lookup.input.toLowerCase();
216
240
  const ciActions = entries.filter((entry) => entry.action.toLowerCase() === inputLower);
@@ -222,7 +246,7 @@ function resolveHarnessKeybinding(context: CommandContext, args: HarnessKeybindi
222
246
  return null;
223
247
  }
224
248
 
225
- function describeBinding(manager: KeybindingsManager, action: KeyAction, combos: KeyCombo[], lookup?: KeybindingLookup): Record<string, unknown> {
249
+ function describeBinding(manager: KeybindingsManager | null, action: KeyAction, combos: KeyCombo[], lookup?: KeybindingLookup): Record<string, unknown> {
226
250
  const defaults = DEFAULT_KEYBINDINGS[action];
227
251
  const customized = !combosEqual(combos, defaults);
228
252
  return {
@@ -230,10 +254,10 @@ function describeBinding(manager: KeybindingsManager, action: KeyAction, combos:
230
254
  description: ACTION_DESCRIPTIONS[action],
231
255
  ...(lookup ? { lookup } : {}),
232
256
  bindings: combos.map((combo) => describeCombo(manager, combo)),
233
- labels: combos.map((combo) => manager.formatCombo(combo)),
257
+ labels: combos.map((combo) => formatCombo(manager, combo)),
234
258
  defaultBindings: defaults.map((combo) => describeCombo(manager, combo)),
235
259
  customized,
236
- source: customized ? 'custom' : 'default',
260
+ source: manager ? (customized ? 'custom' : 'default') : 'default-fallback',
237
261
  modelOperation: keybindingOperationRoute(action),
238
262
  };
239
263
  }
@@ -377,7 +401,7 @@ function writeOverrideFile(configPath: string, overrides: KeybindingsOverrideFil
377
401
  }
378
402
 
379
403
  export function totalHarnessKeybindings(context: CommandContext): number {
380
- return context.workspace.keybindingsManager?.getAll().length ?? 0;
404
+ return context.workspace.keybindingsManager?.getAll().length ?? Object.keys(DEFAULT_KEYBINDINGS).length;
381
405
  }
382
406
 
383
407
  export function totalHarnessShortcuts(context: CommandContext): number {
@@ -391,41 +415,52 @@ export function listHarnessShortcuts(context: CommandContext, args: HarnessKeybi
391
415
  .filter((shortcut) => !query || `${shortcut.key}\n${shortcut.description}`.toLowerCase().includes(query))
392
416
  .slice(0, readLimit(args.limit, 200))
393
417
  .map((shortcut) => ({ ...shortcut, source: 'fixed', userEditable: false }));
418
+ const degraded = keybindings.status === 'degraded';
394
419
  return {
420
+ status: degraded ? 'degraded' : 'available',
395
421
  configPath: keybindings.configPath,
396
422
  fixedShortcuts: fixed,
397
423
  configurableKeybindings: keybindings.keybindings,
398
424
  returned: fixed.length + Number(keybindings.returned ?? 0),
399
425
  total: totalHarnessShortcuts(context),
400
- policy: 'Fixed shortcuts are runtime/editor controls. Configurable keybindings can be inspected, supported shell-safe actions can be run with run_keybinding, and bindings can be changed with set_keybinding/reset_keybinding.',
426
+ policy: degraded
427
+ ? 'Fixed shortcuts and default keybindings are available for discovery. Live keybinding execution and mutation require the runtime keybinding manager.'
428
+ : 'Fixed shortcuts are runtime/editor controls. Configurable keybindings can be inspected, supported shell-safe actions can be run with run_keybinding, and bindings can be changed with set_keybinding/reset_keybinding.',
401
429
  };
402
430
  }
403
431
 
404
432
  export function listHarnessKeybindings(context: CommandContext, args: HarnessKeybindingArgs): Record<string, unknown> {
405
- const manager = requireKeybindingsManager(context);
433
+ const manager = readKeybindingsManager(context);
406
434
  const query = readString(args.query);
407
- const entries = manager.getAll()
435
+ const allEntries = manager?.getAll() ?? defaultKeybindingEntries();
436
+ const entries = allEntries
408
437
  .filter((entry) => bindingMatches(manager, entry, query))
409
438
  .slice(0, readLimit(args.limit, 200))
410
439
  .map((entry) => describeBinding(manager, entry.action, entry.combos));
411
440
  return {
412
- configPath: manager.getConfigPath(),
441
+ status: manager ? 'available' : 'degraded',
442
+ configPath: manager?.getConfigPath() ?? null,
413
443
  keybindings: entries,
414
444
  returned: entries.length,
415
- total: manager.getAll().length,
416
- policy: 'Reads the live resolved keybindings, including modelOperation route metadata. run_keybinding executes only supported shell-safe actions. set_keybinding/reset_keybinding write the same keybindings.json file the user can edit and reload the runtime manager.',
445
+ total: allEntries.length,
446
+ policy: manager
447
+ ? 'Reads the live resolved keybindings, including modelOperation route metadata. run_keybinding executes only supported shell-safe actions. set_keybinding/reset_keybinding write the same keybindings.json file the user can edit and reload the runtime manager.'
448
+ : 'Live keybinding manager is unavailable; default keybindings are shown for discovery only. run_keybinding, set_keybinding, and reset_keybinding require a live manager.',
417
449
  };
418
450
  }
419
451
 
420
452
  export function describeHarnessKeybinding(context: CommandContext, args: HarnessKeybindingArgs): Record<string, unknown> | null {
421
- const manager = requireKeybindingsManager(context);
453
+ const manager = readKeybindingsManager(context);
422
454
  const resolved = resolveHarnessKeybinding(context, args);
423
455
  if (resolved?.status === 'ambiguous') return { status: 'ambiguous', input: resolved.input, candidates: resolved.candidates };
424
456
  if (!resolved) return null;
425
- const entry = manager.getAll().find((candidate) => candidate.action === resolved.action);
457
+ const entries = manager?.getAll() ?? defaultKeybindingEntries();
458
+ const entry = entries.find((candidate) => candidate.action === resolved.action);
426
459
  return entry ? {
427
- configPath: manager.getConfigPath(),
460
+ status: manager ? 'available' : 'degraded',
461
+ configPath: manager?.getConfigPath() ?? null,
428
462
  ...describeBinding(manager, entry.action, entry.combos, resolved.lookup),
463
+ ...(manager ? {} : { note: 'Default keybinding descriptor only; live run/set/reset operations require the runtime keybinding manager.' }),
429
464
  } : null;
430
465
  }
431
466
 
@@ -0,0 +1,248 @@
1
+ import type { CommandContext } from '../input/command-registry.ts';
2
+
3
+ export interface AgentHarnessMcpArgs {
4
+ readonly mcpServerId?: unknown;
5
+ readonly target?: unknown;
6
+ readonly query?: unknown;
7
+ readonly includeParameters?: unknown;
8
+ readonly limit?: unknown;
9
+ }
10
+
11
+ type McpServerLookupSource = 'mcpServerId' | 'target' | 'query';
12
+
13
+ type McpServerResolution =
14
+ | { readonly status: 'found'; readonly server: Record<string, unknown> }
15
+ | { readonly status: 'ambiguous'; readonly input: string; readonly candidates: readonly Record<string, unknown>[] }
16
+ | { readonly status: 'missing_lookup'; readonly usage: string };
17
+
18
+ type McpServerRecord = ReturnType<NonNullable<NonNullable<CommandContext['clients']>['mcpApi']>['listServerSecurity']>[number];
19
+
20
+ interface McpToolRecord {
21
+ readonly serverName: string;
22
+ readonly toolName: string;
23
+ readonly description?: string;
24
+ }
25
+
26
+ function readString(value: unknown): string {
27
+ return typeof value === 'string' ? value.trim() : '';
28
+ }
29
+
30
+ function readLimit(value: unknown, fallback: number): number {
31
+ const parsed = typeof value === 'string' && value.trim() ? Number(value) : value;
32
+ if (typeof parsed !== 'number' || !Number.isFinite(parsed)) return fallback;
33
+ return Math.max(1, Math.min(500, Math.trunc(parsed)));
34
+ }
35
+
36
+ function resolveMcpApi(context: CommandContext): {
37
+ readonly listServerSecurity: () => readonly McpServerRecord[];
38
+ readonly listAllTools?: (() => Promise<readonly McpToolRecord[]>) | undefined;
39
+ } | null {
40
+ const api = context.clients?.mcpApi ?? context.extensions?.mcpRegistry;
41
+ if (!api || typeof api.listServerSecurity !== 'function') return null;
42
+ return {
43
+ listServerSecurity: () => api.listServerSecurity(),
44
+ ...(typeof (api as { readonly listAllTools?: unknown }).listAllTools === 'function'
45
+ ? { listAllTools: () => (api as { listAllTools: () => Promise<readonly McpToolRecord[]> }).listAllTools() }
46
+ : {}),
47
+ };
48
+ }
49
+
50
+ function lookupFromArgs(args: AgentHarnessMcpArgs): { readonly source: McpServerLookupSource; readonly input: string } | null {
51
+ const mcpServerId = readString(args.mcpServerId);
52
+ if (mcpServerId) return { source: 'mcpServerId', input: mcpServerId };
53
+ const target = readString(args.target);
54
+ if (target) return { source: 'target', input: target };
55
+ const query = readString(args.query);
56
+ return query ? { source: 'query', input: query } : null;
57
+ }
58
+
59
+ function serverSearchText(server: McpServerRecord): string {
60
+ return [
61
+ server.name,
62
+ server.connected ? 'connected' : 'disconnected',
63
+ server.trustMode,
64
+ server.role,
65
+ server.schemaFreshness,
66
+ server.quarantineReason ?? '',
67
+ server.quarantineDetail ?? '',
68
+ ...server.allowedHosts,
69
+ ].join('\n').toLowerCase();
70
+ }
71
+
72
+ function describeCandidate(server: McpServerRecord): Record<string, unknown> {
73
+ return {
74
+ mcpServerId: server.name,
75
+ connected: server.connected,
76
+ trustMode: server.trustMode,
77
+ role: server.role,
78
+ schemaFreshness: server.schemaFreshness,
79
+ };
80
+ }
81
+
82
+ function toolsByServer(tools: readonly McpToolRecord[]): ReadonlyMap<string, readonly McpToolRecord[]> {
83
+ const grouped = new Map<string, McpToolRecord[]>();
84
+ for (const tool of tools) {
85
+ const entries = grouped.get(tool.serverName) ?? [];
86
+ entries.push(tool);
87
+ grouped.set(tool.serverName, entries);
88
+ }
89
+ return grouped;
90
+ }
91
+
92
+ function describeServer(
93
+ server: McpServerRecord,
94
+ options: {
95
+ readonly includeParameters?: boolean;
96
+ readonly tools?: readonly McpToolRecord[];
97
+ readonly lookup?: Record<string, unknown>;
98
+ } = {},
99
+ ): Record<string, unknown> {
100
+ const tools = options.tools ?? [];
101
+ return {
102
+ name: server.name,
103
+ connected: server.connected,
104
+ trustMode: server.trustMode,
105
+ role: server.role,
106
+ schemaFreshness: server.schemaFreshness,
107
+ allowedPathCount: server.allowedPaths.length,
108
+ allowedHostCount: server.allowedHosts.length,
109
+ ...(server.quarantineReason ? { quarantineReason: server.quarantineReason } : {}),
110
+ ...(server.quarantineDetail ? { quarantineDetail: server.quarantineDetail } : {}),
111
+ ...(options.lookup ? { lookup: options.lookup } : {}),
112
+ ...(options.includeParameters ? {
113
+ tools: tools.map((tool) => ({
114
+ name: tool.toolName,
115
+ ...(tool.description ? { description: tool.description } : {}),
116
+ })),
117
+ toolCount: tools.length,
118
+ } : { toolCount: tools.length }),
119
+ ...(options.includeParameters ? {
120
+ policy: {
121
+ effect: 'read-only',
122
+ values: 'Server posture returns trust, role, connection, quarantine, and tool metadata; env values and secret config values are never returned.',
123
+ mutation: 'MCP add/remove/reload/trust/role/quarantine operations stay explicit confirmation-gated workspace or slash-command flows.',
124
+ allowAll: 'allow-all trust decisions remain routed through Settings -> MCP, not direct command escalation.',
125
+ },
126
+ modelAccess: {
127
+ reviewCommand: '/mcp review',
128
+ serversCommand: '/mcp servers',
129
+ toolsCommand: `/mcp tools ${server.name}`,
130
+ repairCommand: `/mcp repair ${server.name}`,
131
+ authReviewCommand: '/mcp auth-review',
132
+ configCommand: '/mcp config',
133
+ workspaceActionIds: [
134
+ 'mcp-review',
135
+ 'mcp-tools-server',
136
+ 'mcp-repair',
137
+ 'mcp-config',
138
+ 'mcp-add-server',
139
+ 'mcp-settings',
140
+ ],
141
+ confirmationGatedCommands: [
142
+ `/mcp trust ${server.name} <constrained|ask-on-risk|blocked> --yes`,
143
+ `/mcp role ${server.name} <role> --yes`,
144
+ `/mcp quarantine ${server.name} approve <operatorId> --yes`,
145
+ `/mcp remove ${server.name} --yes`,
146
+ ],
147
+ },
148
+ } : {}),
149
+ };
150
+ }
151
+
152
+ async function readMcpTools(api: ReturnType<typeof resolveMcpApi>, includeParameters: boolean): Promise<readonly McpToolRecord[]> {
153
+ if (!includeParameters || !api?.listAllTools) return [];
154
+ try {
155
+ return await api.listAllTools();
156
+ } catch {
157
+ return [];
158
+ }
159
+ }
160
+
161
+ export function mcpServerCatalogStatus(context: CommandContext): Record<string, unknown> {
162
+ const api = resolveMcpApi(context);
163
+ if (!api) {
164
+ return {
165
+ modes: ['mcp_servers', 'mcp_server'],
166
+ status: 'unavailable',
167
+ readOnly: true,
168
+ };
169
+ }
170
+ const servers = api.listServerSecurity();
171
+ return {
172
+ modes: ['mcp_servers', 'mcp_server'],
173
+ servers: servers.length,
174
+ connected: servers.filter((server) => server.connected).length,
175
+ attention: servers.filter((server) => !server.connected || server.schemaFreshness === 'quarantined').length,
176
+ readOnly: true,
177
+ };
178
+ }
179
+
180
+ export async function mcpServerSummary(context: CommandContext, args: AgentHarnessMcpArgs): Promise<Record<string, unknown>> {
181
+ const api = resolveMcpApi(context);
182
+ if (!api) {
183
+ return {
184
+ status: 'unavailable',
185
+ servers: [],
186
+ returned: 0,
187
+ total: 0,
188
+ policy: 'MCP runtime API is unavailable in this Agent context.',
189
+ };
190
+ }
191
+ const query = readString(args.query).toLowerCase();
192
+ const includeParameters = args.includeParameters === true;
193
+ const servers = api.listServerSecurity();
194
+ const tools = toolsByServer(await readMcpTools(api, includeParameters));
195
+ const filtered = servers
196
+ .filter((server) => !query || serverSearchText(server).includes(query))
197
+ .slice(0, readLimit(args.limit, 100));
198
+ return {
199
+ servers: filtered.map((server) => describeServer(server, {
200
+ includeParameters,
201
+ tools: tools.get(server.name) ?? [],
202
+ })),
203
+ returned: filtered.length,
204
+ total: servers.length,
205
+ connected: servers.filter((server) => server.connected).length,
206
+ attention: servers.filter((server) => !server.connected || server.schemaFreshness === 'quarantined').length,
207
+ policy: 'Read-only MCP server posture. Use confirmed workspace actions or slash-command mirrors for add/remove/reload/trust/role/quarantine mutations.',
208
+ };
209
+ }
210
+
211
+ export async function describeHarnessMcpServer(context: CommandContext, args: AgentHarnessMcpArgs): Promise<McpServerResolution> {
212
+ const lookup = lookupFromArgs(args);
213
+ if (!lookup) {
214
+ return {
215
+ status: 'missing_lookup',
216
+ usage: 'mcp_server requires mcpServerId, target, or query. Use mode:"mcp_servers" to inspect MCP server ids.',
217
+ };
218
+ }
219
+ const api = resolveMcpApi(context);
220
+ if (!api) {
221
+ return {
222
+ status: 'missing_lookup',
223
+ usage: 'MCP runtime API is unavailable in this Agent context.',
224
+ };
225
+ }
226
+ const servers = api.listServerSecurity();
227
+ const normalized = lookup.input.toLowerCase();
228
+ const exact = servers.find((server) => server.name === lookup.input);
229
+ const tools = toolsByServer(await readMcpTools(api, true));
230
+ if (exact) return { status: 'found', server: describeServer(exact, { includeParameters: true, tools: tools.get(exact.name) ?? [], lookup: { ...lookup, resolvedBy: 'id' } }) };
231
+ const insensitive = servers.find((server) => server.name.toLowerCase() === normalized);
232
+ if (insensitive) return { status: 'found', server: describeServer(insensitive, { includeParameters: true, tools: tools.get(insensitive.name) ?? [], lookup: { ...lookup, resolvedBy: 'case-insensitive-id' } }) };
233
+ const searched = servers.filter((server) => serverSearchText(server).includes(normalized));
234
+ if (searched.length === 1) {
235
+ return { status: 'found', server: describeServer(searched[0]!, { includeParameters: true, tools: tools.get(searched[0]!.name) ?? [], lookup: { ...lookup, resolvedBy: 'search' } }) };
236
+ }
237
+ if (searched.length > 1) {
238
+ return {
239
+ status: 'ambiguous',
240
+ input: lookup.input,
241
+ candidates: searched.slice(0, 8).map(describeCandidate),
242
+ };
243
+ }
244
+ return {
245
+ status: 'missing_lookup',
246
+ usage: `Unknown MCP server ${lookup.input}. Use mode:"mcp_servers" to inspect MCP server ids.`,
247
+ };
248
+ }