@pellux/goodvibes-agent 0.1.117 → 1.0.1
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 +20 -4
- package/README.md +35 -14
- package/bin/goodvibes-agent.ts +16 -2
- package/dist/package/main.js +176093 -171065
- 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 +159 -73
- 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 +59 -39
- 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-knowledge-url-editor.ts +4 -11
- 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-local-library-args.ts +52 -0
- package/src/input/commands/agent-runtime-profile-runtime.ts +45 -41
- package/src/input/commands/agent-skills-runtime.ts +87 -99
- 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 +118 -50
- 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 +49 -59
- 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 +63 -72
- 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 +22 -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
|
@@ -3,15 +3,25 @@ import type { McpConfigScope, McpReloadResult, McpServerConfig } from '@pellux/g
|
|
|
3
3
|
import { requireMcpApi, requireShellPaths } from './runtime-services.ts';
|
|
4
4
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
5
5
|
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
6
|
+
import { quoteSlashCommandArg } from '../slash-command-parser.ts';
|
|
6
7
|
|
|
7
8
|
const MCP_ROLES = ['general', 'docs', 'filesystem', 'git', 'database', 'browser', 'automation', 'ops', 'remote'] as const;
|
|
8
9
|
const MCP_TRUST_MODES = ['constrained', 'ask-on-risk', 'allow-all', 'blocked'] as const;
|
|
10
|
+
const MCP_COMMAND_TRUST_MODES = ['constrained', 'ask-on-risk', 'blocked'] as const;
|
|
11
|
+
const MCP_ADD_COMMAND_USAGE = '/mcp add <name> <command> ... --yes; options: --scope project|global, --role <role>, --trust constrained|ask-on-risk|blocked, --env KEY=VALUE, --path <path>, --host <host>';
|
|
12
|
+
const MCP_TRUST_COMMAND_USAGE = '/mcp trust <server> <constrained|ask-on-risk|blocked> --yes';
|
|
13
|
+
const MCP_ROLE_COMMAND_USAGE = '/mcp role <server> <general|docs|filesystem|git|database|browser|automation|ops|remote> --yes';
|
|
9
14
|
|
|
10
15
|
interface ParsedMcpAddArgs {
|
|
11
16
|
readonly scope: McpConfigScope;
|
|
12
17
|
readonly server: McpServerConfig;
|
|
13
18
|
}
|
|
14
19
|
|
|
20
|
+
type McpSecurityServer = ReturnType<NonNullable<NonNullable<CommandContext['clients']>['mcpApi']>['listServerSecurity']>[number];
|
|
21
|
+
type McpDisplayTrustMode = NonNullable<McpServerConfig['trustMode']> | McpSecurityServer['trustMode'];
|
|
22
|
+
type McpDisplayRole = NonNullable<McpServerConfig['role']> | McpSecurityServer['role'];
|
|
23
|
+
type McpDisplayFreshness = McpSecurityServer['schemaFreshness'];
|
|
24
|
+
|
|
15
25
|
function isMcpRole(value: string): value is NonNullable<McpServerConfig['role']> {
|
|
16
26
|
return MCP_ROLES.includes(value as NonNullable<McpServerConfig['role']>);
|
|
17
27
|
}
|
|
@@ -20,10 +30,52 @@ function isMcpTrustMode(value: string): value is NonNullable<McpServerConfig['tr
|
|
|
20
30
|
return MCP_TRUST_MODES.includes(value as NonNullable<McpServerConfig['trustMode']>);
|
|
21
31
|
}
|
|
22
32
|
|
|
33
|
+
function isMcpCommandTrustMode(value: string): value is Exclude<NonNullable<McpServerConfig['trustMode']>, 'allow-all'> {
|
|
34
|
+
return MCP_COMMAND_TRUST_MODES.includes(value as Exclude<NonNullable<McpServerConfig['trustMode']>, 'allow-all'>);
|
|
35
|
+
}
|
|
36
|
+
|
|
23
37
|
function isMcpScope(value: string): value is McpConfigScope {
|
|
24
38
|
return value === 'project' || value === 'global';
|
|
25
39
|
}
|
|
26
40
|
|
|
41
|
+
function stripMcpAddYesFlag(args: readonly string[]): { readonly rest: readonly string[]; readonly yes: boolean } {
|
|
42
|
+
const rest: string[] = [];
|
|
43
|
+
let yes = false;
|
|
44
|
+
let passthrough = false;
|
|
45
|
+
for (const token of args) {
|
|
46
|
+
if (passthrough) {
|
|
47
|
+
rest.push(token);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (token === '--') {
|
|
51
|
+
passthrough = true;
|
|
52
|
+
rest.push(token);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (token === '--yes') {
|
|
56
|
+
yes = true;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
rest.push(token);
|
|
60
|
+
}
|
|
61
|
+
return { rest, yes };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function formatMcpTrustMode(mode: McpDisplayTrustMode): string {
|
|
65
|
+
if (mode === 'ask-on-risk') return 'ask on risky actions';
|
|
66
|
+
if (mode === 'allow-all') return 'allow all actions';
|
|
67
|
+
return mode.replace(/[_-]+/g, ' ');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function formatMcpFreshness(value: McpDisplayFreshness): string {
|
|
71
|
+
if (value === 'quarantined') return 'needs review';
|
|
72
|
+
return value.replace(/[_-]+/g, ' ');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function formatMcpRole(value: McpDisplayRole): string {
|
|
76
|
+
return value.replace(/[_-]+/g, ' ');
|
|
77
|
+
}
|
|
78
|
+
|
|
27
79
|
function validateServerName(name: string): string | null {
|
|
28
80
|
if (!name.trim()) return 'MCP server name is required.';
|
|
29
81
|
if (!/^[a-zA-Z0-9._-]+$/.test(name)) {
|
|
@@ -44,7 +96,7 @@ function parseAddServerArgs(args: string[]): ParsedMcpAddArgs {
|
|
|
44
96
|
const name = args[1]?.trim();
|
|
45
97
|
const command = args[2]?.trim();
|
|
46
98
|
if (!name || !command) {
|
|
47
|
-
throw new Error(
|
|
99
|
+
throw new Error(`Usage: ${MCP_ADD_COMMAND_USAGE}`);
|
|
48
100
|
}
|
|
49
101
|
const nameError = validateServerName(name);
|
|
50
102
|
if (nameError) throw new Error(nameError);
|
|
@@ -71,7 +123,7 @@ function parseAddServerArgs(args: string[]): ParsedMcpAddArgs {
|
|
|
71
123
|
}
|
|
72
124
|
if (token === '--role') {
|
|
73
125
|
const value = readFlagValue(tokens, index, token);
|
|
74
|
-
if (!isMcpRole(value)) throw new Error(`Invalid MCP role "${value}". Expected one of
|
|
126
|
+
if (!isMcpRole(value)) throw new Error(`Invalid MCP role "${value}". Expected one of ${MCP_ROLES.join(', ')}.`);
|
|
75
127
|
role = value;
|
|
76
128
|
index += 1;
|
|
77
129
|
continue;
|
|
@@ -85,7 +137,10 @@ function parseAddServerArgs(args: string[]): ParsedMcpAddArgs {
|
|
|
85
137
|
}
|
|
86
138
|
if (token === '--trust') {
|
|
87
139
|
const value = readFlagValue(tokens, index, token);
|
|
88
|
-
if (!isMcpTrustMode(value)) throw new Error(`Invalid MCP trust mode "${value}". Expected one of
|
|
140
|
+
if (!isMcpTrustMode(value)) throw new Error(`Invalid MCP trust mode "${value}". Expected one of ${MCP_TRUST_MODES.join(', ')}.`);
|
|
141
|
+
if (!isMcpCommandTrustMode(value)) {
|
|
142
|
+
throw new Error(`Use /settings -> MCP to explicitly enable allow-all.\nUsage: ${MCP_ADD_COMMAND_USAGE}`);
|
|
143
|
+
}
|
|
89
144
|
trustMode = value;
|
|
90
145
|
index += 1;
|
|
91
146
|
continue;
|
|
@@ -142,7 +197,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
142
197
|
async handler(args, ctx) {
|
|
143
198
|
const mcpApi = requireMcpApi(ctx);
|
|
144
199
|
const listServerSecurity = () => mcpApi.listServerSecurity();
|
|
145
|
-
const confirmation = stripYesFlag(args);
|
|
200
|
+
const confirmation = args[0] === 'add' ? stripMcpAddYesFlag(args) : stripYesFlag(args);
|
|
146
201
|
const commandArgs = [...confirmation.rest];
|
|
147
202
|
const subcommand = commandArgs[0];
|
|
148
203
|
if (!subcommand && ctx.openMcpWorkspace) {
|
|
@@ -157,7 +212,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
157
212
|
` servers: ${servers.length}`,
|
|
158
213
|
` connected: ${servers.filter((server) => server.connected).length}`,
|
|
159
214
|
` auth or repair attention: ${authRequired.length}`,
|
|
160
|
-
...servers.map((server) => ` ${server.name} trust=${server.trustMode} role=${server.role}
|
|
215
|
+
...servers.map((server) => ` ${server.name} trust=${formatMcpTrustMode(server.trustMode)} role=${formatMcpRole(server.role)} status=${formatMcpFreshness(server.schemaFreshness)} connected=${server.connected ? 'yes' : 'no'}`),
|
|
161
216
|
' next: /mcp auth-review',
|
|
162
217
|
' next: /mcp repair <server>',
|
|
163
218
|
].join('\n'));
|
|
@@ -218,7 +273,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
218
273
|
? [
|
|
219
274
|
'MCP Auth Review',
|
|
220
275
|
...needingAttention.map((server) => (
|
|
221
|
-
` ${server.name} connected
|
|
276
|
+
` ${server.name} connected ${server.connected ? 'yes' : 'no'} status ${formatMcpFreshness(server.schemaFreshness)} trust ${formatMcpTrustMode(server.trustMode)}`
|
|
222
277
|
)),
|
|
223
278
|
' next: /auth review',
|
|
224
279
|
' next: /mcp repair <server>',
|
|
@@ -233,13 +288,13 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
233
288
|
const selected = serverName ? servers.find((server) => server.name === serverName) : servers.find((server) => !server.connected || server.schemaFreshness === 'quarantined');
|
|
234
289
|
if (!selected) {
|
|
235
290
|
ctx.print(serverName
|
|
236
|
-
? `Unknown MCP server
|
|
291
|
+
? `Unknown MCP server ${serverName}`
|
|
237
292
|
: 'MCP Repair\n No MCP server currently needs repair.');
|
|
238
293
|
return;
|
|
239
294
|
}
|
|
240
295
|
const nextSteps = [
|
|
241
296
|
selected.schemaFreshness === 'quarantined'
|
|
242
|
-
? `/mcp quarantine ${selected.name} approve operator --yes`
|
|
297
|
+
? `/mcp quarantine ${quoteSlashCommandArg(selected.name)} approve operator --yes`
|
|
243
298
|
: null,
|
|
244
299
|
!selected.connected ? '/auth review' : null,
|
|
245
300
|
'/mcp review',
|
|
@@ -248,9 +303,9 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
248
303
|
ctx.print([
|
|
249
304
|
`MCP Repair: ${selected.name}`,
|
|
250
305
|
` connected: ${selected.connected ? 'yes' : 'no'}`,
|
|
251
|
-
` trust: ${selected.trustMode}`,
|
|
252
|
-
` role: ${selected.role}`,
|
|
253
|
-
`
|
|
306
|
+
` trust: ${formatMcpTrustMode(selected.trustMode)}`,
|
|
307
|
+
` role: ${formatMcpRole(selected.role)}`,
|
|
308
|
+
` status: ${formatMcpFreshness(selected.schemaFreshness)}`,
|
|
254
309
|
...(selected.quarantineReason ? [` quarantine: ${selected.quarantineReason}`] : []),
|
|
255
310
|
...(selected.quarantineDetail ? [` detail: ${selected.quarantineDetail}`] : []),
|
|
256
311
|
' next:',
|
|
@@ -261,48 +316,58 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
261
316
|
|
|
262
317
|
if (subcommand === 'trust') {
|
|
263
318
|
const serverName = commandArgs[1];
|
|
264
|
-
const
|
|
265
|
-
if (serverName &&
|
|
319
|
+
const requestedMode = commandArgs[2];
|
|
320
|
+
if (serverName && requestedMode) {
|
|
321
|
+
if (!isMcpTrustMode(requestedMode)) {
|
|
322
|
+
ctx.print(`Invalid MCP trust mode "${requestedMode}". Expected constrained, ask-on-risk, blocked, or allow-all.\nUsage: ${MCP_TRUST_COMMAND_USAGE}\nUse /settings -> MCP to explicitly enable allow-all.`);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const mode = requestedMode;
|
|
266
326
|
if (mode === 'allow-all') {
|
|
267
327
|
ctx.print(`Use /settings → MCP to explicitly enable allow-all for ${serverName}. Direct command escalation is blocked.`);
|
|
268
328
|
ctx.openSettingsModal?.();
|
|
269
329
|
return;
|
|
270
330
|
}
|
|
271
331
|
if (!confirmation.yes) {
|
|
272
|
-
requireYesFlag(ctx, `change MCP trust mode for ${serverName}`,
|
|
332
|
+
requireYesFlag(ctx, `change MCP trust mode for ${serverName}`, MCP_TRUST_COMMAND_USAGE);
|
|
273
333
|
return;
|
|
274
334
|
}
|
|
275
335
|
mcpApi.setServerTrustMode(serverName, mode);
|
|
276
|
-
ctx.print(`Updated MCP trust mode for ${serverName} to ${mode}.`);
|
|
336
|
+
ctx.print(`Updated MCP trust mode for ${serverName} to ${formatMcpTrustMode(mode)}.`);
|
|
277
337
|
return;
|
|
278
338
|
}
|
|
279
|
-
if (serverName ||
|
|
280
|
-
ctx.print(
|
|
339
|
+
if (serverName || requestedMode) {
|
|
340
|
+
ctx.print(`Usage: ${MCP_TRUST_COMMAND_USAGE}\nUse /settings -> MCP to explicitly enable allow-all.`);
|
|
281
341
|
return;
|
|
282
342
|
}
|
|
283
343
|
}
|
|
284
344
|
|
|
285
345
|
if (subcommand === 'role') {
|
|
286
346
|
const serverName = commandArgs[1];
|
|
287
|
-
const
|
|
288
|
-
if (serverName &&
|
|
347
|
+
const requestedRole = commandArgs[2];
|
|
348
|
+
if (serverName && requestedRole) {
|
|
349
|
+
if (!isMcpRole(requestedRole)) {
|
|
350
|
+
ctx.print(`Invalid MCP role "${requestedRole}". Expected one of ${MCP_ROLES.join(', ')}.\nUsage: ${MCP_ROLE_COMMAND_USAGE}`);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const role = requestedRole;
|
|
289
354
|
if (!confirmation.yes) {
|
|
290
|
-
requireYesFlag(ctx, `change MCP role for ${serverName}`,
|
|
355
|
+
requireYesFlag(ctx, `change MCP role for ${serverName}`, MCP_ROLE_COMMAND_USAGE);
|
|
291
356
|
return;
|
|
292
357
|
}
|
|
293
358
|
mcpApi.setServerRole(serverName, role);
|
|
294
|
-
ctx.print(`Updated MCP role for ${serverName} to ${role}.`);
|
|
359
|
+
ctx.print(`Updated MCP role for ${serverName} to ${formatMcpRole(role)}.`);
|
|
295
360
|
return;
|
|
296
361
|
}
|
|
297
|
-
if (serverName ||
|
|
298
|
-
ctx.print(
|
|
362
|
+
if (serverName || requestedRole) {
|
|
363
|
+
ctx.print(`Usage: ${MCP_ROLE_COMMAND_USAGE}`);
|
|
299
364
|
return;
|
|
300
365
|
}
|
|
301
366
|
}
|
|
302
367
|
|
|
303
368
|
if (subcommand === 'add') {
|
|
304
369
|
if (!confirmation.yes) {
|
|
305
|
-
requireYesFlag(ctx, 'add or update an MCP server config',
|
|
370
|
+
requireYesFlag(ctx, 'add or update an MCP server config', MCP_ADD_COMMAND_USAGE);
|
|
306
371
|
return;
|
|
307
372
|
}
|
|
308
373
|
let parsedAdd: ParsedMcpAddArgs;
|
|
@@ -317,13 +382,16 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
317
382
|
const result = await mcpApi.upsertServerConfig(shellPaths, parsedAdd.scope, parsedAdd.server);
|
|
318
383
|
const connected = listServerSecurity().find((entry) => entry.name === parsedAdd.server.name)?.connected ?? false;
|
|
319
384
|
ctx.print([
|
|
320
|
-
`MCP server "${parsedAdd.server.name}" saved
|
|
321
|
-
`
|
|
322
|
-
`
|
|
323
|
-
'
|
|
385
|
+
`MCP server "${parsedAdd.server.name}" saved`,
|
|
386
|
+
` scope ${parsedAdd.scope}`,
|
|
387
|
+
` path ${result.path}`,
|
|
388
|
+
...(parsedAdd.scope === 'global' ? [' global config'] : []),
|
|
389
|
+
` Runtime reload: ${connected ? 'connected' : 'server saved; connection needs attention'} (+${result.reload.added} ~${result.reload.changed} -${result.reload.removed}, unchanged ${result.reload.unchanged})`,
|
|
390
|
+
` command ${parsedAdd.server.command}${parsedAdd.server.args?.length ? ` ${parsedAdd.server.args.join(' ')}` : ''}`,
|
|
391
|
+
' next /mcp tools',
|
|
324
392
|
].join('\n'));
|
|
325
393
|
} catch (error) {
|
|
326
|
-
ctx.print(`MCP add failed
|
|
394
|
+
ctx.print(`MCP add failed ${summarizeError(error)}`);
|
|
327
395
|
}
|
|
328
396
|
return;
|
|
329
397
|
}
|
|
@@ -359,10 +427,10 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
359
427
|
try {
|
|
360
428
|
const result = await mcpApi.removeServerConfig(shellPaths, scope, serverName);
|
|
361
429
|
ctx.print(result.removed
|
|
362
|
-
? `Removed MCP server "${serverName}" from ${scope} config ${result.path}. Reload
|
|
430
|
+
? `Removed MCP server "${serverName}" from ${scope} config ${result.path}. Reload +${result.reload.added} ~${result.reload.changed} -${result.reload.removed}, unchanged ${result.reload.unchanged}.`
|
|
363
431
|
: `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
432
|
} catch (error) {
|
|
365
|
-
ctx.print(`MCP remove failed
|
|
433
|
+
ctx.print(`MCP remove failed ${summarizeError(error)}`);
|
|
366
434
|
}
|
|
367
435
|
return;
|
|
368
436
|
}
|
|
@@ -375,9 +443,9 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
375
443
|
try {
|
|
376
444
|
const result = await reloadMcpRuntime(ctx);
|
|
377
445
|
const servers = listServerSecurity();
|
|
378
|
-
ctx.print(`Reloaded MCP runtime from config. ${servers.filter((server) => server.connected).length}/${servers.length} server(s) connected. Result
|
|
446
|
+
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
447
|
} catch (error) {
|
|
380
|
-
ctx.print(`MCP reload failed
|
|
448
|
+
ctx.print(`MCP reload failed ${summarizeError(error)}`);
|
|
381
449
|
}
|
|
382
450
|
return;
|
|
383
451
|
}
|
|
@@ -388,22 +456,24 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
388
456
|
const effective = mcpApi.getEffectiveConfig(shellPaths);
|
|
389
457
|
ctx.print([
|
|
390
458
|
'MCP Config',
|
|
391
|
-
' locations
|
|
459
|
+
' locations',
|
|
392
460
|
...effective.locations.map((location) => ` ${location.scope}/${location.kind}${location.writable ? ' writable' : ' read-only'} ${location.path}`),
|
|
393
|
-
` effective servers
|
|
461
|
+
` effective servers ${effective.servers.length}`,
|
|
394
462
|
...effective.servers.map((entry) => {
|
|
395
463
|
const server = entry.server;
|
|
396
464
|
const envKeys = Object.keys(server.env ?? {});
|
|
397
|
-
return ` - ${server.name}: ${server.command}${server.args?.length ? ` ${server.args.join(' ')}` : ''}
|
|
465
|
+
return ` - ${server.name}: ${server.command}${server.args?.length ? ` ${server.args.join(' ')}` : ''} origin ${entry.source.scope}/${entry.source.kind}${envKeys.length ? ` envKeys=${envKeys.join(',')}` : ''}`;
|
|
398
466
|
}),
|
|
399
467
|
'',
|
|
400
|
-
'Add or update from inside Agent with explicit confirmation
|
|
468
|
+
'Add or update from inside Agent with explicit confirmation',
|
|
401
469
|
' Open /mcp and choose Add or update server, or use Agent Workspace -> Tools & MCP -> Add MCP server.',
|
|
402
|
-
'
|
|
403
|
-
'
|
|
470
|
+
' Use Settings -> MCP for allow-all decisions.',
|
|
471
|
+
' Use -- before server args that look like Agent flags.',
|
|
472
|
+
'Automation equivalent',
|
|
473
|
+
` ${MCP_ADD_COMMAND_USAGE}`,
|
|
404
474
|
].join('\n'));
|
|
405
475
|
} catch (error) {
|
|
406
|
-
ctx.print(`MCP config read failed
|
|
476
|
+
ctx.print(`MCP config read failed ${summarizeError(error)}`);
|
|
407
477
|
}
|
|
408
478
|
return;
|
|
409
479
|
}
|
|
@@ -417,12 +487,12 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
417
487
|
}
|
|
418
488
|
if (action === 'approve') {
|
|
419
489
|
if (!confirmation.yes) {
|
|
420
|
-
requireYesFlag(ctx, `approve MCP
|
|
490
|
+
requireYesFlag(ctx, `approve MCP tool-definition quarantine override for ${serverName}`, '/mcp quarantine <server> approve [operatorId] --yes');
|
|
421
491
|
return;
|
|
422
492
|
}
|
|
423
493
|
const operatorId = commandArgs[3] || 'operator';
|
|
424
494
|
mcpApi.approveSchemaQuarantine(serverName, operatorId);
|
|
425
|
-
ctx.print(`Approved MCP
|
|
495
|
+
ctx.print(`Approved MCP tool-definition quarantine override for ${serverName} as ${operatorId}. Refresh is still recommended.`);
|
|
426
496
|
return;
|
|
427
497
|
}
|
|
428
498
|
if (!confirmation.yes) {
|
|
@@ -431,7 +501,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
431
501
|
}
|
|
432
502
|
const detail = commandArgs.slice(2).join(' ') || 'quarantined by operator';
|
|
433
503
|
mcpApi.quarantineSchema(serverName, 'operator_flagged', detail);
|
|
434
|
-
ctx.print(`Quarantined MCP
|
|
504
|
+
ctx.print(`Quarantined MCP tool definitions for ${serverName}.\nReason: ${detail}`);
|
|
435
505
|
return;
|
|
436
506
|
}
|
|
437
507
|
|
|
@@ -456,18 +526,16 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
456
526
|
});
|
|
457
527
|
}
|
|
458
528
|
|
|
459
|
-
type McpSecurityServer = ReturnType<NonNullable<NonNullable<CommandContext['clients']>['mcpApi']>['listServerSecurity']>[number];
|
|
460
|
-
|
|
461
529
|
function formatMcpServerList(servers: readonly McpSecurityServer[]): string {
|
|
462
530
|
const connected = servers.filter(s => s.connected);
|
|
463
531
|
const disconnected = servers.filter(s => !s.connected);
|
|
464
532
|
const lines: string[] = [`MCP Servers (${connected.length}/${servers.length} connected):`];
|
|
465
533
|
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}`);
|
|
534
|
+
const pathScope = s.allowedPaths.length > 0 ? ` paths ${s.allowedPaths.length}` : '';
|
|
535
|
+
const hostScope = s.allowedHosts.length > 0 ? ` hosts ${s.allowedHosts.length}` : '';
|
|
536
|
+
const freshness = ` status ${formatMcpFreshness(s.schemaFreshness)}`;
|
|
537
|
+
const quarantine = s.schemaFreshness === 'quarantined' ? ` review reason ${s.quarantineReason ?? 'unknown'}` : '';
|
|
538
|
+
lines.push(` ${s.connected ? '[connected] ' : '[disconnected]'} ${s.name} trust=${formatMcpTrustMode(s.trustMode)} role=${formatMcpRole(s.role)}${freshness}${quarantine}${pathScope}${hostScope}`);
|
|
471
539
|
}
|
|
472
540
|
if (connected.length > 0) {
|
|
473
541
|
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
|
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { CommandContext, CommandRegistry } from '../command-registry.ts';
|
|
2
|
+
import {
|
|
3
|
+
buildOperatorActionRequest,
|
|
4
|
+
formatOperatorActionFailure,
|
|
5
|
+
formatOperatorActionPreview,
|
|
6
|
+
formatOperatorActionSuccess,
|
|
7
|
+
postOperatorAction,
|
|
8
|
+
type OperatorActionId,
|
|
9
|
+
} from '../../agent/operator-actions.ts';
|
|
10
|
+
import { resolveAgentConnectedHostConnection } from '../../agent/routine-schedule-promotion.ts';
|
|
11
|
+
import { requireShellPaths } from './runtime-services.ts';
|
|
12
|
+
import { stripYesFlag } from './confirmation.ts';
|
|
13
|
+
|
|
14
|
+
interface OperatorActionOptionParse {
|
|
15
|
+
readonly note?: string;
|
|
16
|
+
readonly remember?: boolean;
|
|
17
|
+
readonly errors: readonly string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function parseOperatorActionOptions(args: readonly string[]): OperatorActionOptionParse {
|
|
21
|
+
const errors: string[] = [];
|
|
22
|
+
let note: string | undefined;
|
|
23
|
+
let remember: boolean | undefined;
|
|
24
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
25
|
+
const token = args[index];
|
|
26
|
+
if (token === '--note') {
|
|
27
|
+
const value = args[index + 1];
|
|
28
|
+
if (!value) {
|
|
29
|
+
errors.push('--note requires text');
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
note = value;
|
|
33
|
+
index += 1;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (token === '--remember') {
|
|
37
|
+
remember = true;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (token === '--no-remember') {
|
|
41
|
+
remember = false;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
errors.push(`Unknown option ${token}`);
|
|
45
|
+
}
|
|
46
|
+
return { note, remember, errors };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function executeConfirmedOperatorAction(
|
|
50
|
+
ctx: CommandContext,
|
|
51
|
+
action: OperatorActionId,
|
|
52
|
+
targetField: 'approvalId' | 'jobId' | 'runId' | 'scheduleId',
|
|
53
|
+
targetId: string,
|
|
54
|
+
rawArgs: readonly string[],
|
|
55
|
+
usage: string,
|
|
56
|
+
): Promise<void> {
|
|
57
|
+
const parsed = stripYesFlag(rawArgs);
|
|
58
|
+
const options = parseOperatorActionOptions(parsed.rest);
|
|
59
|
+
if (options.errors.length > 0) {
|
|
60
|
+
ctx.print([`Usage: ${usage}`, ...options.errors.map((error) => ` ${error}`)].join('\n'));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const request = buildOperatorActionRequest({
|
|
64
|
+
action,
|
|
65
|
+
[targetField]: targetId,
|
|
66
|
+
note: options.note,
|
|
67
|
+
remember: options.remember,
|
|
68
|
+
});
|
|
69
|
+
if (!request.ok) {
|
|
70
|
+
ctx.print(request.error);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const requestSummary = `/${action} ${targetId}`;
|
|
74
|
+
if (!parsed.yes) {
|
|
75
|
+
ctx.print(`${formatOperatorActionPreview(request, requestSummary)}\nUsage: ${usage}`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const shellPaths = requireShellPaths(ctx);
|
|
79
|
+
const connection = resolveAgentConnectedHostConnection(ctx.platform.configManager, shellPaths.homeDirectory);
|
|
80
|
+
const result = await postOperatorAction(connection, request);
|
|
81
|
+
ctx.print(result.ok ? formatOperatorActionSuccess(connection.baseUrl, result) : formatOperatorActionFailure(result));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function approvalActionFromSubcommand(sub: string): OperatorActionId | null {
|
|
85
|
+
if (sub === 'approve') return 'approvals.approve';
|
|
86
|
+
if (sub === 'deny') return 'approvals.deny';
|
|
87
|
+
if (sub === 'cancel') return 'approvals.cancel';
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function automationActionFromParts(scope: string, verb: string): {
|
|
92
|
+
readonly action: OperatorActionId;
|
|
93
|
+
readonly targetField: 'jobId' | 'runId' | 'scheduleId';
|
|
94
|
+
} | null {
|
|
95
|
+
if ((scope === 'job' || scope === 'jobs') && verb === 'run') return { action: 'automation.jobs.run', targetField: 'jobId' };
|
|
96
|
+
if ((scope === 'job' || scope === 'jobs') && verb === 'pause') return { action: 'automation.jobs.pause', targetField: 'jobId' };
|
|
97
|
+
if ((scope === 'job' || scope === 'jobs') && verb === 'resume') return { action: 'automation.jobs.resume', targetField: 'jobId' };
|
|
98
|
+
if ((scope === 'run' || scope === 'runs') && verb === 'cancel') return { action: 'automation.runs.cancel', targetField: 'runId' };
|
|
99
|
+
if ((scope === 'run' || scope === 'runs') && verb === 'retry') return { action: 'automation.runs.retry', targetField: 'runId' };
|
|
100
|
+
if ((scope === 'schedule' || scope === 'schedules') && verb === 'run') return { action: 'schedules.run', targetField: 'scheduleId' };
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function handleApprovalOperatorAction(args: readonly string[], ctx: CommandContext): Promise<boolean> {
|
|
105
|
+
const sub = (args[0] ?? '').toLowerCase();
|
|
106
|
+
const action = approvalActionFromSubcommand(sub);
|
|
107
|
+
if (!action) return false;
|
|
108
|
+
const approvalId = args[1] ?? '';
|
|
109
|
+
const usage = `/approval ${sub} <approval-id> [--note <text>] [--remember|--no-remember] --yes`;
|
|
110
|
+
if (!approvalId) {
|
|
111
|
+
ctx.print(`Usage: ${usage}`);
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
await executeConfirmedOperatorAction(ctx, action, 'approvalId', approvalId, args.slice(2), usage);
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function registerOperatorActionRuntimeCommands(registry: CommandRegistry): void {
|
|
119
|
+
registry.register({
|
|
120
|
+
name: 'automation',
|
|
121
|
+
aliases: ['auto'],
|
|
122
|
+
description: 'Run confirmed connected-host automation actions from the Agent TUI',
|
|
123
|
+
usage: 'job <run|pause|resume> <job-id> --yes | run <cancel|retry> <run-id> --yes | schedule run <schedule-id> --yes',
|
|
124
|
+
argsHint: 'job run <id> --yes | run cancel <id> --yes | schedule run <id> --yes',
|
|
125
|
+
async handler(args, ctx) {
|
|
126
|
+
const scope = (args[0] ?? '').toLowerCase();
|
|
127
|
+
const verb = (args[1] ?? '').toLowerCase();
|
|
128
|
+
const targetId = args[2] ?? '';
|
|
129
|
+
const mapping = automationActionFromParts(scope, verb);
|
|
130
|
+
const usage = '/automation job <run|pause|resume> <job-id> --yes | /automation run <cancel|retry> <run-id> --yes | /automation schedule run <schedule-id> --yes';
|
|
131
|
+
if (!mapping || !targetId) {
|
|
132
|
+
ctx.print(`Usage: ${usage}`);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
await executeConfirmedOperatorAction(ctx, mapping.action, mapping.targetField, targetId, args.slice(3), usage);
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
}
|
|
@@ -57,21 +57,6 @@ export function registerOperatorRuntimeCommands(registry: CommandRegistry): void
|
|
|
57
57
|
else ctx.scrollToLine?.(nextLine);
|
|
58
58
|
},
|
|
59
59
|
});
|
|
60
|
-
|
|
61
|
-
registry.register({
|
|
62
|
-
name: 'profiles',
|
|
63
|
-
aliases: ['profile'],
|
|
64
|
-
description: 'Blocked copied config-profile command; Agent uses isolated profile homes',
|
|
65
|
-
usage: 'disabled',
|
|
66
|
-
argsHint: 'disabled',
|
|
67
|
-
handler(_args, ctx) {
|
|
68
|
-
ctx.print([
|
|
69
|
-
'Copied config profiles are disabled in GoodVibes Agent.',
|
|
70
|
-
'Use /agent profiles or /agent-profile to create and manage isolated Agent profile homes.',
|
|
71
|
-
].join('\n'));
|
|
72
|
-
},
|
|
73
|
-
});
|
|
74
|
-
|
|
75
60
|
registry.register({
|
|
76
61
|
name: 'prev-error',
|
|
77
62
|
aliases: ['pe'],
|
|
@@ -153,7 +138,7 @@ export function registerOperatorRuntimeCommands(registry: CommandRegistry): void
|
|
|
153
138
|
return;
|
|
154
139
|
}
|
|
155
140
|
if (verbosity !== 'minimal' && verbosity !== 'normal' && verbosity !== 'verbose') {
|
|
156
|
-
ctx.print(`Invalid verbosity "${verbosity}". Valid values
|
|
141
|
+
ctx.print(`Invalid verbosity "${verbosity}". Valid values minimal, normal, verbose.`);
|
|
157
142
|
return;
|
|
158
143
|
}
|
|
159
144
|
if (!parsed.yes) {
|
|
@@ -161,7 +146,11 @@ export function registerOperatorRuntimeCommands(registry: CommandRegistry): void
|
|
|
161
146
|
return;
|
|
162
147
|
}
|
|
163
148
|
mgr.setDomainVerbosity(domain, verbosity as 'minimal' | 'normal' | 'verbose');
|
|
164
|
-
ctx.print(
|
|
149
|
+
ctx.print([
|
|
150
|
+
'Domain verbosity set',
|
|
151
|
+
` domain ${domain}`,
|
|
152
|
+
` verbosity ${verbosity}`,
|
|
153
|
+
].join('\n'));
|
|
165
154
|
return;
|
|
166
155
|
}
|
|
167
156
|
|