@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,326 @@
|
|
|
1
|
+
import { findProviderDefinition, formatSupportedProviderTypes } from '@robota-sdk/agent-core';
|
|
2
|
+
import { suggestProviderProfileName } from '@robota-sdk/agent-framework';
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
IProviderDefinition,
|
|
6
|
+
IProviderSetupHelpLink,
|
|
7
|
+
IProviderSetupStepDefinition,
|
|
8
|
+
TProviderSetupField,
|
|
9
|
+
} from '@robota-sdk/agent-core';
|
|
10
|
+
import type { IProviderSetupInput } from '@robota-sdk/agent-framework';
|
|
11
|
+
|
|
12
|
+
export type TProviderSetupType = string;
|
|
13
|
+
export type TPromptInput = (
|
|
14
|
+
label: string,
|
|
15
|
+
masked?: boolean,
|
|
16
|
+
options?: { signal?: AbortSignal },
|
|
17
|
+
) => Promise<string>;
|
|
18
|
+
|
|
19
|
+
export interface IProviderSetupPromptStep extends IProviderSetupStepDefinition {
|
|
20
|
+
key: TProviderSetupField;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface IProviderSetupFlowState {
|
|
24
|
+
type: TProviderSetupType;
|
|
25
|
+
steps: readonly IProviderSetupPromptStep[];
|
|
26
|
+
setupHelpLinks: readonly IProviderSetupHelpLink[];
|
|
27
|
+
stepIndex: number;
|
|
28
|
+
values: Partial<Record<TProviderSetupField, string>>;
|
|
29
|
+
existingProfileNames: readonly string[];
|
|
30
|
+
profileName?: string;
|
|
31
|
+
setCurrent?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface IProviderSetupFlowOptions {
|
|
35
|
+
includeEditOnlySteps?: boolean;
|
|
36
|
+
existingProfileNames?: readonly string[];
|
|
37
|
+
initialValues?: Partial<Record<TProviderSetupField, string>>;
|
|
38
|
+
profileName?: string;
|
|
39
|
+
setCurrent?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type TProviderSetupFlowSubmitResult =
|
|
43
|
+
| {
|
|
44
|
+
status: 'next';
|
|
45
|
+
state: IProviderSetupFlowState;
|
|
46
|
+
}
|
|
47
|
+
| {
|
|
48
|
+
status: 'complete';
|
|
49
|
+
input: IProviderSetupInput;
|
|
50
|
+
}
|
|
51
|
+
| {
|
|
52
|
+
status: 'error';
|
|
53
|
+
state: IProviderSetupFlowState;
|
|
54
|
+
message: string;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export function createProviderSetupFlow(
|
|
58
|
+
type: TProviderSetupType,
|
|
59
|
+
providerDefinitions: readonly IProviderDefinition[],
|
|
60
|
+
options: IProviderSetupFlowOptions = {},
|
|
61
|
+
): IProviderSetupFlowState {
|
|
62
|
+
const definition = getProviderSetupDefinition(type, providerDefinitions);
|
|
63
|
+
return {
|
|
64
|
+
type,
|
|
65
|
+
steps: applyProviderSetupInitialValues(
|
|
66
|
+
getProviderSetupSteps(definition).filter(
|
|
67
|
+
(step) => step.editOnly !== true || options.includeEditOnlySteps === true,
|
|
68
|
+
),
|
|
69
|
+
options.initialValues,
|
|
70
|
+
),
|
|
71
|
+
setupHelpLinks: definition.setupHelpLinks ?? [],
|
|
72
|
+
stepIndex: 0,
|
|
73
|
+
values: {},
|
|
74
|
+
existingProfileNames: options.existingProfileNames ?? [],
|
|
75
|
+
...(options.profileName !== undefined ? { profileName: options.profileName } : {}),
|
|
76
|
+
...(options.setCurrent !== undefined ? { setCurrent: options.setCurrent } : {}),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function formatProviderSetupSelectionPrompt(
|
|
81
|
+
providerDefinitions: readonly IProviderDefinition[],
|
|
82
|
+
): string {
|
|
83
|
+
if (providerDefinitions.length === 0) {
|
|
84
|
+
return ' No providers are available.';
|
|
85
|
+
}
|
|
86
|
+
const lines = [
|
|
87
|
+
' Select provider:',
|
|
88
|
+
...providerDefinitions.map(
|
|
89
|
+
(definition, index) => ` ${index + 1}. ${formatProviderSetupChoiceLabel(definition)}`,
|
|
90
|
+
),
|
|
91
|
+
` Provider [1-${providerDefinitions.length}] (default: 1): `,
|
|
92
|
+
];
|
|
93
|
+
return lines.join('\n');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function resolveProviderSetupSelection(
|
|
97
|
+
rawValue: string,
|
|
98
|
+
providerDefinitions: readonly IProviderDefinition[],
|
|
99
|
+
): TProviderSetupType {
|
|
100
|
+
const value = rawValue.trim();
|
|
101
|
+
const selectedValue = value.length > 0 ? value : '1';
|
|
102
|
+
const index = parseProviderSelectionIndex(selectedValue);
|
|
103
|
+
if (index !== undefined) {
|
|
104
|
+
const definition = providerDefinitions[index];
|
|
105
|
+
if (definition !== undefined) {
|
|
106
|
+
return definition.type;
|
|
107
|
+
}
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Provider selection ${selectedValue} is out of range. Currently supported: ${formatSupportedProviderTypes(providerDefinitions)}`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
const definition = findProviderDefinition(providerDefinitions, selectedValue);
|
|
113
|
+
if (definition === undefined) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`Unknown provider: ${selectedValue}. Currently supported: ${formatSupportedProviderTypes(providerDefinitions)}`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return definition.type;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function getProviderSetupStep(state: IProviderSetupFlowState): IProviderSetupPromptStep {
|
|
122
|
+
const step = state.steps[state.stepIndex];
|
|
123
|
+
if (step === undefined) {
|
|
124
|
+
throw new Error(`Provider setup step ${state.stepIndex} is out of range`);
|
|
125
|
+
}
|
|
126
|
+
return step;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function submitProviderSetupValue(
|
|
130
|
+
state: IProviderSetupFlowState,
|
|
131
|
+
rawValue: string,
|
|
132
|
+
): TProviderSetupFlowSubmitResult {
|
|
133
|
+
const step = getProviderSetupStep(state);
|
|
134
|
+
const value = rawValue.trim() || step.defaultValue || '';
|
|
135
|
+
const validationMessage = validateProviderSetupValue(step, value);
|
|
136
|
+
if (validationMessage !== undefined) {
|
|
137
|
+
return { status: 'error', state, message: validationMessage };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const nextState = {
|
|
141
|
+
...state,
|
|
142
|
+
stepIndex: state.stepIndex + 1,
|
|
143
|
+
values: { ...state.values, [step.key]: value },
|
|
144
|
+
};
|
|
145
|
+
if (nextState.stepIndex < state.steps.length) {
|
|
146
|
+
return { status: 'next', state: nextState };
|
|
147
|
+
}
|
|
148
|
+
return { status: 'complete', input: buildProviderSetupInput(nextState) };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function runProviderSetupPromptFlow(
|
|
152
|
+
type: TProviderSetupType,
|
|
153
|
+
promptInput: TPromptInput,
|
|
154
|
+
providerDefinitions: readonly IProviderDefinition[],
|
|
155
|
+
options: IProviderSetupFlowOptions = {},
|
|
156
|
+
): Promise<IProviderSetupInput> {
|
|
157
|
+
let state = createProviderSetupFlow(type, providerDefinitions, options);
|
|
158
|
+
const stepCount = state.steps.length;
|
|
159
|
+
while (state.stepIndex < stepCount) {
|
|
160
|
+
const step = getProviderSetupStep(state);
|
|
161
|
+
const value = await promptInput(
|
|
162
|
+
formatProviderSetupPromptLabel(step, state.setupHelpLinks),
|
|
163
|
+
step.masked === true,
|
|
164
|
+
);
|
|
165
|
+
const result = submitProviderSetupValue(state, value);
|
|
166
|
+
if (result.status === 'complete') {
|
|
167
|
+
return result.input;
|
|
168
|
+
}
|
|
169
|
+
if (result.status === 'error') {
|
|
170
|
+
throw new Error(result.message);
|
|
171
|
+
}
|
|
172
|
+
state = result.state;
|
|
173
|
+
}
|
|
174
|
+
throw new Error('Provider setup flow ended without completion');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function formatProviderSetupPromptLabel(
|
|
178
|
+
step: IProviderSetupPromptStep,
|
|
179
|
+
setupHelpLinks: readonly IProviderSetupHelpLink[] = [],
|
|
180
|
+
): string {
|
|
181
|
+
const suffix = step.defaultValue !== undefined ? ` (default: ${step.defaultValue})` : '';
|
|
182
|
+
const setupHelp = formatProviderSetupHelpLinks(setupHelpLinks);
|
|
183
|
+
const prefix = setupHelp.length > 0 ? `${setupHelp}\n` : '';
|
|
184
|
+
return `${prefix} ${step.title}${suffix}: `;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const CATEGORY_BADGES: Record<string, string> = {
|
|
188
|
+
'cloud-paid': '[Cloud/Paid]',
|
|
189
|
+
'cloud-free': '[Cloud/Free]',
|
|
190
|
+
'local-free': '[Local/Free]',
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
export function formatProviderSetupChoiceLabel(definition: IProviderDefinition): string {
|
|
194
|
+
const badge =
|
|
195
|
+
definition.category !== undefined ? `${CATEGORY_BADGES[definition.category] ?? ''} ` : '';
|
|
196
|
+
const name =
|
|
197
|
+
definition.displayName !== undefined
|
|
198
|
+
? `${definition.displayName} (${definition.type})`
|
|
199
|
+
: definition.type;
|
|
200
|
+
const label = `${badge}${name}`;
|
|
201
|
+
return definition.description !== undefined ? `${label} — ${definition.description}` : label;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function formatProviderSetupHelpLinks(
|
|
205
|
+
setupHelpLinks: readonly IProviderSetupHelpLink[] = [],
|
|
206
|
+
): string {
|
|
207
|
+
if (setupHelpLinks.length === 0) {
|
|
208
|
+
return '';
|
|
209
|
+
}
|
|
210
|
+
return setupHelpLinks
|
|
211
|
+
.map(
|
|
212
|
+
(link) =>
|
|
213
|
+
` Setup help: ${formatProviderSetupHelpLinkKind(link.kind)}: ${link.label} - ${link.url}`,
|
|
214
|
+
)
|
|
215
|
+
.join('\n');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function parseProviderSelectionIndex(value: string): number | undefined {
|
|
219
|
+
if (!/^\d+$/.test(value)) {
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
return Number(value) - 1;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function validateProviderSetupValue(
|
|
226
|
+
step: IProviderSetupPromptStep,
|
|
227
|
+
value: string,
|
|
228
|
+
): string | undefined {
|
|
229
|
+
if (step.required === true && value.length === 0) {
|
|
230
|
+
return 'Required';
|
|
231
|
+
}
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function getProviderSetupDefinition(
|
|
236
|
+
type: TProviderSetupType,
|
|
237
|
+
providerDefinitions: readonly IProviderDefinition[],
|
|
238
|
+
): IProviderDefinition {
|
|
239
|
+
const definition = findProviderDefinition(providerDefinitions, type);
|
|
240
|
+
if (definition === undefined) {
|
|
241
|
+
throw new Error(
|
|
242
|
+
`Unknown provider: ${type}. Currently supported: ${formatSupportedProviderTypes(providerDefinitions)}`,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
return definition;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function getProviderSetupSteps(definition: IProviderDefinition): IProviderSetupPromptStep[] {
|
|
249
|
+
if (definition.setupSteps !== undefined) {
|
|
250
|
+
return [...definition.setupSteps];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const steps: IProviderSetupPromptStep[] = [
|
|
254
|
+
{
|
|
255
|
+
key: 'model',
|
|
256
|
+
title: `${definition.type} model`,
|
|
257
|
+
defaultValue: definition.defaults?.model,
|
|
258
|
+
required: definition.defaults?.model === undefined,
|
|
259
|
+
},
|
|
260
|
+
];
|
|
261
|
+
if (definition.defaults?.baseURL !== undefined) {
|
|
262
|
+
steps.unshift({
|
|
263
|
+
key: 'baseURL',
|
|
264
|
+
title: `${definition.type} base URL`,
|
|
265
|
+
defaultValue: definition.defaults.baseURL,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
if (definition.requiresApiKey === true) {
|
|
269
|
+
steps.push({
|
|
270
|
+
key: 'apiKey',
|
|
271
|
+
title: `${definition.type} API key`,
|
|
272
|
+
defaultValue: definition.defaults?.apiKey,
|
|
273
|
+
required: definition.defaults?.apiKey === undefined,
|
|
274
|
+
masked: true,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
return steps;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function formatProviderSetupHelpLinkKind(kind: IProviderSetupHelpLink['kind']): string {
|
|
281
|
+
if (kind === 'api-key') {
|
|
282
|
+
return 'API key';
|
|
283
|
+
}
|
|
284
|
+
if (kind === 'console') {
|
|
285
|
+
return 'Console';
|
|
286
|
+
}
|
|
287
|
+
return 'Official';
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function applyProviderSetupInitialValues(
|
|
291
|
+
steps: readonly IProviderSetupPromptStep[],
|
|
292
|
+
initialValues: Partial<Record<TProviderSetupField, string>> | undefined,
|
|
293
|
+
): IProviderSetupPromptStep[] {
|
|
294
|
+
if (initialValues === undefined) {
|
|
295
|
+
return [...steps];
|
|
296
|
+
}
|
|
297
|
+
return steps.map((step) => {
|
|
298
|
+
const initialValue = initialValues[step.key];
|
|
299
|
+
if (initialValue === undefined) {
|
|
300
|
+
return step;
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
...step,
|
|
304
|
+
defaultValue: initialValue,
|
|
305
|
+
required: false,
|
|
306
|
+
};
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function buildProviderSetupInput(state: IProviderSetupFlowState): IProviderSetupInput {
|
|
311
|
+
const profile =
|
|
312
|
+
state.profileName ??
|
|
313
|
+
suggestProviderProfileName(
|
|
314
|
+
{ type: state.type },
|
|
315
|
+
{ existingProfileNames: state.existingProfileNames },
|
|
316
|
+
);
|
|
317
|
+
const apiKey = state.values.apiKey;
|
|
318
|
+
return {
|
|
319
|
+
profile,
|
|
320
|
+
type: state.type,
|
|
321
|
+
model: state.values.model,
|
|
322
|
+
...(apiKey !== undefined && apiKey.length > 0 && { apiKey }),
|
|
323
|
+
...(state.values.baseURL !== undefined && { baseURL: state.values.baseURL }),
|
|
324
|
+
setCurrent: state.setCurrent ?? true,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import {
|
|
2
|
+
checkSettingsDocument,
|
|
3
|
+
ProviderConfigError,
|
|
4
|
+
readMergedProviderSettings,
|
|
5
|
+
resolveEnvDefaultProvider,
|
|
6
|
+
applyProviderConfiguration,
|
|
7
|
+
resolveProviderSettingsWriteTarget,
|
|
8
|
+
WorkspaceAuthorityRequiredError,
|
|
9
|
+
} from '@robota-sdk/agent-framework';
|
|
10
|
+
|
|
11
|
+
import { runOnboardingBranch } from './provider-onboarding.js';
|
|
12
|
+
import { createProviderSetupFlow } from './provider-setup-flow.js';
|
|
13
|
+
import {
|
|
14
|
+
hasProviderConnectionMethods,
|
|
15
|
+
runProviderConnectionSetup,
|
|
16
|
+
} from './provider-connection-setup.js';
|
|
17
|
+
import { createProviderPromptInteraction } from './provider-prompt-interaction.js';
|
|
18
|
+
import {
|
|
19
|
+
formatProviderSetupSelectionPrompt,
|
|
20
|
+
resolveProviderSetupSelection,
|
|
21
|
+
runProviderSetupPromptFlow,
|
|
22
|
+
type TPromptInput,
|
|
23
|
+
} from './provider-setup-flow.js';
|
|
24
|
+
|
|
25
|
+
import type { IProviderDefinition, IUserInteraction } from '@robota-sdk/agent-core';
|
|
26
|
+
import type { ITerminalOutput } from '@robota-sdk/agent-core';
|
|
27
|
+
import type {
|
|
28
|
+
ISettingsDocumentStore,
|
|
29
|
+
TSettingsScope,
|
|
30
|
+
TSettingsSource,
|
|
31
|
+
IProviderConnectionHost,
|
|
32
|
+
IOrgPolicy,
|
|
33
|
+
TProviderSettingsDocument,
|
|
34
|
+
} from '@robota-sdk/agent-framework';
|
|
35
|
+
|
|
36
|
+
export interface IProviderStartupContext {
|
|
37
|
+
provider?: string;
|
|
38
|
+
settingsScope?: TSettingsScope;
|
|
39
|
+
settingsSources: readonly TSettingsSource[];
|
|
40
|
+
settingsStores: readonly ISettingsDocumentStore[];
|
|
41
|
+
/** Selected host snapshot for configure-time credential reference checks. */
|
|
42
|
+
env?: Record<string, string | undefined>;
|
|
43
|
+
connectionHost?: IProviderConnectionHost;
|
|
44
|
+
orgPolicy?: IOrgPolicy;
|
|
45
|
+
interaction?: IUserInteraction;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface IEnsureProviderConfigOptions {
|
|
49
|
+
formatError: (defs: readonly IProviderDefinition[]) => string;
|
|
50
|
+
isInteractive?: () => boolean;
|
|
51
|
+
/** Environment map for env-default synthesis (test seam, default: process.env). */
|
|
52
|
+
env?: Record<string, string | undefined>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function runProviderStartupSetup(
|
|
56
|
+
cwd: string,
|
|
57
|
+
ctx: IProviderStartupContext,
|
|
58
|
+
promptInput: TPromptInput,
|
|
59
|
+
terminal: ITerminalOutput,
|
|
60
|
+
providerDefinitions: readonly IProviderDefinition[],
|
|
61
|
+
): Promise<void> {
|
|
62
|
+
const onboarding = await runOnboardingBranch(
|
|
63
|
+
promptInput,
|
|
64
|
+
terminal,
|
|
65
|
+
providerDefinitions.some((definition) => definition.type === 'openrouter'),
|
|
66
|
+
);
|
|
67
|
+
const access = resolveSettingsAccess(ctx);
|
|
68
|
+
const existingProfileNames = Object.keys(
|
|
69
|
+
readMergedProviderSettings(access.sources).providers ?? {},
|
|
70
|
+
);
|
|
71
|
+
const settingsStore = selectSettingsStore(access.stores, ctx.settingsScope);
|
|
72
|
+
|
|
73
|
+
let type: string;
|
|
74
|
+
if (onboarding.preselectedType !== undefined) {
|
|
75
|
+
type = onboarding.preselectedType;
|
|
76
|
+
} else {
|
|
77
|
+
const providerChoice = await promptInput(
|
|
78
|
+
formatProviderSetupSelectionPrompt(providerDefinitions),
|
|
79
|
+
);
|
|
80
|
+
type = resolveProviderSetupSelection(providerChoice, providerDefinitions);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const setupOptions = {
|
|
84
|
+
providerDefinitions,
|
|
85
|
+
env: ctx.env,
|
|
86
|
+
connectionHost: ctx.connectionHost,
|
|
87
|
+
orgPolicy: ctx.orgPolicy,
|
|
88
|
+
settings: {
|
|
89
|
+
readMergedSettings: () => readMergedProviderSettings(access.sources),
|
|
90
|
+
readTargetSettings: () => settingsStore.read() as TProviderSettingsDocument,
|
|
91
|
+
writeTargetSettings: (settings: TProviderSettingsDocument) => settingsStore.write(settings),
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
if (hasProviderConnectionMethods(type, setupOptions)) {
|
|
95
|
+
const definitions =
|
|
96
|
+
ctx.interaction === undefined
|
|
97
|
+
? providerDefinitions.map((definition) => ({
|
|
98
|
+
...definition,
|
|
99
|
+
connectionMethods: definition.connectionMethods?.filter(
|
|
100
|
+
(method) => method !== 'browser',
|
|
101
|
+
),
|
|
102
|
+
}))
|
|
103
|
+
: providerDefinitions;
|
|
104
|
+
if (ctx.interaction === undefined)
|
|
105
|
+
terminal.writeLine(
|
|
106
|
+
'Browser connection is unavailable here. API key setup remains available.',
|
|
107
|
+
);
|
|
108
|
+
const result = await runProviderConnectionSetup(
|
|
109
|
+
ctx.interaction ?? createProviderPromptInteraction(promptInput),
|
|
110
|
+
createProviderSetupFlow(type, definitions, { existingProfileNames }),
|
|
111
|
+
{ ...setupOptions, providerDefinitions: definitions },
|
|
112
|
+
false,
|
|
113
|
+
);
|
|
114
|
+
if (!result.success) throw new ProviderConfigError(result.message);
|
|
115
|
+
if (result.hostActions === undefined) return;
|
|
116
|
+
} else {
|
|
117
|
+
const input = await runProviderSetupPromptFlow(type, promptInput, providerDefinitions, {
|
|
118
|
+
existingProfileNames,
|
|
119
|
+
});
|
|
120
|
+
applyProviderConfiguration(settingsStore, input, { providerDefinitions, env: ctx.env });
|
|
121
|
+
}
|
|
122
|
+
const language = await promptInput(' Response language (ko/en/ja/zh, default: en): ');
|
|
123
|
+
if (language) {
|
|
124
|
+
const settings = settingsStore.read();
|
|
125
|
+
settings.language = language;
|
|
126
|
+
settingsStore.write(settings);
|
|
127
|
+
}
|
|
128
|
+
terminal.writeLine(`\n Config saved to ${settingsStore.displayName}\n`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function ensureProviderConfig(
|
|
132
|
+
cwd: string,
|
|
133
|
+
ctx: IProviderStartupContext,
|
|
134
|
+
promptInput: TPromptInput,
|
|
135
|
+
terminal: ITerminalOutput,
|
|
136
|
+
providerDefinitions: readonly IProviderDefinition[],
|
|
137
|
+
options: IEnsureProviderConfigOptions,
|
|
138
|
+
): Promise<void> {
|
|
139
|
+
const access = resolveSettingsAccess(ctx);
|
|
140
|
+
const merged = readMergedProviderSettings(access.sources);
|
|
141
|
+
const selectedSettings =
|
|
142
|
+
ctx.provider !== undefined ? { ...merged, currentProvider: ctx.provider } : merged;
|
|
143
|
+
if (
|
|
144
|
+
checkSettingsDocument(selectedSettings, providerDefinitions, ctx.env ?? options.env) === 'valid'
|
|
145
|
+
) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
// Zero-config startup: a recognized provider env key with complete definition defaults
|
|
149
|
+
// makes setup unnecessary — resolution will synthesize an env-default config.
|
|
150
|
+
if (
|
|
151
|
+
ctx.provider === undefined &&
|
|
152
|
+
resolveEnvDefaultProvider(providerDefinitions, options.env) !== undefined
|
|
153
|
+
) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const checkInteractive = options.isInteractive ?? (() => false);
|
|
157
|
+
if (!checkInteractive()) {
|
|
158
|
+
throw new ProviderConfigError(options.formatError(providerDefinitions));
|
|
159
|
+
}
|
|
160
|
+
await runProviderStartupSetup(
|
|
161
|
+
cwd,
|
|
162
|
+
selectStartupContext({ ...ctx, env: ctx.env ?? options.env }, access.stores),
|
|
163
|
+
promptInput,
|
|
164
|
+
terminal,
|
|
165
|
+
providerDefinitions,
|
|
166
|
+
);
|
|
167
|
+
const updated = readMergedProviderSettings(access.sources);
|
|
168
|
+
const updatedSettings =
|
|
169
|
+
ctx.provider !== undefined ? { ...updated, currentProvider: ctx.provider } : updated;
|
|
170
|
+
if (
|
|
171
|
+
checkSettingsDocument(updatedSettings, providerDefinitions, ctx.env ?? options.env) !== 'valid'
|
|
172
|
+
) {
|
|
173
|
+
throw new ProviderConfigError(options.formatError(providerDefinitions));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function resolveSettingsAccess(ctx: IProviderStartupContext): {
|
|
178
|
+
sources: readonly TSettingsSource[];
|
|
179
|
+
stores: readonly ISettingsDocumentStore[];
|
|
180
|
+
} {
|
|
181
|
+
if (ctx.settingsSources === undefined || ctx.settingsStores === undefined) {
|
|
182
|
+
throw new Error('Provider startup requires host settings sources and stores.');
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
sources: ctx.settingsSources,
|
|
186
|
+
stores: ctx.settingsStores,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function selectSettingsStore(
|
|
191
|
+
stores: readonly ISettingsDocumentStore[],
|
|
192
|
+
scope: TSettingsScope | undefined,
|
|
193
|
+
): ISettingsDocumentStore {
|
|
194
|
+
if (scope === undefined) return resolveProviderSettingsWriteTarget(stores);
|
|
195
|
+
const targetScope = scope === 'user' ? 'user' : 'project-local';
|
|
196
|
+
const store = stores.findLast((candidate) => candidate.scope === targetScope);
|
|
197
|
+
if (store !== undefined) return store;
|
|
198
|
+
throw new WorkspaceAuthorityRequiredError(
|
|
199
|
+
`No authorized ${targetScope} settings store is available.`,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function selectStartupContext(
|
|
204
|
+
ctx: IProviderStartupContext,
|
|
205
|
+
stores: readonly ISettingsDocumentStore[],
|
|
206
|
+
): IProviderStartupContext {
|
|
207
|
+
if (ctx.settingsScope !== undefined || ctx.provider !== undefined) return ctx;
|
|
208
|
+
const currentProviderStore = findHighestPriorityCurrentProviderStore(stores);
|
|
209
|
+
if (currentProviderStore?.kind === 'project') return { ...ctx, settingsScope: 'project-local' };
|
|
210
|
+
return ctx;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function findHighestPriorityCurrentProviderStore(
|
|
214
|
+
stores: readonly ISettingsDocumentStore[],
|
|
215
|
+
): ISettingsDocumentStore | undefined {
|
|
216
|
+
for (let index = stores.length - 1; index >= 0; index -= 1) {
|
|
217
|
+
const store = stores[index];
|
|
218
|
+
if (store === undefined) continue;
|
|
219
|
+
const settings = store.read();
|
|
220
|
+
if (typeof settings.currentProvider === 'string') {
|
|
221
|
+
return store;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { createTestCommandHost } from '@robota-sdk/agent-framework/testing';
|
|
4
|
+
|
|
5
|
+
import { createRemoteControlCommandModule } from '../remote-control-command-module.js';
|
|
6
|
+
|
|
7
|
+
describe('createRemoteControlCommandModule metadata', () => {
|
|
8
|
+
it('keeps palette metadata aligned with the executable command and status as the safe default', async () => {
|
|
9
|
+
const module = createRemoteControlCommandModule();
|
|
10
|
+
const palette = module.commandSources?.[0]?.getCommands()[0];
|
|
11
|
+
const executable = module.systemCommands?.[0];
|
|
12
|
+
|
|
13
|
+
expect(palette).toBeDefined();
|
|
14
|
+
expect(executable).toBeDefined();
|
|
15
|
+
expect(palette?.argumentHint).toBe('[enable|stop|status|devices|revoke <device-id>]');
|
|
16
|
+
expect(palette?.description).toBe(executable?.description);
|
|
17
|
+
expect(palette?.modelInvocable).toBe(executable?.modelInvocable);
|
|
18
|
+
expect(palette?.argumentHint).toBe(executable?.argumentHint);
|
|
19
|
+
expect(palette?.userInvocable).toBe(executable?.userInvocable);
|
|
20
|
+
expect(palette?.subcommands?.map(({ name }) => name)).toEqual([
|
|
21
|
+
'status',
|
|
22
|
+
'devices',
|
|
23
|
+
'enable',
|
|
24
|
+
'stop',
|
|
25
|
+
'revoke',
|
|
26
|
+
]);
|
|
27
|
+
expect(palette?.subcommands).toEqual(executable?.subcommands);
|
|
28
|
+
expect(executable?.requiresPermission).toBe(false);
|
|
29
|
+
expect(executable?.modelInvocable).toBe(false);
|
|
30
|
+
expect(executable?.userInvocable).toBe(true);
|
|
31
|
+
|
|
32
|
+
const result = await executable!.execute(createTestCommandHost(), 'status');
|
|
33
|
+
expect(result.message).toContain('not available');
|
|
34
|
+
expect(result.hostActions).toBeUndefined();
|
|
35
|
+
});
|
|
36
|
+
});
|