@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.
- package/CHANGELOG.md +59 -2
- package/README.md +72 -58
- package/dist/package/main.js +7572 -2430
- package/docs/README.md +25 -21
- package/docs/channels-remote-and-api.md +6 -2
- package/docs/connected-host.md +27 -6
- package/docs/getting-started.md +87 -66
- package/docs/knowledge-artifacts-and-multimodal.md +16 -4
- package/docs/project-planning.md +2 -2
- package/docs/providers-and-routing.md +3 -2
- package/docs/release-and-publishing.md +15 -11
- package/docs/tools-and-commands.md +150 -128
- package/docs/voice-and-live-tts.md +1 -1
- package/package.json +8 -3
- package/release/live-verification/live-verification.json +148 -0
- package/release/live-verification/live-verification.md +187 -0
- package/release/performance-snapshot.json +57 -0
- package/release/release-notes.md +19 -0
- package/release/release-readiness.json +581 -0
- package/src/agent/harness-control.ts +42 -3
- package/src/cli/agent-knowledge-command.ts +5 -5
- package/src/cli/agent-knowledge-format.ts +11 -0
- package/src/cli/agent-knowledge-runtime.ts +92 -13
- package/src/cli/bundle-command.ts +5 -4
- package/src/cli/entrypoint.ts +5 -2
- package/src/cli/external-runtime.ts +2 -15
- package/src/cli/management.ts +4 -3
- package/src/input/commands/guidance-runtime.ts +1 -1
- package/src/input/commands/knowledge.ts +2 -2
- package/src/runtime/bootstrap.ts +10 -18
- package/src/runtime/connected-host-auth.ts +16 -0
- package/src/tools/agent-analysis-registry-policy.ts +2 -9
- package/src/tools/agent-channel-send-tool.ts +3 -9
- package/src/tools/agent-context-policy.ts +1 -5
- package/src/tools/agent-find-policy.ts +1 -4
- package/src/tools/agent-harness-channel-metadata.ts +177 -0
- package/src/tools/agent-harness-cli-metadata.ts +4 -1
- package/src/tools/agent-harness-command-catalog.ts +10 -3
- package/src/tools/agent-harness-connected-host-status.ts +9 -3
- package/src/tools/agent-harness-delegation-posture.ts +216 -0
- package/src/tools/agent-harness-keybinding-metadata.ts +57 -22
- package/src/tools/agent-harness-mcp-metadata.ts +248 -0
- package/src/tools/agent-harness-media-posture.ts +282 -0
- package/src/tools/agent-harness-metadata.ts +44 -9
- package/src/tools/agent-harness-model-routing.ts +501 -0
- package/src/tools/agent-harness-model-tool-catalog.ts +7 -2
- package/src/tools/agent-harness-notification-metadata.ts +217 -0
- package/src/tools/agent-harness-operator-methods.ts +285 -0
- package/src/tools/agent-harness-pairing-posture.ts +265 -0
- package/src/tools/agent-harness-panel-metadata.ts +26 -12
- package/src/tools/agent-harness-provider-account-metadata.ts +205 -0
- package/src/tools/agent-harness-release-evidence.ts +364 -0
- package/src/tools/agent-harness-release-readiness.ts +298 -0
- package/src/tools/agent-harness-security-posture.ts +648 -0
- package/src/tools/agent-harness-service-posture.ts +207 -0
- package/src/tools/agent-harness-session-metadata.ts +284 -0
- package/src/tools/agent-harness-setup-posture.ts +295 -0
- package/src/tools/agent-harness-tool-schema.ts +104 -27
- package/src/tools/agent-harness-tool.ts +251 -235
- package/src/tools/agent-harness-ui-surface-metadata.ts +20 -12
- package/src/tools/agent-harness-workspace-actions.ts +260 -0
- package/src/tools/agent-knowledge-ingest-tool.ts +4 -10
- package/src/tools/agent-knowledge-tool.ts +120 -25
- package/src/tools/agent-local-registry-tool.ts +3 -7
- package/src/tools/agent-media-generate-tool.ts +2 -8
- package/src/tools/agent-notify-tool.ts +3 -8
- package/src/tools/agent-operator-action-tool.ts +4 -10
- package/src/tools/agent-operator-briefing-tool.ts +1 -6
- package/src/tools/agent-read-policy.ts +1 -4
- package/src/tools/agent-reminder-schedule-tool.ts +4 -9
- package/src/tools/agent-tool-policy-guard.ts +15 -51
- package/src/tools/agent-web-search-policy.ts +1 -4
- package/src/tools/agent-work-plan-tool.ts +1 -6
- 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
|
|
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:
|
|
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:
|
|
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) =>
|
|
216
|
+
...entry.combos.map((combo) => formatCombo(manager, combo)),
|
|
193
217
|
].join('\n').toLowerCase();
|
|
194
218
|
}
|
|
195
219
|
|
|
196
|
-
function bindingCandidate(manager: KeybindingsManager, entry:
|
|
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) =>
|
|
224
|
+
labels: entry.combos.map((combo) => formatCombo(manager, combo)),
|
|
201
225
|
};
|
|
202
226
|
}
|
|
203
227
|
|
|
204
|
-
function bindingMatches(manager: KeybindingsManager, entry:
|
|
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 =
|
|
234
|
+
const manager = readKeybindingsManager(context);
|
|
211
235
|
const lookup = keybindingLookupFromArgs(args);
|
|
212
236
|
if (!lookup) return null;
|
|
213
|
-
const entries = manager
|
|
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) =>
|
|
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 ??
|
|
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:
|
|
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 =
|
|
433
|
+
const manager = readKeybindingsManager(context);
|
|
406
434
|
const query = readString(args.query);
|
|
407
|
-
const
|
|
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
|
-
|
|
441
|
+
status: manager ? 'available' : 'degraded',
|
|
442
|
+
configPath: manager?.getConfigPath() ?? null,
|
|
413
443
|
keybindings: entries,
|
|
414
444
|
returned: entries.length,
|
|
415
|
-
total:
|
|
416
|
-
policy:
|
|
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 =
|
|
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
|
|
457
|
+
const entries = manager?.getAll() ?? defaultKeybindingEntries();
|
|
458
|
+
const entry = entries.find((candidate) => candidate.action === resolved.action);
|
|
426
459
|
return entry ? {
|
|
427
|
-
|
|
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
|
+
}
|