@robota-sdk/agent-command 3.0.0-beta.100
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 +1351 -0
- package/LICENSE +661 -0
- package/README.md +100 -0
- package/dist/node/index.cjs +121 -0
- package/dist/node/index.d.cts +1053 -0
- package/dist/node/index.d.cts.map +1 -0
- package/dist/node/index.d.ts +1053 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +122 -0
- package/dist/node/index.js.map +1 -0
- package/package.json +83 -0
- package/src/advisor/advisor-command-module.ts +47 -0
- package/src/advisor/advisor-command.test.ts +102 -0
- package/src/advisor/advisor-command.ts +55 -0
- package/src/advisor/advisor-cost-functional.test.ts +92 -0
- package/src/advisor/index.ts +6 -0
- package/src/agent/__tests__/agent-command.test.ts +597 -0
- package/src/agent/agent-command-module.ts +89 -0
- package/src/agent/agent-command-parser.ts +180 -0
- package/src/agent/agent-command.ts +255 -0
- package/src/agent/index.ts +7 -0
- package/src/background/__tests__/background-command-module.test.ts +261 -0
- package/src/background/background-command-module.ts +52 -0
- package/src/background/background-command.ts +65 -0
- package/src/background/index.ts +6 -0
- package/src/command-module-utils.test.ts +37 -0
- package/src/command-module-utils.ts +35 -0
- package/src/compact/__tests__/compact-command-module.test.ts +181 -0
- package/src/compact/compact-command-module.ts +59 -0
- package/src/compact/compact-command.ts +33 -0
- package/src/compact/index.ts +6 -0
- package/src/context/__tests__/context-command-module.test.ts +502 -0
- package/src/context/auto-compact-format.ts +25 -0
- package/src/context/context-breakdown.ts +295 -0
- package/src/context/context-command-module.ts +82 -0
- package/src/context/context-command.ts +291 -0
- package/src/context/index.ts +6 -0
- package/src/default/__tests__/__snapshots__/model-exposure.test.ts.snap +121 -0
- package/src/default/__tests__/default-command-modules.test.ts +192 -0
- package/src/default/__tests__/model-exposure.test.ts +269 -0
- package/src/default/__tests__/org-policy-forwarding.test.ts +106 -0
- package/src/default/__tests__/provider-environment-forwarding.test.ts +98 -0
- package/src/default/default-command-modules.ts +247 -0
- package/src/default/index.ts +5 -0
- package/src/devices/__tests__/devices-command-module.test.ts +299 -0
- package/src/devices/devices-command-module.ts +308 -0
- package/src/devices/devices-command-port.ts +143 -0
- package/src/devices/index.ts +14 -0
- package/src/doctor/__tests__/doctor-command-module.test.ts +145 -0
- package/src/doctor/__tests__/doctor-display-vocabulary.test.ts +38 -0
- package/src/doctor/__tests__/doctor-fixture.ts +146 -0
- package/src/doctor/__tests__/doctor-redaction.test.ts +29 -0
- package/src/doctor/__tests__/doctor-runner.test.ts +484 -0
- package/src/doctor/doctor-command-module.ts +132 -0
- package/src/doctor/doctor-extensions-probe.ts +323 -0
- package/src/doctor/doctor-node-deps.ts +88 -0
- package/src/doctor/doctor-redaction.ts +99 -0
- package/src/doctor/doctor-render.ts +70 -0
- package/src/doctor/doctor-repair.ts +136 -0
- package/src/doctor/doctor-runner.ts +122 -0
- package/src/doctor/doctor-settings-probe.ts +239 -0
- package/src/doctor/doctor-storage-probe.ts +187 -0
- package/src/doctor/doctor-types.ts +109 -0
- package/src/doctor/index.ts +32 -0
- package/src/editor/__tests__/editor-command-functional.test.ts +124 -0
- package/src/editor/editor-command-module.ts +53 -0
- package/src/editor/editor-command.ts +58 -0
- package/src/editor/index.ts +7 -0
- package/src/editor/resolve-editor.ts +21 -0
- package/src/effort/effort-command-module.ts +51 -0
- package/src/effort/effort-command.test.ts +203 -0
- package/src/effort/effort-command.ts +136 -0
- package/src/effort/index.ts +6 -0
- package/src/events/__tests__/events-command.test.ts +73 -0
- package/src/events/events-command-module.ts +49 -0
- package/src/events/events-command.ts +69 -0
- package/src/events/index.ts +2 -0
- package/src/exit/__tests__/exit-command-module.test.ts +61 -0
- package/src/exit/exit-command-module.ts +47 -0
- package/src/exit/exit-command.ts +26 -0
- package/src/exit/index.ts +6 -0
- package/src/fork/__tests__/fork-command.test.ts +184 -0
- package/src/fork/fork-command-module.ts +61 -0
- package/src/fork/fork-command.ts +162 -0
- package/src/fork/index.ts +8 -0
- package/src/git/__tests__/fake-git-port.ts +35 -0
- package/src/git/__tests__/git-command-module.test.ts +111 -0
- package/src/git/__tests__/git-commit.test.ts +185 -0
- package/src/git/git-command-module.ts +138 -0
- package/src/git/git-commit.ts +254 -0
- package/src/git/index.ts +20 -0
- package/src/goal/__tests__/goal-command.test.ts +80 -0
- package/src/goal/goal-command-module.ts +49 -0
- package/src/goal/goal-command.ts +69 -0
- package/src/goal/index.ts +6 -0
- package/src/handoff/__tests__/handoff-command-module.test.ts +25 -0
- package/src/handoff/__tests__/handoff-command.test.ts +218 -0
- package/src/handoff/handoff-command-module.ts +49 -0
- package/src/handoff/handoff-command.ts +187 -0
- package/src/handoff/index.ts +6 -0
- package/src/help/__tests__/help-command-module.test.ts +134 -0
- package/src/help/__tests__/help-command.test.ts +130 -0
- package/src/help/help-command-module.ts +47 -0
- package/src/help/help-command.ts +11 -0
- package/src/help/index.ts +6 -0
- package/src/index.ts +163 -0
- package/src/keybindings/__tests__/keybindings-command-module.test.ts +57 -0
- package/src/keybindings/index.ts +6 -0
- package/src/keybindings/keybindings-command-module.ts +88 -0
- package/src/language/__tests__/language-command-module.test.ts +126 -0
- package/src/language/index.ts +6 -0
- package/src/language/language-command-module.ts +55 -0
- package/src/language/language-command.ts +52 -0
- package/src/mcp-activation/__tests__/mcp-activation-command.test.ts +895 -0
- package/src/mcp-activation/__tests__/mcp-model-view.test.ts +242 -0
- package/src/mcp-activation/__tests__/mcp-skill-command.test.ts +141 -0
- package/src/mcp-activation/index.ts +12 -0
- package/src/mcp-activation/mcp-activation-command-module.ts +123 -0
- package/src/mcp-activation/mcp-activation-command.ts +662 -0
- package/src/mcp-activation/mcp-model-notice.ts +99 -0
- package/src/mcp-activation/mcp-model-status.ts +104 -0
- package/src/memory/__tests__/memory-command-module.test.ts +476 -0
- package/src/memory/__tests__/memory-command-unavailable.test.ts +63 -0
- package/src/memory/index.ts +6 -0
- package/src/memory/memory-command-module.ts +77 -0
- package/src/memory/memory-command.ts +326 -0
- package/src/mode/__tests__/mode-command-module.test.ts +230 -0
- package/src/mode/index.ts +6 -0
- package/src/mode/mode-command-module.ts +55 -0
- package/src/mode/mode-command.ts +67 -0
- package/src/model/__tests__/model-command-module.test.ts +80 -0
- package/src/model/__tests__/model-command.test.ts +265 -0
- package/src/model/index.ts +6 -0
- package/src/model/model-command-module.ts +56 -0
- package/src/model/model-command.ts +249 -0
- package/src/output-style/__tests__/output-style-command.test.ts +96 -0
- package/src/output-style/index.ts +6 -0
- package/src/output-style/output-style-command-module.ts +143 -0
- package/src/peers/__tests__/peers-command-module.test.ts +24 -0
- package/src/peers/__tests__/peers-command.test.ts +399 -0
- package/src/peers/index.ts +9 -0
- package/src/peers/peers-command-module.ts +49 -0
- package/src/peers/peers-command.ts +220 -0
- package/src/permissions/__tests__/permissions-command-module.test.ts +276 -0
- package/src/permissions/index.ts +6 -0
- package/src/permissions/permissions-command-module.ts +55 -0
- package/src/permissions/permissions-command.ts +89 -0
- package/src/plan/__tests__/plan-command.test.ts +129 -0
- package/src/plan/index.ts +9 -0
- package/src/plan/plan-command-module.ts +49 -0
- package/src/plan/plan-command.ts +92 -0
- package/src/plugin/__tests__/plugin-active-turn.test.ts +100 -0
- package/src/plugin/__tests__/plugin-command-module.test.ts +300 -0
- package/src/plugin/__tests__/plugin-host-boundary.test.ts +11 -0
- package/src/plugin/index.ts +7 -0
- package/src/plugin/plugin-command-module.ts +85 -0
- package/src/plugin/plugin-command.ts +271 -0
- package/src/preset/__tests__/host-supplied-preset-registry.test.ts +98 -0
- package/src/preset/__tests__/preset-command-module.test.ts +220 -0
- package/src/preset/index.ts +6 -0
- package/src/preset/preset-command-module.ts +80 -0
- package/src/preset/preset-command.ts +148 -0
- package/src/provider/__tests__/optional-provider-credentials.test.ts +130 -0
- package/src/provider/__tests__/org-policy.test.ts +174 -0
- package/src/provider/__tests__/provider-command-module.test.ts +884 -0
- package/src/provider/__tests__/provider-connection-setup.test.ts +567 -0
- package/src/provider/__tests__/provider-reference-switch.test.ts +113 -0
- package/src/provider/__tests__/provider-setup-flow.test.ts +244 -0
- package/src/provider/__tests__/provider-startup-host-settings.test.ts +16 -0
- package/src/provider/__tests__/scripted-interaction.ts +40 -0
- package/src/provider/index.ts +32 -0
- package/src/provider/provider-command-execution.ts +235 -0
- package/src/provider/provider-command-module.ts +82 -0
- package/src/provider/provider-command-profile-lifecycle.ts +239 -0
- package/src/provider/provider-command-profile-operations.ts +218 -0
- package/src/provider/provider-command-profile.ts +116 -0
- package/src/provider/provider-command-setup.ts +79 -0
- package/src/provider/provider-connection-setup.ts +287 -0
- package/src/provider/provider-connection-validation.ts +38 -0
- package/src/provider/provider-onboarding.ts +73 -0
- package/src/provider/provider-prompt-interaction.ts +28 -0
- package/src/provider/provider-setup-ask.ts +67 -0
- package/src/provider/provider-setup-flow.ts +326 -0
- package/src/provider/provider-startup.ts +225 -0
- package/src/remote-control/__tests__/remote-control-command-module.test.ts +36 -0
- package/src/remote-control/__tests__/remote-control-command.test.ts +216 -0
- package/src/remote-control/index.ts +8 -0
- package/src/remote-control/remote-control-command-module.ts +61 -0
- package/src/remote-control/remote-control-command.ts +138 -0
- package/src/reset/__tests__/reset-command-module.test.ts +63 -0
- package/src/reset/index.ts +2 -0
- package/src/reset/reset-command-module.ts +47 -0
- package/src/reset/reset-command.ts +10 -0
- package/src/rewind/__tests__/rewind-command-module.test.ts +287 -0
- package/src/rewind/index.ts +2 -0
- package/src/rewind/rewind-command-module.ts +56 -0
- package/src/rewind/rewind-command.ts +279 -0
- package/src/sandbox/__tests__/sandbox-command.test.ts +80 -0
- package/src/sandbox/index.ts +2 -0
- package/src/sandbox/sandbox-command-module.ts +56 -0
- package/src/sandbox/sandbox-command.ts +94 -0
- package/src/schedule/__tests__/loop-command-functional.test.ts +399 -0
- package/src/schedule/__tests__/loop-command.test.ts +594 -0
- package/src/schedule/__tests__/loop-jitter.test.ts +25 -0
- package/src/schedule/__tests__/monitor-model-permission-functional.test.ts +126 -0
- package/src/schedule/__tests__/schedule-command.test.ts +247 -0
- package/src/schedule/__tests__/schedule-redos.test.ts +114 -0
- package/src/schedule/index.ts +12 -0
- package/src/schedule/loop-command.ts +427 -0
- package/src/schedule/loop-jitter.ts +41 -0
- package/src/schedule/schedule-command-module.ts +184 -0
- package/src/schedule/schedule-command.ts +161 -0
- package/src/schedule/schedule-spec-parser.ts +70 -0
- package/src/session/__tests__/model-pricing.test.ts +66 -0
- package/src/session/__tests__/rename-host-persistence.test.ts +72 -0
- package/src/session/__tests__/session-command-module.test.ts +589 -0
- package/src/session/index.ts +17 -0
- package/src/session/model-pricing.ts +26 -0
- package/src/session/session-command-module.ts +197 -0
- package/src/session/session-command.ts +272 -0
- package/src/settings/index.ts +7 -0
- package/src/settings/settings-command-module.ts +47 -0
- package/src/shell/__tests__/resolve-shell-host.test.ts +11 -0
- package/src/shell/__tests__/shell-command-functional.test.ts +118 -0
- package/src/shell/index.ts +8 -0
- package/src/shell/resolve-shell.ts +25 -0
- package/src/shell/shell-command-module.ts +53 -0
- package/src/shell/shell-command.ts +48 -0
- package/src/shell/spawn-inherited.ts +32 -0
- package/src/skills/__tests__/skill-read-command.test.ts +64 -0
- package/src/skills/__tests__/skills-command-module.test.ts +200 -0
- package/src/skills/index.ts +7 -0
- package/src/skills/skill-read-command.ts +60 -0
- package/src/skills/skills-command-module.ts +76 -0
- package/src/skills/skills-command.ts +108 -0
- package/src/statusline/__tests__/statusline-command-module.test.ts +96 -0
- package/src/statusline/index.ts +6 -0
- package/src/statusline/statusline-command-module.ts +55 -0
- package/src/statusline/statusline-command.ts +80 -0
- package/src/terminal-client/__tests__/terminal-client-commands.test.ts +216 -0
- package/src/terminal-client/index.ts +5 -0
- package/src/terminal-client/terminal-client-commands.ts +109 -0
- package/src/theme/__tests__/theme-command.test.ts +204 -0
- package/src/theme/index.ts +12 -0
- package/src/theme/theme-command-module.ts +221 -0
- package/src/user-local/__tests__/user-local-command.test.ts +180 -0
- package/src/user-local/index.ts +13 -0
- package/src/user-local/user-local-command-constants.ts +5 -0
- package/src/user-local/user-local-command-module.ts +65 -0
- package/src/user-local/user-local-command.ts +214 -0
- package/src/user-local/user-local-memory-command.ts +160 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { confirmAction, isConfirmed, selectAction, textAction } from '@robota-sdk/agent-core';
|
|
2
|
+
import {
|
|
3
|
+
deleteProviderProfile,
|
|
4
|
+
sanitizeProviderProfileName,
|
|
5
|
+
upsertProviderProfile,
|
|
6
|
+
} from '@robota-sdk/agent-framework';
|
|
7
|
+
|
|
8
|
+
import { formatProviderChoiceLabel } from './provider-command-profile-operations.js';
|
|
9
|
+
|
|
10
|
+
import type { IUserInteraction } from '@robota-sdk/agent-core';
|
|
11
|
+
import type {
|
|
12
|
+
IProviderCommandModuleOptions,
|
|
13
|
+
IProviderProfileSettings,
|
|
14
|
+
} from '@robota-sdk/agent-framework';
|
|
15
|
+
import type { ICommandResult } from '@robota-sdk/agent-interface-command';
|
|
16
|
+
|
|
17
|
+
const MAX_DUPLICATE_PROFILE_SUFFIX = 1000;
|
|
18
|
+
const PROVIDER_RESTART_ACTION = {
|
|
19
|
+
type: 'session-restart',
|
|
20
|
+
reason: 'other',
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
export async function buildProviderDuplicate(
|
|
24
|
+
ui: IUserInteraction,
|
|
25
|
+
profileName: string,
|
|
26
|
+
options: IProviderCommandModuleOptions,
|
|
27
|
+
): Promise<ICommandResult> {
|
|
28
|
+
const settings = options.settings.readMergedSettings();
|
|
29
|
+
if (!settings.providers?.[profileName]) {
|
|
30
|
+
return { message: `Provider profile "${profileName}" was not found.`, success: false };
|
|
31
|
+
}
|
|
32
|
+
const defaultName = suggestDuplicateProfileName(profileName, Object.keys(settings.providers));
|
|
33
|
+
let errorMessage: string | undefined;
|
|
34
|
+
for (;;) {
|
|
35
|
+
const response = await ui.ask(
|
|
36
|
+
textAction('provider-duplicate', `Duplicate ${profileName} as`, {
|
|
37
|
+
description: errorMessage,
|
|
38
|
+
placeholder: defaultName,
|
|
39
|
+
allowEmpty: true,
|
|
40
|
+
}),
|
|
41
|
+
);
|
|
42
|
+
if (response.type === 'cancelled') {
|
|
43
|
+
return { message: 'Provider duplicate cancelled.', success: true };
|
|
44
|
+
}
|
|
45
|
+
const value = response.text ?? '';
|
|
46
|
+
// Re-ask on validation failure, surfacing the reason (CMD-004 re-ask loop replaces the closure).
|
|
47
|
+
const validationMessage = validateDuplicateProfileName(value, defaultName, options);
|
|
48
|
+
if (validationMessage !== undefined) {
|
|
49
|
+
errorMessage = validationMessage;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
return completeProviderDuplicate(profileName, value, defaultName, options);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function validateDuplicateProfileName(
|
|
57
|
+
value: string,
|
|
58
|
+
defaultName: string,
|
|
59
|
+
options: IProviderCommandModuleOptions,
|
|
60
|
+
): string | undefined {
|
|
61
|
+
const name = normalizeProfileName(value, defaultName);
|
|
62
|
+
if (name.length === 0) {
|
|
63
|
+
return 'Required';
|
|
64
|
+
}
|
|
65
|
+
if (options.settings.readMergedSettings().providers?.[name] !== undefined) {
|
|
66
|
+
return `Provider profile "${name}" already exists`;
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function completeProviderDuplicate(
|
|
72
|
+
profileName: string,
|
|
73
|
+
value: string,
|
|
74
|
+
defaultName: string,
|
|
75
|
+
options: IProviderCommandModuleOptions,
|
|
76
|
+
): ICommandResult {
|
|
77
|
+
const settings = options.settings.readMergedSettings();
|
|
78
|
+
const sourceProfile = settings.providers?.[profileName];
|
|
79
|
+
if (!sourceProfile) {
|
|
80
|
+
return { message: `Provider profile "${profileName}" was not found.`, success: false };
|
|
81
|
+
}
|
|
82
|
+
const targetName = normalizeProfileName(value, defaultName);
|
|
83
|
+
const validationMessage = validateDuplicateProfileName(targetName, defaultName, options);
|
|
84
|
+
if (validationMessage !== undefined) {
|
|
85
|
+
return { message: validationMessage, success: false };
|
|
86
|
+
}
|
|
87
|
+
options.settings.writeTargetSettings(
|
|
88
|
+
upsertProviderProfile(options.settings.readTargetSettings(), targetName, { ...sourceProfile }),
|
|
89
|
+
);
|
|
90
|
+
return {
|
|
91
|
+
message: `Provider profile duplicated: ${profileName} -> ${targetName}.`,
|
|
92
|
+
success: true,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* #3282 §4b: the direct, non-interactive delete itself — no replacement ask (the interactive
|
|
98
|
+
* per-profile menu's Delete, `buildProviderDelete` below, still offers one); refuses instead when the
|
|
99
|
+
* profile is the one in use. Never asks to CONFIRM either — its caller in `provider-command-execution`
|
|
100
|
+
* does that first, when a human can answer, before reaching this function; this function itself is
|
|
101
|
+
* called only once a delete is already decided (confirmed by a human, or `--confirmed` from the
|
|
102
|
+
* Settings screen's own ConfirmDialog).
|
|
103
|
+
*/
|
|
104
|
+
export function buildProviderProfileDelete(
|
|
105
|
+
providers: Record<string, IProviderProfileSettings> | undefined,
|
|
106
|
+
currentProvider: string | undefined,
|
|
107
|
+
profileName: string | undefined,
|
|
108
|
+
options: IProviderCommandModuleOptions,
|
|
109
|
+
): ICommandResult {
|
|
110
|
+
if (!profileName) {
|
|
111
|
+
return { message: 'Usage: /provider delete <profile>', success: false };
|
|
112
|
+
}
|
|
113
|
+
if (!providers?.[profileName]) {
|
|
114
|
+
return { message: `Provider profile "${profileName}" was not found.`, success: false };
|
|
115
|
+
}
|
|
116
|
+
if (Object.keys(providers).length <= 1) {
|
|
117
|
+
return { message: 'Cannot delete the only provider profile.', success: false };
|
|
118
|
+
}
|
|
119
|
+
if (currentProvider === profileName) {
|
|
120
|
+
return {
|
|
121
|
+
message: 'Cannot delete the profile in use. Switch to another profile first.',
|
|
122
|
+
success: false,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (options.settings.readTargetSettings().providers?.[profileName] === undefined) {
|
|
126
|
+
return {
|
|
127
|
+
message: `Provider profile "${profileName}" is not stored in the active write target; edit its source settings file or override it before deleting.`,
|
|
128
|
+
success: false,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
options.settings.writeTargetSettings(
|
|
132
|
+
deleteProviderProfile(options.settings.readTargetSettings(), profileName),
|
|
133
|
+
);
|
|
134
|
+
return { message: `Provider profile deleted: ${profileName}.`, success: true };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function buildProviderDelete(
|
|
138
|
+
ui: IUserInteraction,
|
|
139
|
+
profileName: string,
|
|
140
|
+
options: IProviderCommandModuleOptions,
|
|
141
|
+
): Promise<ICommandResult> {
|
|
142
|
+
const settings = options.settings.readMergedSettings();
|
|
143
|
+
const providers = settings.providers ?? {};
|
|
144
|
+
if (!providers[profileName]) {
|
|
145
|
+
return { message: `Provider profile "${profileName}" was not found.`, success: false };
|
|
146
|
+
}
|
|
147
|
+
if (Object.keys(providers).length <= 1) {
|
|
148
|
+
return { message: 'Cannot delete the only provider profile.', success: false };
|
|
149
|
+
}
|
|
150
|
+
if (options.settings.readTargetSettings().providers?.[profileName] === undefined) {
|
|
151
|
+
return {
|
|
152
|
+
message: `Provider profile "${profileName}" is not stored in the active write target; edit its source settings file or override it before deleting.`,
|
|
153
|
+
success: false,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
// #3282 §2: the GUI renders this confirm on its shared ConfirmDialog (id 'provider-delete') with
|
|
157
|
+
// this exact title — kept in the quoted-name form the TUI's inline picker shows the same way.
|
|
158
|
+
const response = await ui.ask(
|
|
159
|
+
confirmAction('provider-delete', `Delete profile "${profileName}"?`),
|
|
160
|
+
);
|
|
161
|
+
if (!isConfirmed(response)) {
|
|
162
|
+
return { message: 'Provider delete cancelled.', success: true };
|
|
163
|
+
}
|
|
164
|
+
return confirmProviderDelete(ui, profileName, options);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function confirmProviderDelete(
|
|
168
|
+
ui: IUserInteraction,
|
|
169
|
+
profileName: string,
|
|
170
|
+
options: IProviderCommandModuleOptions,
|
|
171
|
+
): Promise<ICommandResult> {
|
|
172
|
+
const settings = options.settings.readMergedSettings();
|
|
173
|
+
if (settings.currentProvider !== profileName) {
|
|
174
|
+
options.settings.writeTargetSettings(
|
|
175
|
+
deleteProviderProfile(options.settings.readTargetSettings(), profileName),
|
|
176
|
+
);
|
|
177
|
+
return { message: `Provider profile deleted: ${profileName}.`, success: true };
|
|
178
|
+
}
|
|
179
|
+
const replacementOptions = Object.entries(settings.providers ?? {})
|
|
180
|
+
.filter(([name]) => name !== profileName)
|
|
181
|
+
.map(([name, profile]) => ({
|
|
182
|
+
value: name,
|
|
183
|
+
label: formatProviderChoiceLabel(name, profile, settings.currentProvider),
|
|
184
|
+
}));
|
|
185
|
+
const response = await ui.ask(
|
|
186
|
+
selectAction(
|
|
187
|
+
'provider-delete-replacement',
|
|
188
|
+
`Replacement provider for ${profileName}`,
|
|
189
|
+
replacementOptions,
|
|
190
|
+
{
|
|
191
|
+
maxVisible: 8,
|
|
192
|
+
},
|
|
193
|
+
),
|
|
194
|
+
);
|
|
195
|
+
if (response.type !== 'answer' || response.values[0] === undefined) {
|
|
196
|
+
return { message: 'Provider delete cancelled.', success: true };
|
|
197
|
+
}
|
|
198
|
+
return completeActiveProviderDelete(profileName, response.values[0], options);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function completeActiveProviderDelete(
|
|
202
|
+
profileName: string,
|
|
203
|
+
replacementName: string,
|
|
204
|
+
options: IProviderCommandModuleOptions,
|
|
205
|
+
): ICommandResult {
|
|
206
|
+
const settings = options.settings.readMergedSettings();
|
|
207
|
+
if (settings.providers?.[replacementName] === undefined || replacementName === profileName) {
|
|
208
|
+
return { message: `Provider profile "${replacementName}" was not found.`, success: false };
|
|
209
|
+
}
|
|
210
|
+
const target = deleteProviderProfile(options.settings.readTargetSettings(), profileName);
|
|
211
|
+
options.settings.writeTargetSettings({ ...target, currentProvider: replacementName });
|
|
212
|
+
return {
|
|
213
|
+
message: `Provider profile deleted: ${profileName}. Restarting with ${replacementName}...`,
|
|
214
|
+
success: true,
|
|
215
|
+
hostActions: [{ ...PROVIDER_RESTART_ACTION, message: 'Provider delete restart' }],
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function suggestDuplicateProfileName(
|
|
220
|
+
profileName: string,
|
|
221
|
+
existingProfileNames: readonly string[],
|
|
222
|
+
): string {
|
|
223
|
+
const base = sanitizeProviderProfileName(`${profileName}-copy`) ?? 'provider-copy';
|
|
224
|
+
if (!existingProfileNames.includes(base)) {
|
|
225
|
+
return base;
|
|
226
|
+
}
|
|
227
|
+
for (let index = 2; index < MAX_DUPLICATE_PROFILE_SUFFIX; index += 1) {
|
|
228
|
+
const candidate = `${base}-${index}`;
|
|
229
|
+
if (!existingProfileNames.includes(candidate)) {
|
|
230
|
+
return candidate;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return `${base}-${Date.now()}`;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function normalizeProfileName(value: string, defaultName: string): string {
|
|
237
|
+
const raw = value.trim() || defaultName;
|
|
238
|
+
return sanitizeProviderProfileName(raw) ?? '';
|
|
239
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildProviderSetupPatch,
|
|
3
|
+
formatOrgPolicyViolationMessage,
|
|
4
|
+
isApiKeyPlaintext,
|
|
5
|
+
setCurrentProvider,
|
|
6
|
+
upsertProviderProfile,
|
|
7
|
+
} from '@robota-sdk/agent-framework';
|
|
8
|
+
|
|
9
|
+
import { runProviderSetupAsk } from './provider-command-setup.js';
|
|
10
|
+
import { createProviderSetupFlow } from './provider-setup-flow.js';
|
|
11
|
+
import {
|
|
12
|
+
hasProviderConnectionMethods,
|
|
13
|
+
runProviderConnectionSetup,
|
|
14
|
+
} from './provider-connection-setup.js';
|
|
15
|
+
import { validateProviderConnection } from './provider-connection-validation.js';
|
|
16
|
+
|
|
17
|
+
import type { IUserInteraction } from '@robota-sdk/agent-core';
|
|
18
|
+
import type {
|
|
19
|
+
IProviderCommandModuleOptions,
|
|
20
|
+
IProviderProfileSettings,
|
|
21
|
+
IProviderSetupInput,
|
|
22
|
+
} from '@robota-sdk/agent-framework';
|
|
23
|
+
import type { ICommandResult } from '@robota-sdk/agent-interface-command';
|
|
24
|
+
|
|
25
|
+
export function formatProviderChoiceLabel(
|
|
26
|
+
name: string,
|
|
27
|
+
profile: IProviderProfileSettings,
|
|
28
|
+
currentProvider: string | undefined,
|
|
29
|
+
): string {
|
|
30
|
+
const marker = name === currentProvider ? '* ' : '';
|
|
31
|
+
return `${marker}${name}: ${profile.type ?? 'unknown'} ${profile.model ?? '(no model)'}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function buildProviderSwitch(
|
|
35
|
+
providers: Record<string, IProviderProfileSettings> | undefined,
|
|
36
|
+
profileName: string | undefined,
|
|
37
|
+
options: IProviderCommandModuleOptions,
|
|
38
|
+
): Promise<ICommandResult> {
|
|
39
|
+
if (!profileName) {
|
|
40
|
+
return { message: 'Usage: /provider switch <profile>', success: false };
|
|
41
|
+
}
|
|
42
|
+
if (!providers?.[profileName]) {
|
|
43
|
+
return { message: `Provider profile "${profileName}" was not found.`, success: false };
|
|
44
|
+
}
|
|
45
|
+
const { orgPolicy } = options;
|
|
46
|
+
if (orgPolicy?.allowedProviders && !orgPolicy.allowedProviders.includes(profileName)) {
|
|
47
|
+
return {
|
|
48
|
+
message: formatOrgPolicyViolationMessage(
|
|
49
|
+
`Provider "${profileName}" is not allowed by your organization policy. Allowed: ${orgPolicy.allowedProviders.join(', ')}.`,
|
|
50
|
+
orgPolicy.adminContact,
|
|
51
|
+
),
|
|
52
|
+
success: false,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const initial = options.settings.readMergedSettings();
|
|
56
|
+
if (initial.currentProvider === profileName) {
|
|
57
|
+
return { message: `Already using provider "${profileName}".`, success: true };
|
|
58
|
+
}
|
|
59
|
+
const profile = providers[profileName];
|
|
60
|
+
// #3282: the hot-swap this returns as a `hostActions` entry builds this exact profile against
|
|
61
|
+
// these exact definitions (`resolveUserSettingsProviderSwitch` in agent-framework). Validating it
|
|
62
|
+
// HERE, before anything is written, means a switch that would fail changes nothing on disk — the
|
|
63
|
+
// previous behavior wrote `currentProvider` unconditionally and let a downstream hot-swap failure
|
|
64
|
+
// (e.g. "Unknown provider: anthropic. Currently supported: ", an empty list) stand uncorrected.
|
|
65
|
+
try {
|
|
66
|
+
await validateProviderConnection(profileName, profile, options);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
return {
|
|
69
|
+
message: `Failed to switch to "${profileName}": ${error instanceof Error ? error.message : String(error)}`,
|
|
70
|
+
success: false,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const target = options.settings.readTargetSettings();
|
|
74
|
+
const merged = options.settings.readMergedSettings();
|
|
75
|
+
if (
|
|
76
|
+
merged.currentProvider !== initial.currentProvider ||
|
|
77
|
+
JSON.stringify(merged.providers?.[profileName]) !== JSON.stringify(profile)
|
|
78
|
+
) {
|
|
79
|
+
return {
|
|
80
|
+
success: false,
|
|
81
|
+
message: 'Provider settings changed while switching. Run /provider switch again.',
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const next =
|
|
85
|
+
target.providers?.[profileName] !== undefined || merged.providers?.[profileName] !== undefined
|
|
86
|
+
? { ...target, currentProvider: profileName }
|
|
87
|
+
: setCurrentProvider(target, profileName);
|
|
88
|
+
options.settings.writeTargetSettings(next);
|
|
89
|
+
const modelLabel = profile.model ?? 'unknown model';
|
|
90
|
+
return {
|
|
91
|
+
message: `Switched to ${profileName} (${modelLabel}). History preserved.`,
|
|
92
|
+
success: true,
|
|
93
|
+
hostActions: [{ type: 'provider-hot-swap', profileName }],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function buildProviderEdit(
|
|
98
|
+
ui: IUserInteraction,
|
|
99
|
+
profileName: string,
|
|
100
|
+
options: IProviderCommandModuleOptions,
|
|
101
|
+
): Promise<ICommandResult> {
|
|
102
|
+
const settings = options.settings.readMergedSettings();
|
|
103
|
+
const profile = settings.providers?.[profileName];
|
|
104
|
+
if (!profile) {
|
|
105
|
+
return { message: `Provider profile "${profileName}" was not found.`, success: false };
|
|
106
|
+
}
|
|
107
|
+
if (!profile.type) {
|
|
108
|
+
return { message: `Provider profile "${profileName}" is missing type.`, success: false };
|
|
109
|
+
}
|
|
110
|
+
let flow;
|
|
111
|
+
try {
|
|
112
|
+
flow = createProviderSetupFlow(profile.type, options.providerDefinitions, {
|
|
113
|
+
includeEditOnlySteps: true,
|
|
114
|
+
profileName,
|
|
115
|
+
setCurrent: false,
|
|
116
|
+
initialValues: getProviderProfileSetupValues(profile),
|
|
117
|
+
});
|
|
118
|
+
if (hasProviderConnectionMethods(profile.type, options)) {
|
|
119
|
+
flow = { ...flow, steps: flow.steps.filter((step) => step.key !== 'apiKey') };
|
|
120
|
+
}
|
|
121
|
+
} catch (error) {
|
|
122
|
+
return { message: error instanceof Error ? error.message : String(error), success: false };
|
|
123
|
+
}
|
|
124
|
+
return runProviderSetupAsk(
|
|
125
|
+
ui,
|
|
126
|
+
flow,
|
|
127
|
+
(input) => completeProviderEdit(input, profileName, options),
|
|
128
|
+
'Provider edit cancelled.',
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Explicitly replace this profile's connection; ordinary editing preserves its credential. */
|
|
133
|
+
export async function buildProviderReconnect(
|
|
134
|
+
ui: IUserInteraction,
|
|
135
|
+
profileName: string,
|
|
136
|
+
options: IProviderCommandModuleOptions,
|
|
137
|
+
): Promise<ICommandResult> {
|
|
138
|
+
const profile = options.settings.readMergedSettings().providers?.[profileName];
|
|
139
|
+
if (!profile?.type)
|
|
140
|
+
return { success: false, message: `Provider profile "${profileName}" was not found.` };
|
|
141
|
+
if (!hasProviderConnectionMethods(profile.type, options)) {
|
|
142
|
+
return {
|
|
143
|
+
success: false,
|
|
144
|
+
message: `Use /provider edit ${profileName} to update this provider's credentials.`,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const flow = createProviderSetupFlow(profile.type, options.providerDefinitions, {
|
|
148
|
+
profileName,
|
|
149
|
+
setCurrent: false,
|
|
150
|
+
initialValues: { ...(profile.model === undefined ? {} : { model: profile.model }) },
|
|
151
|
+
});
|
|
152
|
+
return runProviderConnectionSetup(ui, flow, options, false, true);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function getProviderProfileSetupValues(profile: IProviderProfileSettings): {
|
|
156
|
+
model?: string;
|
|
157
|
+
apiKey?: string;
|
|
158
|
+
baseURL?: string;
|
|
159
|
+
} {
|
|
160
|
+
return {
|
|
161
|
+
...(typeof profile.model === 'string' ? { model: profile.model } : {}),
|
|
162
|
+
...(typeof profile.apiKey === 'string' ? { apiKey: profile.apiKey } : {}),
|
|
163
|
+
...(typeof profile.baseURL === 'string' ? { baseURL: profile.baseURL } : {}),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function completeProviderEdit(
|
|
168
|
+
input: IProviderSetupInput,
|
|
169
|
+
profileName: string,
|
|
170
|
+
options: IProviderCommandModuleOptions,
|
|
171
|
+
): ICommandResult {
|
|
172
|
+
const merged = options.settings.readMergedSettings();
|
|
173
|
+
const currentProfile = merged.providers?.[profileName];
|
|
174
|
+
if (!currentProfile) {
|
|
175
|
+
return { message: `Provider profile "${profileName}" was not found.`, success: false };
|
|
176
|
+
}
|
|
177
|
+
const { orgPolicy } = options;
|
|
178
|
+
if (orgPolicy?.requireApiKeyFromEnv && isApiKeyPlaintext(input.apiKey)) {
|
|
179
|
+
return {
|
|
180
|
+
message: formatOrgPolicyViolationMessage(
|
|
181
|
+
'Your organization policy requires API keys to be stored as environment variable references ($ENV:VAR_NAME), not as plaintext.',
|
|
182
|
+
orgPolicy.adminContact,
|
|
183
|
+
),
|
|
184
|
+
success: false,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
const target = options.settings.readTargetSettings();
|
|
188
|
+
const patch = buildProviderSetupPatch(
|
|
189
|
+
{
|
|
190
|
+
...input,
|
|
191
|
+
...(input.apiKey === undefined && currentProfile.apiKeyRef !== undefined
|
|
192
|
+
? { apiKeyRef: currentProfile.apiKeyRef }
|
|
193
|
+
: {}),
|
|
194
|
+
...(input.apiKey === undefined && currentProfile.apiKey !== undefined
|
|
195
|
+
? { apiKey: currentProfile.apiKey }
|
|
196
|
+
: {}),
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
providerDefinitions: options.providerDefinitions,
|
|
200
|
+
...(options.env === undefined ? {} : { env: options.env }),
|
|
201
|
+
},
|
|
202
|
+
);
|
|
203
|
+
const updatedProfile = patch.providers[profileName];
|
|
204
|
+
if (!updatedProfile) {
|
|
205
|
+
return { message: `Provider profile "${profileName}" was not updated.`, success: false };
|
|
206
|
+
}
|
|
207
|
+
options.settings.writeTargetSettings(
|
|
208
|
+
upsertProviderProfile(target, profileName, { ...currentProfile, ...updatedProfile }),
|
|
209
|
+
);
|
|
210
|
+
const isCurrent = merged.currentProvider === profileName;
|
|
211
|
+
return {
|
|
212
|
+
message: isCurrent
|
|
213
|
+
? `Provider ${profileName} updated. Switching...`
|
|
214
|
+
: `Provider ${profileName} updated.`,
|
|
215
|
+
success: true,
|
|
216
|
+
...(isCurrent ? { hostActions: [{ type: 'provider-hot-swap' as const, profileName }] } : {}),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { selectAction } from '@robota-sdk/agent-core';
|
|
2
|
+
import { testProviderProfileCommand } from '@robota-sdk/agent-framework';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
buildProviderDelete,
|
|
6
|
+
buildProviderDuplicate,
|
|
7
|
+
} from './provider-command-profile-lifecycle.js';
|
|
8
|
+
import {
|
|
9
|
+
buildProviderEdit,
|
|
10
|
+
buildProviderReconnect,
|
|
11
|
+
buildProviderSwitch,
|
|
12
|
+
formatProviderChoiceLabel,
|
|
13
|
+
} from './provider-command-profile-operations.js';
|
|
14
|
+
import { hasProviderConnectionMethods } from './provider-connection-setup.js';
|
|
15
|
+
|
|
16
|
+
import type { IUserInteraction } from '@robota-sdk/agent-core';
|
|
17
|
+
import type {
|
|
18
|
+
IProviderCommandModuleOptions,
|
|
19
|
+
IProviderProfileSettings,
|
|
20
|
+
} from '@robota-sdk/agent-framework';
|
|
21
|
+
import type { ICommandResult } from '@robota-sdk/agent-interface-command';
|
|
22
|
+
|
|
23
|
+
const ACTION_SWITCH = 'switch';
|
|
24
|
+
const ACTION_EDIT = 'edit';
|
|
25
|
+
const ACTION_TEST = 'test';
|
|
26
|
+
const ACTION_DUPLICATE = 'duplicate';
|
|
27
|
+
const ACTION_DELETE = 'delete';
|
|
28
|
+
const ACTION_RECONNECT = 'reconnect';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Ask the user to pick a provider profile, then drive its action menu (CMD-004 inline ask). Replaces
|
|
32
|
+
* the former returned choice→submit continuation chain — the picker's own option list is the rendered
|
|
33
|
+
* profile list, so the caller no longer prepends a separate list message.
|
|
34
|
+
*/
|
|
35
|
+
export async function askProviderProfileSelection(
|
|
36
|
+
ui: IUserInteraction,
|
|
37
|
+
currentProvider: string | undefined,
|
|
38
|
+
providers: Record<string, IProviderProfileSettings> | undefined,
|
|
39
|
+
options: IProviderCommandModuleOptions,
|
|
40
|
+
): Promise<ICommandResult> {
|
|
41
|
+
const profileOptions = Object.entries(providers ?? {}).map(([name, profile]) => ({
|
|
42
|
+
value: name,
|
|
43
|
+
label: formatProviderChoiceLabel(name, profile, currentProvider),
|
|
44
|
+
}));
|
|
45
|
+
const response = await ui.ask(
|
|
46
|
+
selectAction('provider-profile', 'Select provider profile', profileOptions, { maxVisible: 8 }),
|
|
47
|
+
);
|
|
48
|
+
if (response.type !== 'answer' || response.values[0] === undefined) {
|
|
49
|
+
return { message: 'Provider profile selection cancelled.', success: true };
|
|
50
|
+
}
|
|
51
|
+
return askProviderProfileAction(ui, response.values[0], options);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function askProviderProfileAction(
|
|
55
|
+
ui: IUserInteraction,
|
|
56
|
+
profileName: string,
|
|
57
|
+
options: IProviderCommandModuleOptions,
|
|
58
|
+
): Promise<ICommandResult> {
|
|
59
|
+
const settings = options.settings.readMergedSettings();
|
|
60
|
+
if (!settings.providers?.[profileName]) {
|
|
61
|
+
return { message: `Provider profile "${profileName}" was not found.`, success: false };
|
|
62
|
+
}
|
|
63
|
+
// #3282 §2: no separate 'cancel' option here — the picker's own persistent Cancel affordance
|
|
64
|
+
// (every ask renderer offers one, e.g. `PermissionPrompt`'s ghost Cancel button in agent-ui-web)
|
|
65
|
+
// is the only Cancel a person sees. A second one, indistinguishable from Switch/Edit/Test/Duplicate
|
|
66
|
+
// in a flat list, was the audited bug (#3282 issue text): "Delete looks the same as Switch... plus
|
|
67
|
+
// a second Cancel." Order matters too — Switch first as the primary action, then Edit/Test/
|
|
68
|
+
// Duplicate, with Delete last: a client (the GUI's provider-action menu) that wants Delete
|
|
69
|
+
// separated and styled destructive can rely on it always being the final entry.
|
|
70
|
+
const response = await ui.ask(
|
|
71
|
+
selectAction('provider-profile-action', `Provider profile: ${profileName}`, [
|
|
72
|
+
{ value: ACTION_SWITCH, label: 'Switch' },
|
|
73
|
+
{ value: ACTION_EDIT, label: 'Edit' },
|
|
74
|
+
...(hasProviderConnectionMethods(settings.providers[profileName]?.type ?? '', options)
|
|
75
|
+
? [{ value: ACTION_RECONNECT, label: 'Reconnect' }]
|
|
76
|
+
: []),
|
|
77
|
+
{ value: ACTION_TEST, label: 'Test' },
|
|
78
|
+
{ value: ACTION_DUPLICATE, label: 'Duplicate' },
|
|
79
|
+
{ value: ACTION_DELETE, label: 'Delete' },
|
|
80
|
+
]),
|
|
81
|
+
);
|
|
82
|
+
if (response.type !== 'answer' || response.values[0] === undefined) {
|
|
83
|
+
return { message: 'Provider profile action cancelled.', success: true };
|
|
84
|
+
}
|
|
85
|
+
return executeProviderProfileAction(ui, profileName, response.values[0], options);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function executeProviderProfileAction(
|
|
89
|
+
ui: IUserInteraction,
|
|
90
|
+
profileName: string,
|
|
91
|
+
action: string,
|
|
92
|
+
options: IProviderCommandModuleOptions,
|
|
93
|
+
): Promise<ICommandResult> {
|
|
94
|
+
const settings = options.settings.readMergedSettings();
|
|
95
|
+
switch (action) {
|
|
96
|
+
case ACTION_SWITCH:
|
|
97
|
+
return buildProviderSwitch(settings.providers, profileName, options);
|
|
98
|
+
case ACTION_EDIT:
|
|
99
|
+
return buildProviderEdit(ui, profileName, options);
|
|
100
|
+
case ACTION_RECONNECT:
|
|
101
|
+
return buildProviderReconnect(ui, profileName, options);
|
|
102
|
+
case ACTION_TEST:
|
|
103
|
+
return testProviderProfileCommand(
|
|
104
|
+
settings.currentProvider,
|
|
105
|
+
settings.providers,
|
|
106
|
+
profileName,
|
|
107
|
+
options,
|
|
108
|
+
);
|
|
109
|
+
case ACTION_DUPLICATE:
|
|
110
|
+
return buildProviderDuplicate(ui, profileName, options);
|
|
111
|
+
case ACTION_DELETE:
|
|
112
|
+
return buildProviderDelete(ui, profileName, options);
|
|
113
|
+
default:
|
|
114
|
+
return { message: `Unknown provider profile action "${action}".`, success: false };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { buildProviderSetupPatch, mergeProviderPatch } from '@robota-sdk/agent-framework';
|
|
2
|
+
|
|
3
|
+
import { createProviderSetupFlow } from './provider-setup-flow.js';
|
|
4
|
+
import { runProviderSetupAsk } from './provider-setup-ask.js';
|
|
5
|
+
import {
|
|
6
|
+
hasProviderConnectionMethods,
|
|
7
|
+
runProviderConnectionSetup,
|
|
8
|
+
} from './provider-connection-setup.js';
|
|
9
|
+
|
|
10
|
+
import type { IProviderSetupFlowState } from './provider-setup-flow.js';
|
|
11
|
+
import type { IUserInteraction } from '@robota-sdk/agent-core';
|
|
12
|
+
import type {
|
|
13
|
+
IProviderCommandModuleOptions,
|
|
14
|
+
IProviderSetupInput,
|
|
15
|
+
} from '@robota-sdk/agent-framework';
|
|
16
|
+
import type { ICommandResult } from '@robota-sdk/agent-interface-command';
|
|
17
|
+
|
|
18
|
+
export { runProviderSetupAsk } from './provider-setup-ask.js';
|
|
19
|
+
|
|
20
|
+
const PROVIDER_RESTART_ACTION = {
|
|
21
|
+
type: 'session-restart',
|
|
22
|
+
reason: 'other',
|
|
23
|
+
} as const;
|
|
24
|
+
|
|
25
|
+
export function createSetupFlow(
|
|
26
|
+
type: string,
|
|
27
|
+
options: IProviderCommandModuleOptions,
|
|
28
|
+
): IProviderSetupFlowState {
|
|
29
|
+
return createProviderSetupFlow(type, options.providerDefinitions, {
|
|
30
|
+
existingProfileNames: Object.keys(options.settings.readMergedSettings().providers ?? {}),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Run the `/provider add` setup wizard inline. */
|
|
35
|
+
export function runProviderAddSetup(
|
|
36
|
+
ui: IUserInteraction,
|
|
37
|
+
flow: IProviderSetupFlowState,
|
|
38
|
+
options: IProviderCommandModuleOptions,
|
|
39
|
+
isSetupRequired: boolean,
|
|
40
|
+
): Promise<ICommandResult> {
|
|
41
|
+
if (hasProviderConnectionMethods(flow.type, options)) {
|
|
42
|
+
return runProviderConnectionSetup(ui, flow, options, isSetupRequired);
|
|
43
|
+
}
|
|
44
|
+
return runProviderSetupAsk(
|
|
45
|
+
ui,
|
|
46
|
+
flow,
|
|
47
|
+
(input) => completeProviderSetup(input, options, isSetupRequired),
|
|
48
|
+
'Provider setup cancelled.',
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function completeProviderSetup(
|
|
53
|
+
input: IProviderSetupInput,
|
|
54
|
+
options: IProviderCommandModuleOptions,
|
|
55
|
+
isSetupRequired: boolean,
|
|
56
|
+
): ICommandResult {
|
|
57
|
+
const target = options.settings.readTargetSettings();
|
|
58
|
+
const patch = buildProviderSetupPatch(input, {
|
|
59
|
+
providerDefinitions: options.providerDefinitions,
|
|
60
|
+
...(options.env === undefined ? {} : { env: options.env }),
|
|
61
|
+
});
|
|
62
|
+
options.settings.writeTargetSettings(mergeProviderPatch(target, patch));
|
|
63
|
+
// #3282 §3: the session's own live setup-mode state (never settings — an ordinary env-default
|
|
64
|
+
// session also has no persisted `currentProvider`, and restarts like any other session). Only a
|
|
65
|
+
// session that itself started with a placeholder provider hot-swaps the real one in; every other
|
|
66
|
+
// `/provider add`, even a session's first EVER persisted profile, restarts as before.
|
|
67
|
+
if (isSetupRequired) {
|
|
68
|
+
return {
|
|
69
|
+
message: `Provider ${input.profile} configured.`,
|
|
70
|
+
success: true,
|
|
71
|
+
hostActions: [{ type: 'provider-hot-swap', profileName: input.profile }],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
message: `Provider ${input.profile} configured. Restarting...`,
|
|
76
|
+
success: true,
|
|
77
|
+
hostActions: [{ ...PROVIDER_RESTART_ACTION, message: 'Provider setup restart' }],
|
|
78
|
+
};
|
|
79
|
+
}
|