@pellux/goodvibes-agent 0.1.117 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -4
- package/README.md +35 -14
- package/bin/goodvibes-agent.ts +16 -2
- package/dist/package/main.js +176073 -170980
- package/docs/README.md +11 -5
- package/docs/channels-remote-and-api.md +50 -0
- package/docs/connected-host.md +3 -3
- package/docs/getting-started.md +29 -15
- package/docs/knowledge-artifacts-and-multimodal.md +91 -0
- package/docs/project-planning.md +79 -0
- package/docs/providers-and-routing.md +46 -0
- package/docs/release-and-publishing.md +44 -9
- package/docs/tools-and-commands.md +123 -0
- package/docs/voice-and-live-tts.md +51 -0
- package/package.json +2 -5
- package/src/agent/channel-delivery.ts +201 -0
- package/src/agent/media-generation.ts +159 -0
- package/src/agent/memory-prompt.ts +0 -1
- package/src/agent/note-registry.ts +329 -0
- package/src/agent/operator-actions.ts +343 -0
- package/src/agent/persona-registry.ts +15 -14
- package/src/agent/record-labels.ts +107 -0
- package/src/agent/reminder-schedule-format.ts +33 -24
- package/src/agent/reminder-schedule.ts +26 -25
- package/src/agent/routine-registry.ts +13 -12
- package/src/agent/routine-schedule-args.ts +2 -1
- package/src/agent/routine-schedule-format.ts +77 -53
- package/src/agent/routine-schedule-promotion.ts +34 -32
- package/src/agent/routine-schedule-receipts.ts +28 -26
- package/src/agent/runtime-profile-starters.ts +2 -2
- package/src/agent/runtime-profile.ts +18 -17
- package/src/agent/skill-registry.ts +25 -24
- package/src/cli/agent-knowledge-args.ts +5 -1
- package/src/cli/agent-knowledge-command.ts +39 -33
- package/src/cli/agent-knowledge-format.ts +80 -67
- package/src/cli/agent-knowledge-methods.ts +1 -1
- package/src/cli/agent-knowledge-runtime.ts +32 -26
- package/src/cli/bundle-command.ts +13 -8
- package/src/cli/config-overrides.ts +37 -36
- package/src/cli/external-runtime.ts +10 -4
- package/src/cli/help.ts +29 -11
- package/src/cli/local-library-command.ts +134 -68
- package/src/cli/management-commands.ts +98 -62
- package/src/cli/management.ts +52 -26
- package/src/cli/memory-command.ts +66 -32
- package/src/cli/parser.ts +37 -24
- package/src/cli/profiles-command.ts +52 -35
- package/src/cli/provider-auth-routes.ts +2 -1
- package/src/cli/routines-command.ts +44 -36
- package/src/cli/service-posture.ts +6 -6
- package/src/cli/status.ts +46 -121
- package/src/core/conversation-message-snapshot.ts +131 -0
- package/src/input/agent-workspace-activation.ts +33 -7
- package/src/input/agent-workspace-basic-command-editor-submission.ts +7 -3
- package/src/input/agent-workspace-basic-command-editors.ts +30 -10
- package/src/input/agent-workspace-categories.ts +276 -64
- package/src/input/agent-workspace-channel-command-editor-submission.ts +34 -0
- package/src/input/agent-workspace-channel-command-editors.ts +23 -5
- package/src/input/agent-workspace-channels.ts +35 -2
- package/src/input/agent-workspace-command-editor.ts +18 -2
- package/src/input/agent-workspace-config-reader.ts +16 -0
- package/src/input/agent-workspace-delegation-editor-submission.ts +1 -1
- package/src/input/agent-workspace-editors.ts +140 -2
- package/src/input/agent-workspace-knowledge-query-editor.ts +1 -1
- package/src/input/agent-workspace-learned-behavior.ts +2 -2
- package/src/input/agent-workspace-library-command-editor-submission.ts +1 -1
- package/src/input/agent-workspace-library-command-editors.ts +2 -2
- package/src/input/agent-workspace-local-operations.ts +218 -0
- package/src/input/agent-workspace-local-selection.ts +75 -0
- package/src/input/agent-workspace-media-command-editor-submission.ts +62 -0
- package/src/input/agent-workspace-media-command-editors.ts +27 -0
- package/src/input/agent-workspace-memory-command-editors.ts +1 -1
- package/src/input/agent-workspace-memory-editor.ts +2 -2
- package/src/input/agent-workspace-navigation.ts +38 -2
- package/src/input/agent-workspace-notify-editor-submission.ts +16 -2
- package/src/input/agent-workspace-operations-command-editor-submission.ts +63 -1
- package/src/input/agent-workspace-operations-command-editors.ts +80 -3
- package/src/input/agent-workspace-panel-route.ts +43 -0
- package/src/input/agent-workspace-reminder-schedule-editor.ts +1 -1
- package/src/input/agent-workspace-routine-schedule-editor.ts +2 -2
- package/src/input/agent-workspace-search.ts +169 -0
- package/src/input/agent-workspace-setup.ts +22 -11
- package/src/input/agent-workspace-skill-bundle-command-editor-submission.ts +6 -6
- package/src/input/agent-workspace-skill-bundle-command-editors.ts +10 -10
- package/src/input/agent-workspace-snapshot.ts +112 -13
- package/src/input/agent-workspace-task-command-editors.ts +4 -4
- package/src/input/agent-workspace-token.ts +18 -2
- package/src/input/agent-workspace-types.ts +92 -4
- package/src/input/agent-workspace-voice-media.ts +3 -6
- package/src/input/agent-workspace-web-research-editor.ts +104 -0
- package/src/input/agent-workspace.ts +136 -208
- package/src/input/command-registry.ts +4 -1
- package/src/input/commands/agent-runtime-profile-runtime.ts +45 -41
- package/src/input/commands/agent-skills-runtime.ts +83 -70
- package/src/input/commands/agent-workspace-runtime.ts +23 -7
- package/src/input/commands/brief-runtime.ts +25 -24
- package/src/input/commands/channels-runtime.ts +145 -31
- package/src/input/commands/delegation-runtime.ts +29 -23
- package/src/input/commands/experience-runtime.ts +6 -4
- package/src/input/commands/guidance-runtime.ts +4 -4
- package/src/input/commands/health-runtime.ts +140 -115
- package/src/input/commands/knowledge.ts +57 -56
- package/src/input/commands/local-provider-runtime.ts +36 -18
- package/src/input/commands/local-runtime.ts +138 -16
- package/src/input/commands/local-setup-review.ts +7 -7
- package/src/input/commands/mcp-runtime.ts +56 -35
- package/src/input/commands/notify-runtime.ts +38 -9
- package/src/input/commands/operator-actions-runtime.ts +138 -0
- package/src/input/commands/operator-runtime.ts +6 -17
- package/src/input/commands/personas-runtime.ts +45 -30
- package/src/input/commands/planning-runtime.ts +1 -1
- package/src/input/commands/platform-access-runtime.ts +29 -29
- package/src/input/commands/provider-accounts-runtime.ts +60 -39
- package/src/input/commands/qrcode-runtime.ts +45 -6
- package/src/input/commands/recall-bundle.ts +11 -11
- package/src/input/commands/recall-capture.ts +13 -11
- package/src/input/commands/recall-query.ts +29 -21
- package/src/input/commands/recall-review.ts +2 -1
- package/src/input/commands/routines-runtime.ts +59 -43
- package/src/input/commands/schedule-runtime.ts +33 -20
- package/src/input/commands/security-runtime.ts +4 -4
- package/src/input/commands/session-content.ts +80 -78
- package/src/input/commands/session-workflow.ts +132 -93
- package/src/input/commands/session.ts +3 -174
- package/src/input/commands/shell-core.ts +32 -17
- package/src/input/commands/subscription-runtime.ts +53 -179
- package/src/input/commands/tasks-runtime.ts +20 -20
- package/src/input/commands/work-plan-runtime.ts +33 -8
- package/src/input/commands.ts +2 -2
- package/src/input/feed-context-factory.ts +2 -1
- package/src/input/file-picker.ts +3 -2
- package/src/input/handler-command-route.ts +4 -7
- package/src/input/handler-content-actions.ts +89 -1
- package/src/input/handler-feed-routes.ts +19 -12
- package/src/input/handler-feed.ts +6 -3
- package/src/input/handler-interactions.ts +7 -5
- package/src/input/handler-modal-stack.ts +3 -3
- package/src/input/handler-onboarding.ts +24 -80
- package/src/input/handler-shortcuts.ts +15 -4
- package/src/input/handler.ts +47 -17
- package/src/input/input-history.ts +5 -6
- package/src/input/keybindings.ts +22 -11
- package/src/input/onboarding/onboarding-wizard-apply.ts +13 -0
- package/src/input/onboarding/onboarding-wizard-constants.ts +1 -0
- package/src/input/onboarding/onboarding-wizard-operator-steps.ts +147 -57
- package/src/input/onboarding/onboarding-wizard-steps.ts +46 -29
- package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
- package/src/input/onboarding/onboarding-wizard.ts +68 -0
- package/src/input/profile-picker-modal.ts +31 -12
- package/src/input/session-picker-modal.ts +21 -4
- package/src/input/settings-modal-behavior.ts +0 -3
- package/src/input/settings-modal-subscriptions.ts +3 -3
- package/src/input/settings-modal-types.ts +2 -13
- package/src/input/settings-modal.ts +6 -52
- package/src/input/submission-intent.ts +0 -1
- package/src/input/submission-router.ts +3 -3
- package/src/main.ts +18 -8
- package/src/panels/approval-panel.ts +8 -8
- package/src/panels/automation-control-panel.ts +2 -2
- package/src/panels/base-panel.ts +1 -1
- package/src/panels/builtin/agent.ts +1 -1
- package/src/panels/builtin/operations.ts +1 -10
- package/src/panels/builtin/session.ts +9 -9
- package/src/panels/builtin/shared.ts +2 -2
- package/src/panels/cost-tracker-panel.ts +1 -1
- package/src/panels/docs-panel.ts +5 -3
- package/src/panels/index.ts +0 -1
- package/src/panels/knowledge-panel.ts +6 -5
- package/src/panels/memory-panel.ts +7 -6
- package/src/panels/panel-list-panel.ts +36 -80
- package/src/panels/project-planning-panel.ts +18 -11
- package/src/panels/provider-account-snapshot.ts +51 -25
- package/src/panels/provider-accounts-panel.ts +33 -18
- package/src/panels/provider-health-domains.ts +45 -4
- package/src/panels/provider-health-panel.ts +5 -4
- package/src/panels/qr-panel.ts +1 -1
- package/src/panels/schedule-panel.ts +9 -17
- package/src/panels/security-panel.ts +8 -8
- package/src/panels/session-browser-panel.ts +10 -10
- package/src/panels/subscription-panel.ts +3 -3
- package/src/panels/system-messages-panel.ts +1 -1
- package/src/panels/tasks-panel.ts +20 -13
- package/src/panels/tool-inspector-panel.ts +19 -12
- package/src/panels/work-plan-panel.ts +5 -5
- package/src/planning/project-planning-coordinator.ts +1 -1
- package/src/provider-auth-route-display.ts +9 -0
- package/src/renderer/agent-workspace-style.ts +34 -0
- package/src/renderer/agent-workspace.ts +228 -52
- package/src/renderer/autocomplete-overlay.ts +25 -6
- package/src/renderer/bookmark-modal.ts +19 -4
- package/src/renderer/buffer.ts +4 -2
- package/src/renderer/context-inspector.ts +50 -13
- package/src/renderer/diff.ts +1 -1
- package/src/renderer/file-picker-overlay.ts +19 -6
- package/src/renderer/help-overlay.ts +106 -33
- package/src/renderer/history-search-overlay.ts +19 -4
- package/src/renderer/live-tail-modal.ts +27 -5
- package/src/renderer/mcp-workspace.ts +164 -50
- package/src/renderer/model-picker-overlay.ts +58 -2
- package/src/renderer/model-workspace.ts +104 -20
- package/src/renderer/process-modal.ts +27 -6
- package/src/renderer/profile-picker-modal.ts +20 -4
- package/src/renderer/search-overlay.ts +25 -5
- package/src/renderer/selection-modal-overlay.ts +46 -14
- package/src/renderer/session-picker-modal.ts +18 -1
- package/src/renderer/settings-modal-helpers.ts +2 -40
- package/src/renderer/settings-modal.ts +83 -50
- package/src/renderer/tool-call.ts +20 -11
- package/src/runtime/agent-runtime-events.ts +6 -6
- package/src/runtime/bootstrap-command-context.ts +7 -1
- package/src/runtime/bootstrap-command-parts.ts +9 -7
- package/src/runtime/bootstrap-core.ts +19 -1
- package/src/runtime/bootstrap-hook-bridge.ts +7 -18
- package/src/runtime/bootstrap-shell.ts +4 -4
- package/src/runtime/bootstrap.ts +31 -22
- package/src/runtime/connected-host-auth.ts +7 -2
- package/src/runtime/diagnostics/panels/index.ts +2 -2
- package/src/runtime/diagnostics/panels/policy.ts +7 -7
- package/src/runtime/index.ts +2 -1
- package/src/runtime/onboarding/apply-file-helpers.ts +66 -0
- package/src/runtime/onboarding/apply.ts +80 -79
- package/src/runtime/onboarding/derivation.ts +5 -8
- package/src/runtime/onboarding/snapshot.ts +0 -15
- package/src/runtime/onboarding/types.ts +8 -19
- package/src/runtime/onboarding/verify.ts +32 -10
- package/src/runtime/operator-token-cleanup.ts +1 -1
- package/src/runtime/services.ts +91 -22
- package/src/runtime/store/selectors/index.ts +3 -3
- package/src/runtime/store/state.ts +1 -4
- package/src/runtime/surface-feature-flags.ts +41 -6
- package/src/runtime/ui-read-models.ts +3 -4
- package/src/runtime/ui-services.ts +6 -6
- package/src/shell/blocking-input.ts +2 -1
- package/src/shell/ui-openers.ts +3 -13
- package/src/tools/agent-analysis-registry-policy.ts +0 -1
- package/src/tools/agent-channel-send-tool.ts +133 -0
- package/src/tools/agent-context-policy.ts +1 -1
- package/src/tools/agent-knowledge-ingest-tool.ts +405 -0
- package/src/tools/agent-knowledge-tool.ts +170 -0
- package/src/tools/agent-local-registry-tool.ts +269 -69
- package/src/tools/agent-media-generate-tool.ts +133 -0
- package/src/tools/agent-notify-tool.ts +143 -0
- package/src/tools/agent-operator-action-tool.ts +137 -0
- package/src/tools/agent-operator-briefing-tool.ts +217 -0
- package/src/tools/agent-reminder-schedule-tool.ts +237 -0
- package/src/tools/agent-tool-policy-guard.ts +8 -8
- package/src/tools/agent-work-plan-tool.ts +256 -0
- package/src/version.ts +1 -1
- package/src/input/commands/policy-dispatch.ts +0 -339
- package/src/input/commands/policy.ts +0 -13
- package/src/panels/panel-picker.ts +0 -105
- package/src/panels/policy-panel.ts +0 -308
- 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
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
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' :
|
|
82
|
+
severity: (services.length === 0 && oauthProviderCount === 0 && builtinSubscriptionProviderCount === 0) ? 'warn' : hostIntegrationIssues.length === 0 ? 'pass' : 'warn',
|
|
83
83
|
area: 'host',
|
|
84
84
|
message: (services.length === 0 && oauthProviderCount === 0 && builtinSubscriptionProviderCount === 0)
|
|
85
85
|
? 'no connected host integrations configured'
|
|
86
|
-
:
|
|
86
|
+
: hostIntegrationIssues.length === 0
|
|
87
87
|
? `${services.length} host integration(s), ${oauthProviderCount + builtinSubscriptionProviderCount} oauth provider(s), ${activeSubscriptionCount} active subscription override(s)`
|
|
88
|
-
: `${
|
|
88
|
+
: `${hostIntegrationIssues.length} host integration configuration issue(s)`,
|
|
89
89
|
},
|
|
90
90
|
{
|
|
91
91
|
severity: quarantinedPluginCount === 0 ? 'pass' : 'warn',
|
|
@@ -111,7 +111,7 @@ export async function buildSetupReviewSnapshot(ctx: CommandContext): Promise<Set
|
|
|
111
111
|
builtinSubscriptionProviderCount,
|
|
112
112
|
activeSubscriptionCount,
|
|
113
113
|
pendingSubscriptionCount,
|
|
114
|
-
|
|
114
|
+
hostIntegrationIssues,
|
|
115
115
|
skillCount: skills.length,
|
|
116
116
|
pluginCount: plugins.length,
|
|
117
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
|
|
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
|
|
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}
|
|
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'));
|
|
@@ -218,7 +238,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
218
238
|
? [
|
|
219
239
|
'MCP Auth Review',
|
|
220
240
|
...needingAttention.map((server) => (
|
|
221
|
-
` ${server.name} connected
|
|
241
|
+
` ${server.name} connected ${server.connected ? 'yes' : 'no'} status ${formatMcpFreshness(server.schemaFreshness)} trust ${formatMcpTrustMode(server.trustMode)}`
|
|
222
242
|
)),
|
|
223
243
|
' next: /auth review',
|
|
224
244
|
' next: /mcp repair <server>',
|
|
@@ -233,7 +253,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
233
253
|
const selected = serverName ? servers.find((server) => server.name === serverName) : servers.find((server) => !server.connected || server.schemaFreshness === 'quarantined');
|
|
234
254
|
if (!selected) {
|
|
235
255
|
ctx.print(serverName
|
|
236
|
-
? `Unknown MCP server
|
|
256
|
+
? `Unknown MCP server ${serverName}`
|
|
237
257
|
: 'MCP Repair\n No MCP server currently needs repair.');
|
|
238
258
|
return;
|
|
239
259
|
}
|
|
@@ -248,9 +268,9 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
248
268
|
ctx.print([
|
|
249
269
|
`MCP Repair: ${selected.name}`,
|
|
250
270
|
` connected: ${selected.connected ? 'yes' : 'no'}`,
|
|
251
|
-
` trust: ${selected.trustMode}`,
|
|
252
|
-
` role: ${selected.role}`,
|
|
253
|
-
`
|
|
271
|
+
` trust: ${formatMcpTrustMode(selected.trustMode)}`,
|
|
272
|
+
` role: ${formatMcpRole(selected.role)}`,
|
|
273
|
+
` status: ${formatMcpFreshness(selected.schemaFreshness)}`,
|
|
254
274
|
...(selected.quarantineReason ? [` quarantine: ${selected.quarantineReason}`] : []),
|
|
255
275
|
...(selected.quarantineDetail ? [` detail: ${selected.quarantineDetail}`] : []),
|
|
256
276
|
' next:',
|
|
@@ -273,7 +293,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
273
293
|
return;
|
|
274
294
|
}
|
|
275
295
|
mcpApi.setServerTrustMode(serverName, mode);
|
|
276
|
-
ctx.print(`Updated MCP trust mode for ${serverName} to ${mode}.`);
|
|
296
|
+
ctx.print(`Updated MCP trust mode for ${serverName} to ${formatMcpTrustMode(mode)}.`);
|
|
277
297
|
return;
|
|
278
298
|
}
|
|
279
299
|
if (serverName || mode) {
|
|
@@ -291,7 +311,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
291
311
|
return;
|
|
292
312
|
}
|
|
293
313
|
mcpApi.setServerRole(serverName, role);
|
|
294
|
-
ctx.print(`Updated MCP role for ${serverName} to ${role}.`);
|
|
314
|
+
ctx.print(`Updated MCP role for ${serverName} to ${formatMcpRole(role)}.`);
|
|
295
315
|
return;
|
|
296
316
|
}
|
|
297
317
|
if (serverName || role) {
|
|
@@ -317,13 +337,16 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
317
337
|
const result = await mcpApi.upsertServerConfig(shellPaths, parsedAdd.scope, parsedAdd.server);
|
|
318
338
|
const connected = listServerSecurity().find((entry) => entry.name === parsedAdd.server.name)?.connected ?? false;
|
|
319
339
|
ctx.print([
|
|
320
|
-
`MCP server "${parsedAdd.server.name}" saved
|
|
321
|
-
`
|
|
322
|
-
`
|
|
323
|
-
'
|
|
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',
|
|
324
347
|
].join('\n'));
|
|
325
348
|
} catch (error) {
|
|
326
|
-
ctx.print(`MCP add failed
|
|
349
|
+
ctx.print(`MCP add failed ${summarizeError(error)}`);
|
|
327
350
|
}
|
|
328
351
|
return;
|
|
329
352
|
}
|
|
@@ -359,10 +382,10 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
359
382
|
try {
|
|
360
383
|
const result = await mcpApi.removeServerConfig(shellPaths, scope, serverName);
|
|
361
384
|
ctx.print(result.removed
|
|
362
|
-
? `Removed MCP server "${serverName}" from ${scope} config ${result.path}. Reload
|
|
385
|
+
? `Removed MCP server "${serverName}" from ${scope} config ${result.path}. Reload +${result.reload.added} ~${result.reload.changed} -${result.reload.removed}, unchanged ${result.reload.unchanged}.`
|
|
363
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.`);
|
|
364
387
|
} catch (error) {
|
|
365
|
-
ctx.print(`MCP remove failed
|
|
388
|
+
ctx.print(`MCP remove failed ${summarizeError(error)}`);
|
|
366
389
|
}
|
|
367
390
|
return;
|
|
368
391
|
}
|
|
@@ -375,9 +398,9 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
375
398
|
try {
|
|
376
399
|
const result = await reloadMcpRuntime(ctx);
|
|
377
400
|
const servers = listServerSecurity();
|
|
378
|
-
ctx.print(`Reloaded MCP runtime from config. ${servers.filter((server) => server.connected).length}/${servers.length} server(s) connected. Result
|
|
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}.`);
|
|
379
402
|
} catch (error) {
|
|
380
|
-
ctx.print(`MCP reload failed
|
|
403
|
+
ctx.print(`MCP reload failed ${summarizeError(error)}`);
|
|
381
404
|
}
|
|
382
405
|
return;
|
|
383
406
|
}
|
|
@@ -388,22 +411,22 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
388
411
|
const effective = mcpApi.getEffectiveConfig(shellPaths);
|
|
389
412
|
ctx.print([
|
|
390
413
|
'MCP Config',
|
|
391
|
-
' locations
|
|
414
|
+
' locations',
|
|
392
415
|
...effective.locations.map((location) => ` ${location.scope}/${location.kind}${location.writable ? ' writable' : ' read-only'} ${location.path}`),
|
|
393
|
-
` effective servers
|
|
416
|
+
` effective servers ${effective.servers.length}`,
|
|
394
417
|
...effective.servers.map((entry) => {
|
|
395
418
|
const server = entry.server;
|
|
396
419
|
const envKeys = Object.keys(server.env ?? {});
|
|
397
|
-
return ` - ${server.name}: ${server.command}${server.args?.length ? ` ${server.args.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(',')}` : ''}`;
|
|
398
421
|
}),
|
|
399
422
|
'',
|
|
400
|
-
'Add or update from inside Agent with explicit confirmation
|
|
423
|
+
'Add or update from inside Agent with explicit confirmation',
|
|
401
424
|
' Open /mcp and choose Add or update server, or use Agent Workspace -> Tools & MCP -> Add MCP server.',
|
|
402
|
-
'Automation equivalent
|
|
425
|
+
'Automation equivalent',
|
|
403
426
|
' /mcp add <name> <command> [args...] [--scope project|global] [--role <role>] [--trust <mode>] --yes',
|
|
404
427
|
].join('\n'));
|
|
405
428
|
} catch (error) {
|
|
406
|
-
ctx.print(`MCP config read failed
|
|
429
|
+
ctx.print(`MCP config read failed ${summarizeError(error)}`);
|
|
407
430
|
}
|
|
408
431
|
return;
|
|
409
432
|
}
|
|
@@ -417,12 +440,12 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
417
440
|
}
|
|
418
441
|
if (action === 'approve') {
|
|
419
442
|
if (!confirmation.yes) {
|
|
420
|
-
requireYesFlag(ctx, `approve MCP
|
|
443
|
+
requireYesFlag(ctx, `approve MCP tool-definition quarantine override for ${serverName}`, '/mcp quarantine <server> approve [operatorId] --yes');
|
|
421
444
|
return;
|
|
422
445
|
}
|
|
423
446
|
const operatorId = commandArgs[3] || 'operator';
|
|
424
447
|
mcpApi.approveSchemaQuarantine(serverName, operatorId);
|
|
425
|
-
ctx.print(`Approved MCP
|
|
448
|
+
ctx.print(`Approved MCP tool-definition quarantine override for ${serverName} as ${operatorId}. Refresh is still recommended.`);
|
|
426
449
|
return;
|
|
427
450
|
}
|
|
428
451
|
if (!confirmation.yes) {
|
|
@@ -431,7 +454,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
431
454
|
}
|
|
432
455
|
const detail = commandArgs.slice(2).join(' ') || 'quarantined by operator';
|
|
433
456
|
mcpApi.quarantineSchema(serverName, 'operator_flagged', detail);
|
|
434
|
-
ctx.print(`Quarantined MCP
|
|
457
|
+
ctx.print(`Quarantined MCP tool definitions for ${serverName}.\nReason: ${detail}`);
|
|
435
458
|
return;
|
|
436
459
|
}
|
|
437
460
|
|
|
@@ -456,18 +479,16 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
456
479
|
});
|
|
457
480
|
}
|
|
458
481
|
|
|
459
|
-
type McpSecurityServer = ReturnType<NonNullable<NonNullable<CommandContext['clients']>['mcpApi']>['listServerSecurity']>[number];
|
|
460
|
-
|
|
461
482
|
function formatMcpServerList(servers: readonly McpSecurityServer[]): string {
|
|
462
483
|
const connected = servers.filter(s => s.connected);
|
|
463
484
|
const disconnected = servers.filter(s => !s.connected);
|
|
464
485
|
const lines: string[] = [`MCP Servers (${connected.length}/${servers.length} connected):`];
|
|
465
486
|
for (const s of servers) {
|
|
466
|
-
const pathScope = s.allowedPaths.length > 0 ? ` paths
|
|
467
|
-
const hostScope = s.allowedHosts.length > 0 ? ` hosts
|
|
468
|
-
const freshness = `
|
|
469
|
-
const quarantine = s.schemaFreshness === 'quarantined' ? `
|
|
470
|
-
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}`);
|
|
471
492
|
}
|
|
472
493
|
if (connected.length > 0) {
|
|
473
494
|
lines.push('');
|
|
@@ -6,9 +6,9 @@ export function registerNotifyRuntimeCommands(registry: CommandRegistry): void {
|
|
|
6
6
|
registry.register({
|
|
7
7
|
name: 'notify',
|
|
8
8
|
aliases: [],
|
|
9
|
-
description: 'Manage
|
|
10
|
-
usage: 'add <url> --yes | remove <url> --yes | list | clear --yes | test --yes',
|
|
11
|
-
argsHint: 'list|add --yes|remove --yes|test --yes',
|
|
9
|
+
description: 'Manage and send configured Agent webhook notifications',
|
|
10
|
+
usage: 'add <url> --yes | remove <url> --yes | list | clear --yes | test --yes | send <message> --yes',
|
|
11
|
+
argsHint: 'list|add --yes|remove --yes|test --yes|send --yes',
|
|
12
12
|
async handler(args, ctx) {
|
|
13
13
|
const parsed = stripYesFlag(args);
|
|
14
14
|
const commandArgs = [...parsed.rest];
|
|
@@ -18,8 +18,8 @@ export function registerNotifyRuntimeCommands(registry: CommandRegistry): void {
|
|
|
18
18
|
const sub = commandArgs[0];
|
|
19
19
|
|
|
20
20
|
if (!sub || sub === 'list') {
|
|
21
|
-
if (urls.length === 0) ctx.print('No webhook URLs configured.\nUse
|
|
22
|
-
else ctx.print(`Webhook URLs (${urls.length})
|
|
21
|
+
if (urls.length === 0) ctx.print('No webhook URLs configured.\nUse /notify add <url>');
|
|
22
|
+
else ctx.print(`Webhook URLs (${urls.length})\n${urls.map((u, i) => ` ${i + 1}. ${u}`).join('\n')}`);
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -34,11 +34,11 @@ export function registerNotifyRuntimeCommands(registry: CommandRegistry): void {
|
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
36
|
try { new URL(url); } catch {
|
|
37
|
-
ctx.print(`Invalid URL
|
|
37
|
+
ctx.print(`Invalid URL ${url}`);
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
40
|
if (urls.includes(url)) {
|
|
41
|
-
ctx.print(`Already configured
|
|
41
|
+
ctx.print(`Already configured ${url}`);
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
44
|
urls.push(url);
|
|
@@ -86,7 +86,7 @@ export function registerNotifyRuntimeCommands(registry: CommandRegistry): void {
|
|
|
86
86
|
return;
|
|
87
87
|
}
|
|
88
88
|
if (urls.length === 0) {
|
|
89
|
-
ctx.print('No webhook URLs configured. Use
|
|
89
|
+
ctx.print('No webhook URLs configured. Use /notify add <url>');
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
92
92
|
ctx.print(`Testing ${urls.length} webhook${urls.length !== 1 ? 's' : ''}...`);
|
|
@@ -96,7 +96,36 @@ export function registerNotifyRuntimeCommands(registry: CommandRegistry): void {
|
|
|
96
96
|
return;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
if (sub === 'send') {
|
|
100
|
+
const message = commandArgs.slice(1).join(' ').trim();
|
|
101
|
+
if (!message) {
|
|
102
|
+
ctx.print('Usage: /notify send <message> --yes');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (!parsed.yes) {
|
|
106
|
+
requireYesFlag(ctx, 'send a notification to configured webhook targets', '/notify send <message> --yes');
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (urls.length === 0) {
|
|
110
|
+
ctx.print('No webhook URLs configured. Use /notify add <url>');
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
notifier.setUrls(urls);
|
|
114
|
+
const result = await notifier.send(message);
|
|
115
|
+
ctx.print([
|
|
116
|
+
'Notification sent',
|
|
117
|
+
` attempted ${result.attempted}`,
|
|
118
|
+
` delivered ${result.delivered}`,
|
|
119
|
+
` failed ${result.failed}`,
|
|
120
|
+
...result.results.slice(0, 20).map((delivery, index) => (
|
|
121
|
+
` target ${index + 1} ${delivery.ok ? 'ok' : `failed${delivery.error ? ` (${delivery.error})` : ''}`}`
|
|
122
|
+
)),
|
|
123
|
+
...(result.results.length > 20 ? [` ${result.results.length - 20} more target(s) omitted.`] : []),
|
|
124
|
+
].join('\n'));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
ctx.print('Usage: /notify add <url> --yes | remove <url> --yes | list | clear --yes | test --yes | send <message> --yes');
|
|
100
129
|
},
|
|
101
130
|
});
|
|
102
131
|
}
|