@robota-sdk/agent-command 3.0.0-beta.76 → 3.0.0-beta.77
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/LICENSE +661 -21
- package/README.md +12 -6
- package/dist/node/index.cjs +38 -33
- package/dist/node/index.d.ts +68 -6
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +40 -35
- package/dist/node/index.js.map +1 -1
- package/package.json +7 -7
- package/src/agent/agent-command-parser.ts +1 -1
- package/src/agent/agent-command.ts +2 -1
- package/src/background/__tests__/background-command-module.test.ts +2 -5
- package/src/default/__tests__/default-command-modules.test.ts +5 -2
- package/src/default/default-command-modules.ts +6 -0
- package/src/editor/__tests__/editor-command-functional.test.ts +91 -0
- package/src/editor/editor-command-module.ts +47 -0
- package/src/editor/editor-command.ts +53 -0
- package/src/editor/index.ts +7 -0
- package/src/editor/resolve-editor.ts +21 -0
- package/src/exit/__tests__/exit-command-module.test.ts +21 -2
- package/src/exit/exit-command-module.ts +1 -10
- package/src/exit/exit-command.ts +15 -1
- package/src/goal/__tests__/goal-command.test.ts +75 -0
- package/src/goal/goal-command-module.ts +48 -0
- package/src/goal/goal-command.ts +70 -0
- package/src/goal/index.ts +6 -0
- package/src/index.ts +3 -0
- package/src/language/__tests__/language-command-module.test.ts +16 -0
- package/src/language/language-command-module.ts +1 -18
- package/src/language/language-command.ts +35 -9
- package/src/mode/__tests__/mode-command-module.test.ts +34 -0
- package/src/mode/mode-command-module.ts +1 -18
- package/src/mode/mode-command.ts +31 -8
- package/src/preset/__tests__/preset-command-module.test.ts +36 -0
- package/src/preset/preset-command-module.ts +1 -18
- package/src/preset/preset-command.ts +37 -8
- package/src/provider/__tests__/org-policy.test.ts +19 -13
- package/src/provider/__tests__/provider-command-module.test.ts +151 -80
- package/src/provider/__tests__/scripted-interaction.ts +28 -0
- package/src/provider/provider-command-execution.ts +67 -50
- package/src/provider/provider-command-module.ts +3 -19
- package/src/provider/provider-command-profile-lifecycle.ts +52 -72
- package/src/provider/provider-command-profile-operations.ts +15 -51
- package/src/provider/provider-command-profile.ts +44 -49
- package/src/provider/provider-command-setup.ts +56 -51
- package/src/session/__tests__/session-command-module.test.ts +38 -0
- package/src/session/session-command-module.ts +1 -10
- package/src/session/session-command.ts +14 -1
- package/src/shell/__tests__/shell-command-functional.test.ts +96 -0
- package/src/shell/index.ts +8 -0
- package/src/shell/resolve-shell.ts +25 -0
- package/src/shell/shell-command-module.ts +47 -0
- package/src/shell/shell-command.ts +44 -0
- package/src/shell/spawn-inherited.ts +32 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { confirmAction, isConfirmed, selectAction, textAction } from '@robota-sdk/agent-core';
|
|
1
2
|
import {
|
|
2
3
|
deleteProviderProfile,
|
|
3
4
|
sanitizeProviderProfileName,
|
|
@@ -6,48 +7,47 @@ import {
|
|
|
6
7
|
|
|
7
8
|
import { formatProviderChoiceLabel } from './provider-command-profile-operations.js';
|
|
8
9
|
|
|
10
|
+
import type { IUserInteraction } from '@robota-sdk/agent-core';
|
|
9
11
|
import type { IProviderCommandModuleOptions } from '@robota-sdk/agent-framework';
|
|
10
|
-
import type {
|
|
12
|
+
import type { ICommandResult } from '@robota-sdk/agent-interface-transport';
|
|
11
13
|
|
|
12
|
-
const YES = 'yes';
|
|
13
14
|
const MAX_DUPLICATE_PROFILE_SUFFIX = 1000;
|
|
14
15
|
const PROVIDER_RESTART_EFFECT = {
|
|
15
16
|
type: 'session-restart-requested',
|
|
16
17
|
reason: 'other',
|
|
17
18
|
} as const;
|
|
18
19
|
|
|
19
|
-
export function buildProviderDuplicate(
|
|
20
|
+
export async function buildProviderDuplicate(
|
|
21
|
+
ui: IUserInteraction,
|
|
20
22
|
profileName: string,
|
|
21
23
|
options: IProviderCommandModuleOptions,
|
|
22
|
-
): ICommandResult {
|
|
24
|
+
): Promise<ICommandResult> {
|
|
23
25
|
const settings = options.settings.readMergedSettings();
|
|
24
26
|
if (!settings.providers?.[profileName]) {
|
|
25
27
|
return { message: `Provider profile "${profileName}" was not found.`, success: false };
|
|
26
28
|
}
|
|
27
29
|
const defaultName = suggestDuplicateProfileName(profileName, Object.keys(settings.providers));
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
cancel: () => ({ message: 'Provider duplicate cancelled.', success: true }),
|
|
50
|
-
};
|
|
30
|
+
let errorMessage: string | undefined;
|
|
31
|
+
for (;;) {
|
|
32
|
+
const response = await ui.ask(
|
|
33
|
+
textAction('provider-duplicate', `Duplicate ${profileName} as`, {
|
|
34
|
+
description: errorMessage,
|
|
35
|
+
placeholder: defaultName,
|
|
36
|
+
allowEmpty: true,
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
if (response.type === 'cancelled') {
|
|
40
|
+
return { message: 'Provider duplicate cancelled.', success: true };
|
|
41
|
+
}
|
|
42
|
+
const value = response.text ?? '';
|
|
43
|
+
// Re-ask on validation failure, surfacing the reason (CMD-004 re-ask loop replaces the closure).
|
|
44
|
+
const validationMessage = validateDuplicateProfileName(value, defaultName, options);
|
|
45
|
+
if (validationMessage !== undefined) {
|
|
46
|
+
errorMessage = validationMessage;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
return completeProviderDuplicate(profileName, value, defaultName, options);
|
|
50
|
+
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
function validateDuplicateProfileName(
|
|
@@ -90,10 +90,11 @@ function completeProviderDuplicate(
|
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
export function buildProviderDelete(
|
|
93
|
+
export async function buildProviderDelete(
|
|
94
|
+
ui: IUserInteraction,
|
|
94
95
|
profileName: string,
|
|
95
96
|
options: IProviderCommandModuleOptions,
|
|
96
|
-
): ICommandResult {
|
|
97
|
+
): Promise<ICommandResult> {
|
|
97
98
|
const settings = options.settings.readMergedSettings();
|
|
98
99
|
const providers = settings.providers ?? {};
|
|
99
100
|
if (!providers[profileName]) {
|
|
@@ -108,40 +109,20 @@ export function buildProviderDelete(
|
|
|
108
109
|
success: false,
|
|
109
110
|
};
|
|
110
111
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function createProviderDeleteConfirmationInteraction(
|
|
119
|
-
profileName: string,
|
|
120
|
-
options: IProviderCommandModuleOptions,
|
|
121
|
-
): ICommandInteraction {
|
|
122
|
-
return {
|
|
123
|
-
prompt: {
|
|
124
|
-
kind: 'choice',
|
|
125
|
-
title: `Delete provider profile ${profileName}?`,
|
|
126
|
-
options: [
|
|
127
|
-
{ value: YES, label: 'Yes' },
|
|
128
|
-
{ value: 'no', label: 'No' },
|
|
129
|
-
],
|
|
130
|
-
},
|
|
131
|
-
submit: (value) => {
|
|
132
|
-
if (value !== YES) {
|
|
133
|
-
return { message: 'Provider delete cancelled.', success: true };
|
|
134
|
-
}
|
|
135
|
-
return confirmProviderDelete(profileName, options);
|
|
136
|
-
},
|
|
137
|
-
cancel: () => ({ message: 'Provider delete cancelled.', success: true }),
|
|
138
|
-
};
|
|
112
|
+
const response = await ui.ask(
|
|
113
|
+
confirmAction('provider-delete', `Delete provider profile ${profileName}?`),
|
|
114
|
+
);
|
|
115
|
+
if (!isConfirmed(response)) {
|
|
116
|
+
return { message: 'Provider delete cancelled.', success: true };
|
|
117
|
+
}
|
|
118
|
+
return confirmProviderDelete(ui, profileName, options);
|
|
139
119
|
}
|
|
140
120
|
|
|
141
|
-
function confirmProviderDelete(
|
|
121
|
+
async function confirmProviderDelete(
|
|
122
|
+
ui: IUserInteraction,
|
|
142
123
|
profileName: string,
|
|
143
124
|
options: IProviderCommandModuleOptions,
|
|
144
|
-
): ICommandResult {
|
|
125
|
+
): Promise<ICommandResult> {
|
|
145
126
|
const settings = options.settings.readMergedSettings();
|
|
146
127
|
if (settings.currentProvider !== profileName) {
|
|
147
128
|
options.settings.writeTargetSettings(
|
|
@@ -155,21 +136,20 @@ function confirmProviderDelete(
|
|
|
155
136
|
value: name,
|
|
156
137
|
label: formatProviderChoiceLabel(name, profile, settings.currentProvider),
|
|
157
138
|
}));
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
title: `Replacement provider for ${profileName}`,
|
|
165
|
-
options: replacementOptions,
|
|
139
|
+
const response = await ui.ask(
|
|
140
|
+
selectAction(
|
|
141
|
+
'provider-delete-replacement',
|
|
142
|
+
`Replacement provider for ${profileName}`,
|
|
143
|
+
replacementOptions,
|
|
144
|
+
{
|
|
166
145
|
maxVisible: 8,
|
|
167
146
|
},
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
}
|
|
147
|
+
),
|
|
148
|
+
);
|
|
149
|
+
if (response.type !== 'answer' || response.values[0] === undefined) {
|
|
150
|
+
return { message: 'Provider delete cancelled.', success: true };
|
|
151
|
+
}
|
|
152
|
+
return completeActiveProviderDelete(profileName, response.values[0], options);
|
|
173
153
|
}
|
|
174
154
|
|
|
175
155
|
function completeActiveProviderDelete(
|
|
@@ -6,19 +6,16 @@ import {
|
|
|
6
6
|
upsertProviderProfile,
|
|
7
7
|
} from '@robota-sdk/agent-framework';
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
toProviderSetupStepPrompt,
|
|
12
|
-
} from './provider-command-setup.js';
|
|
13
|
-
import { createProviderSetupFlow, submitProviderSetupValue } from './provider-setup-flow.js';
|
|
9
|
+
import { runProviderSetupAsk } from './provider-command-setup.js';
|
|
10
|
+
import { createProviderSetupFlow } from './provider-setup-flow.js';
|
|
14
11
|
|
|
15
|
-
import type {
|
|
12
|
+
import type { IUserInteraction } from '@robota-sdk/agent-core';
|
|
16
13
|
import type {
|
|
17
14
|
IProviderCommandModuleOptions,
|
|
18
15
|
IProviderProfileSettings,
|
|
19
16
|
IProviderSetupInput,
|
|
20
17
|
} from '@robota-sdk/agent-framework';
|
|
21
|
-
import type {
|
|
18
|
+
import type { ICommandResult } from '@robota-sdk/agent-interface-transport';
|
|
22
19
|
|
|
23
20
|
export function formatProviderChoiceLabel(
|
|
24
21
|
name: string,
|
|
@@ -69,10 +66,11 @@ export function buildProviderSwitch(
|
|
|
69
66
|
};
|
|
70
67
|
}
|
|
71
68
|
|
|
72
|
-
export function buildProviderEdit(
|
|
69
|
+
export async function buildProviderEdit(
|
|
70
|
+
ui: IUserInteraction,
|
|
73
71
|
profileName: string,
|
|
74
72
|
options: IProviderCommandModuleOptions,
|
|
75
|
-
): ICommandResult {
|
|
73
|
+
): Promise<ICommandResult> {
|
|
76
74
|
const settings = options.settings.readMergedSettings();
|
|
77
75
|
const profile = settings.providers?.[profileName];
|
|
78
76
|
if (!profile) {
|
|
@@ -81,20 +79,22 @@ export function buildProviderEdit(
|
|
|
81
79
|
if (!profile.type) {
|
|
82
80
|
return { message: `Provider profile "${profileName}" is missing type.`, success: false };
|
|
83
81
|
}
|
|
82
|
+
let flow;
|
|
84
83
|
try {
|
|
85
|
-
|
|
84
|
+
flow = createProviderSetupFlow(profile.type, options.providerDefinitions, {
|
|
86
85
|
profileName,
|
|
87
86
|
setCurrent: false,
|
|
88
87
|
initialValues: getProviderProfileSetupValues(profile),
|
|
89
88
|
});
|
|
90
|
-
return {
|
|
91
|
-
message: `Provider edit requested: ${profileName}`,
|
|
92
|
-
success: true,
|
|
93
|
-
interaction: createProviderEditInteraction(flow, profileName, options),
|
|
94
|
-
};
|
|
95
89
|
} catch (error) {
|
|
96
90
|
return { message: error instanceof Error ? error.message : String(error), success: false };
|
|
97
91
|
}
|
|
92
|
+
return runProviderSetupAsk(
|
|
93
|
+
ui,
|
|
94
|
+
flow,
|
|
95
|
+
(input) => completeProviderEdit(input, profileName, options),
|
|
96
|
+
'Provider edit cancelled.',
|
|
97
|
+
);
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
function getProviderProfileSetupValues(profile: IProviderProfileSettings): {
|
|
@@ -109,42 +109,6 @@ function getProviderProfileSetupValues(profile: IProviderProfileSettings): {
|
|
|
109
109
|
};
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
function createProviderEditInteraction(
|
|
113
|
-
flow: IProviderSetupFlowState,
|
|
114
|
-
profileName: string,
|
|
115
|
-
options: IProviderCommandModuleOptions,
|
|
116
|
-
): ICommandInteraction {
|
|
117
|
-
return {
|
|
118
|
-
prompt: toProviderSetupStepPrompt(flow),
|
|
119
|
-
submit: (value) => submitProviderEditInteractionValue(flow, profileName, value, options),
|
|
120
|
-
cancel: () => ({ message: 'Provider edit cancelled.', success: true }),
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function submitProviderEditInteractionValue(
|
|
125
|
-
flow: IProviderSetupFlowState,
|
|
126
|
-
profileName: string,
|
|
127
|
-
value: string,
|
|
128
|
-
options: IProviderCommandModuleOptions,
|
|
129
|
-
): ICommandResult {
|
|
130
|
-
const result = submitProviderSetupValue(flow, value);
|
|
131
|
-
if (result.status === 'error') {
|
|
132
|
-
return {
|
|
133
|
-
message: result.message,
|
|
134
|
-
success: false,
|
|
135
|
-
interaction: createProviderEditInteraction(flow, profileName, options),
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
if (result.status === 'complete') {
|
|
139
|
-
return completeProviderEdit(result.input, profileName, options);
|
|
140
|
-
}
|
|
141
|
-
return {
|
|
142
|
-
message: '',
|
|
143
|
-
success: true,
|
|
144
|
-
interaction: createProviderEditInteraction(result.state, profileName, options),
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
112
|
function completeProviderEdit(
|
|
149
113
|
input: IProviderSetupInput,
|
|
150
114
|
profileName: string,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { selectAction } from '@robota-sdk/agent-core';
|
|
1
2
|
import { testProviderProfileCommand } from '@robota-sdk/agent-framework';
|
|
2
3
|
|
|
3
4
|
import {
|
|
@@ -10,11 +11,12 @@ import {
|
|
|
10
11
|
formatProviderChoiceLabel,
|
|
11
12
|
} from './provider-command-profile-operations.js';
|
|
12
13
|
|
|
14
|
+
import type { IUserInteraction } from '@robota-sdk/agent-core';
|
|
13
15
|
import type {
|
|
14
16
|
IProviderCommandModuleOptions,
|
|
15
17
|
IProviderProfileSettings,
|
|
16
18
|
} from '@robota-sdk/agent-framework';
|
|
17
|
-
import type {
|
|
19
|
+
import type { ICommandResult } from '@robota-sdk/agent-interface-transport';
|
|
18
20
|
|
|
19
21
|
const ACTION_SWITCH = 'switch';
|
|
20
22
|
const ACTION_EDIT = 'edit';
|
|
@@ -23,64 +25,57 @@ const ACTION_DUPLICATE = 'duplicate';
|
|
|
23
25
|
const ACTION_DELETE = 'delete';
|
|
24
26
|
const ACTION_CANCEL = 'cancel';
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Ask the user to pick a provider profile, then drive its action menu (CMD-004 inline ask). Replaces
|
|
30
|
+
* the former returned choice→submit continuation chain — the picker's own option list is the rendered
|
|
31
|
+
* profile list, so the caller no longer prepends a separate list message.
|
|
32
|
+
*/
|
|
33
|
+
export async function askProviderProfileSelection(
|
|
34
|
+
ui: IUserInteraction,
|
|
27
35
|
currentProvider: string | undefined,
|
|
28
36
|
providers: Record<string, IProviderProfileSettings> | undefined,
|
|
29
37
|
options: IProviderCommandModuleOptions,
|
|
30
|
-
):
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
cancel: () => ({ message: 'Provider profile selection cancelled.', success: true }),
|
|
43
|
-
};
|
|
38
|
+
): Promise<ICommandResult> {
|
|
39
|
+
const profileOptions = Object.entries(providers ?? {}).map(([name, profile]) => ({
|
|
40
|
+
value: name,
|
|
41
|
+
label: formatProviderChoiceLabel(name, profile, currentProvider),
|
|
42
|
+
}));
|
|
43
|
+
const response = await ui.ask(
|
|
44
|
+
selectAction('provider-profile', 'Select provider profile', profileOptions, { maxVisible: 8 }),
|
|
45
|
+
);
|
|
46
|
+
if (response.type !== 'answer' || response.values[0] === undefined) {
|
|
47
|
+
return { message: 'Provider profile selection cancelled.', success: true };
|
|
48
|
+
}
|
|
49
|
+
return askProviderProfileAction(ui, response.values[0], options);
|
|
44
50
|
}
|
|
45
51
|
|
|
46
|
-
function
|
|
52
|
+
async function askProviderProfileAction(
|
|
53
|
+
ui: IUserInteraction,
|
|
47
54
|
profileName: string,
|
|
48
55
|
options: IProviderCommandModuleOptions,
|
|
49
|
-
): ICommandResult {
|
|
56
|
+
): Promise<ICommandResult> {
|
|
50
57
|
const settings = options.settings.readMergedSettings();
|
|
51
58
|
if (!settings.providers?.[profileName]) {
|
|
52
59
|
return { message: `Provider profile "${profileName}" was not found.`, success: false };
|
|
53
60
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
title: `Provider profile: ${profileName}`,
|
|
69
|
-
options: [
|
|
70
|
-
{ value: ACTION_SWITCH, label: 'Switch' },
|
|
71
|
-
{ value: ACTION_EDIT, label: 'Edit' },
|
|
72
|
-
{ value: ACTION_TEST, label: 'Test' },
|
|
73
|
-
{ value: ACTION_DUPLICATE, label: 'Duplicate' },
|
|
74
|
-
{ value: ACTION_DELETE, label: 'Delete' },
|
|
75
|
-
{ value: ACTION_CANCEL, label: 'Cancel' },
|
|
76
|
-
],
|
|
77
|
-
},
|
|
78
|
-
submit: (value) => executeProviderProfileAction(profileName, value, options),
|
|
79
|
-
cancel: () => ({ message: 'Provider profile action cancelled.', success: true }),
|
|
80
|
-
};
|
|
61
|
+
const response = await ui.ask(
|
|
62
|
+
selectAction('provider-profile-action', `Provider profile: ${profileName}`, [
|
|
63
|
+
{ value: ACTION_SWITCH, label: 'Switch' },
|
|
64
|
+
{ value: ACTION_EDIT, label: 'Edit' },
|
|
65
|
+
{ value: ACTION_TEST, label: 'Test' },
|
|
66
|
+
{ value: ACTION_DUPLICATE, label: 'Duplicate' },
|
|
67
|
+
{ value: ACTION_DELETE, label: 'Delete' },
|
|
68
|
+
{ value: ACTION_CANCEL, label: 'Cancel' },
|
|
69
|
+
]),
|
|
70
|
+
);
|
|
71
|
+
if (response.type !== 'answer' || response.values[0] === undefined) {
|
|
72
|
+
return { message: 'Provider profile action cancelled.', success: true };
|
|
73
|
+
}
|
|
74
|
+
return executeProviderProfileAction(ui, profileName, response.values[0], options);
|
|
81
75
|
}
|
|
82
76
|
|
|
83
77
|
async function executeProviderProfileAction(
|
|
78
|
+
ui: IUserInteraction,
|
|
84
79
|
profileName: string,
|
|
85
80
|
action: string,
|
|
86
81
|
options: IProviderCommandModuleOptions,
|
|
@@ -90,18 +85,18 @@ async function executeProviderProfileAction(
|
|
|
90
85
|
case ACTION_SWITCH:
|
|
91
86
|
return buildProviderSwitch(settings.providers, profileName, options);
|
|
92
87
|
case ACTION_EDIT:
|
|
93
|
-
return buildProviderEdit(profileName, options);
|
|
88
|
+
return buildProviderEdit(ui, profileName, options);
|
|
94
89
|
case ACTION_TEST:
|
|
95
|
-
return
|
|
90
|
+
return testProviderProfileCommand(
|
|
96
91
|
settings.currentProvider,
|
|
97
92
|
settings.providers,
|
|
98
93
|
profileName,
|
|
99
94
|
options,
|
|
100
95
|
);
|
|
101
96
|
case ACTION_DUPLICATE:
|
|
102
|
-
return buildProviderDuplicate(profileName, options);
|
|
97
|
+
return buildProviderDuplicate(ui, profileName, options);
|
|
103
98
|
case ACTION_DELETE:
|
|
104
|
-
return buildProviderDelete(profileName, options);
|
|
99
|
+
return buildProviderDelete(ui, profileName, options);
|
|
105
100
|
case ACTION_CANCEL:
|
|
106
101
|
return { message: 'Provider profile action cancelled.', success: true };
|
|
107
102
|
default:
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { textAction } from '@robota-sdk/agent-core';
|
|
1
2
|
import { buildProviderSetupPatch, mergeProviderPatch } from '@robota-sdk/agent-framework';
|
|
2
3
|
|
|
3
4
|
import {
|
|
@@ -5,19 +6,15 @@ import {
|
|
|
5
6
|
formatProviderSetupHelpLinks,
|
|
6
7
|
getProviderSetupStep,
|
|
7
8
|
submitProviderSetupValue,
|
|
8
|
-
validateProviderSetupValue,
|
|
9
9
|
} from './provider-setup-flow.js';
|
|
10
10
|
|
|
11
11
|
import type { IProviderSetupFlowState } from './provider-setup-flow.js';
|
|
12
|
+
import type { IActionRequest, IUserInteraction } from '@robota-sdk/agent-core';
|
|
12
13
|
import type {
|
|
13
14
|
IProviderCommandModuleOptions,
|
|
14
15
|
IProviderSetupInput,
|
|
15
16
|
} from '@robota-sdk/agent-framework';
|
|
16
|
-
import type {
|
|
17
|
-
ICommandInteraction,
|
|
18
|
-
ICommandResult,
|
|
19
|
-
TCommandInteractionPrompt,
|
|
20
|
-
} from '@robota-sdk/agent-interface-transport';
|
|
17
|
+
import type { ICommandResult } from '@robota-sdk/agent-interface-transport';
|
|
21
18
|
|
|
22
19
|
const PROVIDER_RESTART_EFFECT = {
|
|
23
20
|
type: 'session-restart-requested',
|
|
@@ -33,62 +30,70 @@ export function createSetupFlow(
|
|
|
33
30
|
});
|
|
34
31
|
}
|
|
35
32
|
|
|
36
|
-
|
|
33
|
+
/** Build the per-step `IActionRequest` for the current setup step (CMD-004 inline ask). */
|
|
34
|
+
function toProviderSetupStepRequest(
|
|
37
35
|
flow: IProviderSetupFlowState,
|
|
38
|
-
|
|
39
|
-
):
|
|
40
|
-
return {
|
|
41
|
-
prompt: toProviderSetupStepPrompt(flow),
|
|
42
|
-
submit: (value) => submitProviderSetupInteractionValue(flow, value, options),
|
|
43
|
-
cancel: () => ({ message: 'Provider setup cancelled.', success: true }),
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function toProviderSetupStepPrompt(
|
|
48
|
-
flow: IProviderSetupFlowState,
|
|
49
|
-
): TCommandInteractionPrompt {
|
|
36
|
+
errorMessage?: string,
|
|
37
|
+
): IActionRequest {
|
|
50
38
|
const step = getProviderSetupStep(flow);
|
|
51
39
|
const placeholder =
|
|
52
40
|
step.masked === true && step.defaultValue !== undefined ? '(unchanged)' : step.defaultValue;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
41
|
+
const helpLinks = formatProviderSetupHelpLinks(flow.setupHelpLinks);
|
|
42
|
+
const description =
|
|
43
|
+
[errorMessage, helpLinks.length > 0 ? helpLinks : undefined]
|
|
44
|
+
.filter((part): part is string => part !== undefined && part.length > 0)
|
|
45
|
+
.join('\n') || undefined;
|
|
46
|
+
return textAction(`provider-setup-${step.key}`, step.title, {
|
|
47
|
+
description,
|
|
48
|
+
placeholder,
|
|
49
|
+
allowEmpty: step.defaultValue !== undefined,
|
|
50
|
+
masked: step.masked,
|
|
51
|
+
});
|
|
62
52
|
}
|
|
63
53
|
|
|
64
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Drive the setup-step engine through inline `ui.ask` calls (CMD-004), re-asking the same step when a
|
|
56
|
+
* step fails validation (the error is surfaced in the request description). The `complete` sink differs
|
|
57
|
+
* between the add and edit paths.
|
|
58
|
+
*/
|
|
59
|
+
export async function runProviderSetupAsk(
|
|
60
|
+
ui: IUserInteraction,
|
|
65
61
|
flow: IProviderSetupFlowState,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
complete: (input: IProviderSetupInput) => ICommandResult,
|
|
63
|
+
cancelMessage: string,
|
|
64
|
+
): Promise<ICommandResult> {
|
|
65
|
+
let state = flow;
|
|
66
|
+
let errorMessage: string | undefined;
|
|
67
|
+
for (;;) {
|
|
68
|
+
const response = await ui.ask(toProviderSetupStepRequest(state, errorMessage));
|
|
69
|
+
if (response.type === 'cancelled') {
|
|
70
|
+
return { message: cancelMessage, success: true };
|
|
71
|
+
}
|
|
72
|
+
const result = submitProviderSetupValue(state, response.text ?? '');
|
|
73
|
+
if (result.status === 'error') {
|
|
74
|
+
errorMessage = result.message;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (result.status === 'complete') {
|
|
78
|
+
return complete(result.input);
|
|
79
|
+
}
|
|
80
|
+
state = result.state;
|
|
81
|
+
errorMessage = undefined;
|
|
82
|
+
}
|
|
69
83
|
}
|
|
70
84
|
|
|
71
|
-
|
|
85
|
+
/** Run the `/provider add` setup wizard inline. */
|
|
86
|
+
export function runProviderAddSetup(
|
|
87
|
+
ui: IUserInteraction,
|
|
72
88
|
flow: IProviderSetupFlowState,
|
|
73
|
-
value: string,
|
|
74
89
|
options: IProviderCommandModuleOptions,
|
|
75
|
-
): ICommandResult {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
if (result.status === 'complete') {
|
|
85
|
-
return completeProviderSetup(result.input, options);
|
|
86
|
-
}
|
|
87
|
-
return {
|
|
88
|
-
message: '',
|
|
89
|
-
success: true,
|
|
90
|
-
interaction: createProviderSetupInteraction(result.state, options),
|
|
91
|
-
};
|
|
90
|
+
): Promise<ICommandResult> {
|
|
91
|
+
return runProviderSetupAsk(
|
|
92
|
+
ui,
|
|
93
|
+
flow,
|
|
94
|
+
(input) => completeProviderSetup(input, options),
|
|
95
|
+
'Provider setup cancelled.',
|
|
96
|
+
);
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
function completeProviderSetup(
|
|
@@ -201,6 +201,44 @@ describe('createSessionCommandModule', () => {
|
|
|
201
201
|
});
|
|
202
202
|
});
|
|
203
203
|
|
|
204
|
+
it('CMD-004: confirms before clearing and proceeds on yes', async () => {
|
|
205
|
+
const clearConversationHistory = vi.fn();
|
|
206
|
+
const context = {
|
|
207
|
+
...createCommandContext(),
|
|
208
|
+
clearConversationHistory,
|
|
209
|
+
getUserInteraction: () => ({
|
|
210
|
+
ask: async () => ({ type: 'answer' as const, values: ['yes'] }),
|
|
211
|
+
}),
|
|
212
|
+
};
|
|
213
|
+
const executor = new SystemCommandExecutor([
|
|
214
|
+
...(createSessionCommandModule().systemCommands ?? []),
|
|
215
|
+
]);
|
|
216
|
+
|
|
217
|
+
const result = await executor.execute('clear', context, '');
|
|
218
|
+
|
|
219
|
+
expect(clearConversationHistory).toHaveBeenCalledTimes(1);
|
|
220
|
+
expect(result?.effects).toEqual([{ type: 'conversation-history-cleared' }]);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('CMD-004: cancels the clear when the user declines', async () => {
|
|
224
|
+
const clearConversationHistory = vi.fn();
|
|
225
|
+
const context = {
|
|
226
|
+
...createCommandContext(),
|
|
227
|
+
clearConversationHistory,
|
|
228
|
+
getUserInteraction: () => ({
|
|
229
|
+
ask: async () => ({ type: 'answer' as const, values: ['no'] }),
|
|
230
|
+
}),
|
|
231
|
+
};
|
|
232
|
+
const executor = new SystemCommandExecutor([
|
|
233
|
+
...(createSessionCommandModule().systemCommands ?? []),
|
|
234
|
+
]);
|
|
235
|
+
|
|
236
|
+
const result = await executor.execute('clear', context, '');
|
|
237
|
+
|
|
238
|
+
expect(clearConversationHistory).not.toHaveBeenCalled();
|
|
239
|
+
expect(result?.message).toBe('Clear cancelled.');
|
|
240
|
+
});
|
|
241
|
+
|
|
204
242
|
it('falls back to runtime clearHistory when the host has not implemented the richer API', async () => {
|
|
205
243
|
const runtime = createRuntime();
|
|
206
244
|
const context = {
|
|
@@ -15,11 +15,7 @@ import {
|
|
|
15
15
|
} from './session-command.js';
|
|
16
16
|
|
|
17
17
|
import type { ICommandModule, ISystemCommand } from '@robota-sdk/agent-framework';
|
|
18
|
-
import type {
|
|
19
|
-
ICommand,
|
|
20
|
-
ICommandInteractionHint,
|
|
21
|
-
ICommandSource,
|
|
22
|
-
} from '@robota-sdk/agent-interface-transport';
|
|
18
|
+
import type { ICommand, ICommandSource } from '@robota-sdk/agent-interface-transport';
|
|
23
19
|
|
|
24
20
|
export function createClearCommandEntry(): ICommand {
|
|
25
21
|
return {
|
|
@@ -155,10 +151,6 @@ export class SessionCommandSource implements ICommandSource {
|
|
|
155
151
|
}
|
|
156
152
|
}
|
|
157
153
|
|
|
158
|
-
const SESSION_INTERACTION_HINTS: Record<string, ICommandInteractionHint> = {
|
|
159
|
-
clear: { type: 'confirm', message: 'Clear conversation history?' },
|
|
160
|
-
};
|
|
161
|
-
|
|
162
154
|
export function createSessionCommandModule(): ICommandModule {
|
|
163
155
|
return {
|
|
164
156
|
name: 'agent-command-session',
|
|
@@ -170,6 +162,5 @@ export function createSessionCommandModule(): ICommandModule {
|
|
|
170
162
|
createCostSystemCommand(),
|
|
171
163
|
createValidateSessionSystemCommand(),
|
|
172
164
|
],
|
|
173
|
-
interactionHints: SESSION_INTERACTION_HINTS,
|
|
174
165
|
};
|
|
175
166
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
|
|
4
|
+
import { confirmAction, isConfirmed } from '@robota-sdk/agent-core';
|
|
4
5
|
import {
|
|
5
6
|
RENAME_COMMAND_USAGE,
|
|
6
7
|
clearConversationHistory,
|
|
@@ -19,7 +20,19 @@ import type { ICommandResult } from '@robota-sdk/agent-interface-transport';
|
|
|
19
20
|
|
|
20
21
|
export const CLEAR_COMMAND_MESSAGE = 'Conversation cleared.';
|
|
21
22
|
|
|
22
|
-
export function executeClearCommand(
|
|
23
|
+
export async function executeClearCommand(
|
|
24
|
+
context: ICommandHostContext,
|
|
25
|
+
_args: string,
|
|
26
|
+
): Promise<ICommandResult> {
|
|
27
|
+
// Confirm only when an interactive renderer is attached; with no human the explicit /clear proceeds.
|
|
28
|
+
const ui = context.getUserInteraction?.();
|
|
29
|
+
if (ui) {
|
|
30
|
+
const response = await ui.ask(confirmAction('clear', 'Clear conversation history?'));
|
|
31
|
+
if (!isConfirmed(response)) {
|
|
32
|
+
return { success: true, message: 'Clear cancelled.' };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
23
36
|
clearConversationHistory(context);
|
|
24
37
|
return {
|
|
25
38
|
success: true,
|