@agent-api/cli 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -5
- package/dist/index.js +8 -6
- package/dist/runtime.d.ts +4 -0
- package/dist/runtime.js +4 -0
- package/dist/tui/ink/app.d.ts +1 -1
- package/dist/tui/ink/app.js +27 -130
- package/dist/tui/ink/components.d.ts +1 -2
- package/dist/tui/ink/components.js +1 -3
- package/package.json +9 -6
- package/dist/agent/runner.d.ts +0 -117
- package/dist/agent/runner.js +0 -486
- package/dist/agent.d.ts +0 -2
- package/dist/agent.js +0 -2
- package/dist/chat-options.d.ts +0 -37
- package/dist/chat-options.js +0 -42
- package/dist/config.d.ts +0 -66
- package/dist/config.js +0 -201
- package/dist/conversation/index.d.ts +0 -17
- package/dist/conversation/index.js +0 -54
- package/dist/profile.d.ts +0 -57
- package/dist/profile.js +0 -211
- package/dist/runtime/index.d.ts +0 -5
- package/dist/runtime/index.js +0 -152
- package/dist/tui/workbench.d.ts +0 -187
- package/dist/tui/workbench.js +0 -392
- package/dist/update.d.ts +0 -16
- package/dist/update.js +0 -74
- package/dist/workbench/auth-controller.d.ts +0 -43
- package/dist/workbench/auth-controller.js +0 -84
- package/dist/workbench/auth-gate-controller.d.ts +0 -62
- package/dist/workbench/auth-gate-controller.js +0 -231
- package/dist/workbench/command-controller.d.ts +0 -29
- package/dist/workbench/command-controller.js +0 -426
- package/dist/workbench/conversation-controller.d.ts +0 -32
- package/dist/workbench/conversation-controller.js +0 -53
- package/dist/workbench/engine.d.ts +0 -66
- package/dist/workbench/engine.js +0 -291
- package/dist/workbench/input-controller.d.ts +0 -44
- package/dist/workbench/input-controller.js +0 -71
- package/dist/workbench/isolator-installer.d.ts +0 -29
- package/dist/workbench/isolator-installer.js +0 -208
- package/dist/workbench/lifecycle-controller.d.ts +0 -30
- package/dist/workbench/lifecycle-controller.js +0 -75
- package/dist/workbench/local-controller.d.ts +0 -21
- package/dist/workbench/local-controller.js +0 -94
- package/dist/workbench/render-model.d.ts +0 -46
- package/dist/workbench/render-model.js +0 -61
- package/dist/workbench/runtime-controller.d.ts +0 -12
- package/dist/workbench/runtime-controller.js +0 -57
- package/dist/workbench/session.d.ts +0 -27
- package/dist/workbench/session.js +0 -42
- package/dist/workbench/settings-controller.d.ts +0 -80
- package/dist/workbench/settings-controller.js +0 -309
- package/dist/workbench/shell-isolation.d.ts +0 -20
- package/dist/workbench/shell-isolation.js +0 -13
- package/dist/workbench/turn-controller.d.ts +0 -25
- package/dist/workbench/turn-controller.js +0 -164
- package/dist/workbench/view-model.d.ts +0 -34
- package/dist/workbench/view-model.js +0 -121
- package/dist/workdir/index.d.ts +0 -22
- package/dist/workdir/index.js +0 -46
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
import { defaultBaseURL } from "../config.js";
|
|
2
|
-
import { createWorkbenchAuthController, } from "./auth-controller.js";
|
|
3
|
-
export const authMethods = [
|
|
4
|
-
{ method: "browser", label: "Browser session", description: "Interactive login with refreshable local session." },
|
|
5
|
-
{ method: "api_key", label: "API key", description: "Paste a static API key for shell-only environments." },
|
|
6
|
-
{ method: "exit", label: "Exit", description: "Leave without signing in." },
|
|
7
|
-
];
|
|
8
|
-
export function createWorkbenchAuthGateController(options = {}) {
|
|
9
|
-
const authController = options.authController ?? createWorkbenchAuthController();
|
|
10
|
-
const formatError = options.formatError ?? userFacingError;
|
|
11
|
-
return {
|
|
12
|
-
authMethods,
|
|
13
|
-
initialState(input) {
|
|
14
|
-
return {
|
|
15
|
-
status: "checking",
|
|
16
|
-
selectedMethod: 0,
|
|
17
|
-
profile: input.profile || "default",
|
|
18
|
-
baseURL: input.baseURL || process.env.AGENT_API_BASE_URL || defaultBaseURL,
|
|
19
|
-
apiKey: input.apiKey || process.env.AGENT_API_KEY || "",
|
|
20
|
-
message: "Checking local auth profile...",
|
|
21
|
-
error: "",
|
|
22
|
-
browserURL: "",
|
|
23
|
-
browserCode: "",
|
|
24
|
-
};
|
|
25
|
-
},
|
|
26
|
-
async check(profile) {
|
|
27
|
-
try {
|
|
28
|
-
const result = await authController.check(profile);
|
|
29
|
-
return {
|
|
30
|
-
profileName: result.profileName,
|
|
31
|
-
state: readyState(profile || result.profileName, `Authenticated.`),
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
catch (error) {
|
|
35
|
-
return {
|
|
36
|
-
state: selectState(profile || "default", "Choose an auth method to continue into the workbench.", formatError(error)),
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
handleInput(input, key, state) {
|
|
41
|
-
if (key.ctrl && input === "c")
|
|
42
|
-
return inputResult(state, { type: "exit" });
|
|
43
|
-
if (state.status === "ready" || state.status === "checking" || state.status === "browser_waiting")
|
|
44
|
-
return inputResult(state);
|
|
45
|
-
if (state.status === "select")
|
|
46
|
-
return handleSelectInput(key, state);
|
|
47
|
-
if (key.return)
|
|
48
|
-
return inputResult(state, { type: "submit" });
|
|
49
|
-
if (key.backspace || key.delete)
|
|
50
|
-
return inputResult(editAuthField(state, (value) => value.slice(0, -1)));
|
|
51
|
-
if (input && !key.ctrl && !key.meta)
|
|
52
|
-
return inputResult(editAuthField(state, (value) => value + input));
|
|
53
|
-
return inputResult(state);
|
|
54
|
-
},
|
|
55
|
-
async submit(state, submitOptions = {}) {
|
|
56
|
-
const profile = state.profile.trim() || "default";
|
|
57
|
-
const baseURL = state.baseURL.trim() || defaultBaseURL;
|
|
58
|
-
switch (state.status) {
|
|
59
|
-
case "api_profile":
|
|
60
|
-
return { state: { ...state, profile, status: "api_base_url", error: "" } };
|
|
61
|
-
case "api_base_url":
|
|
62
|
-
return { state: { ...state, baseURL, status: "api_key", error: "" } };
|
|
63
|
-
case "api_key":
|
|
64
|
-
return loginWithAPIKey(authController, state, { baseURL, formatError, profile });
|
|
65
|
-
case "browser_profile":
|
|
66
|
-
return { state: { ...state, profile, status: "browser_base_url", error: "" } };
|
|
67
|
-
case "browser_base_url":
|
|
68
|
-
return loginWithBrowser(authController, state, { baseURL, formatError, onState: submitOptions.onState, profile });
|
|
69
|
-
default:
|
|
70
|
-
return { state };
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
requestLogin(state, profileName) {
|
|
74
|
-
return {
|
|
75
|
-
...state,
|
|
76
|
-
profile: profileName,
|
|
77
|
-
status: "select",
|
|
78
|
-
message: "Choose an auth method to continue into the workbench.",
|
|
79
|
-
error: "",
|
|
80
|
-
};
|
|
81
|
-
},
|
|
82
|
-
requestLogout(state, profileName) {
|
|
83
|
-
return {
|
|
84
|
-
...state,
|
|
85
|
-
profile: profileName,
|
|
86
|
-
status: "select",
|
|
87
|
-
message: `Logged out of profile "${profileName}" for this app session. Choose an auth method to continue.`,
|
|
88
|
-
error: "",
|
|
89
|
-
};
|
|
90
|
-
},
|
|
91
|
-
requestSwitchProfile(state, currentProfile, nextProfile) {
|
|
92
|
-
return {
|
|
93
|
-
...state,
|
|
94
|
-
profile: nextProfile || currentProfile,
|
|
95
|
-
status: "select",
|
|
96
|
-
message: nextProfile ? `Choose an auth method for profile "${nextProfile}".` : "Choose an auth method for another profile.",
|
|
97
|
-
error: "",
|
|
98
|
-
};
|
|
99
|
-
},
|
|
100
|
-
deletedProfile(state, profileName) {
|
|
101
|
-
return {
|
|
102
|
-
...state,
|
|
103
|
-
status: "select",
|
|
104
|
-
message: `Deleted profile "${profileName}". Choose an auth method to continue.`,
|
|
105
|
-
error: "",
|
|
106
|
-
};
|
|
107
|
-
},
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
function handleSelectInput(key, state) {
|
|
111
|
-
if (key.upArrow) {
|
|
112
|
-
return inputResult({ ...state, selectedMethod: Math.max(0, state.selectedMethod - 1) });
|
|
113
|
-
}
|
|
114
|
-
if (key.downArrow) {
|
|
115
|
-
return inputResult({ ...state, selectedMethod: Math.min(authMethods.length - 1, state.selectedMethod + 1) });
|
|
116
|
-
}
|
|
117
|
-
if (key.return) {
|
|
118
|
-
const method = authMethods[state.selectedMethod]?.method;
|
|
119
|
-
if (method === "exit")
|
|
120
|
-
return inputResult(state, { type: "exit" });
|
|
121
|
-
return inputResult({
|
|
122
|
-
...state,
|
|
123
|
-
status: method === "api_key" ? "api_profile" : "browser_profile",
|
|
124
|
-
message: method === "api_key" ? "Save an API key profile." : "Create a browser session profile.",
|
|
125
|
-
error: "",
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
return inputResult(state);
|
|
129
|
-
}
|
|
130
|
-
async function loginWithAPIKey(authController, state, input) {
|
|
131
|
-
const apiKey = state.apiKey.trim();
|
|
132
|
-
if (!apiKey)
|
|
133
|
-
return { state: { ...state, error: "API key is required." } };
|
|
134
|
-
const saving = { ...state, message: "Saving API key profile...", error: "" };
|
|
135
|
-
try {
|
|
136
|
-
const saved = await authController.loginAPIKey({ profile: input.profile, baseURL: input.baseURL, apiKey });
|
|
137
|
-
return {
|
|
138
|
-
profileName: saved.profileName,
|
|
139
|
-
state: { ...saving, status: "ready", message: `Signed in as profile "${input.profile}".`, error: "" },
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
catch (error) {
|
|
143
|
-
return { state: { ...saving, error: input.formatError(error) } };
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
async function loginWithBrowser(authController, state, input) {
|
|
147
|
-
const waiting = {
|
|
148
|
-
...state,
|
|
149
|
-
profile: input.profile,
|
|
150
|
-
baseURL: input.baseURL,
|
|
151
|
-
status: "browser_waiting",
|
|
152
|
-
message: "Starting browser auth challenge...",
|
|
153
|
-
error: "",
|
|
154
|
-
browserURL: "",
|
|
155
|
-
browserCode: "",
|
|
156
|
-
};
|
|
157
|
-
input.onState?.(waiting);
|
|
158
|
-
try {
|
|
159
|
-
const saved = await authController.loginBrowser({
|
|
160
|
-
profile: input.profile,
|
|
161
|
-
baseURL: input.baseURL,
|
|
162
|
-
onChallenge(challenge) {
|
|
163
|
-
input.onState?.({
|
|
164
|
-
...waiting,
|
|
165
|
-
message: "Open the URL to approve this terminal session.",
|
|
166
|
-
browserURL: challenge.url,
|
|
167
|
-
browserCode: challenge.code,
|
|
168
|
-
});
|
|
169
|
-
},
|
|
170
|
-
onStatus(status) {
|
|
171
|
-
input.onState?.({ ...waiting, message: `Browser auth status: ${status}` });
|
|
172
|
-
},
|
|
173
|
-
});
|
|
174
|
-
return {
|
|
175
|
-
profileName: saved.profileName,
|
|
176
|
-
state: { ...waiting, status: "ready", message: `Signed in as profile "${input.profile}".`, error: "" },
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
catch (error) {
|
|
180
|
-
return {
|
|
181
|
-
state: {
|
|
182
|
-
...waiting,
|
|
183
|
-
status: "select",
|
|
184
|
-
message: "Browser auth did not complete. Choose an auth method to continue.",
|
|
185
|
-
error: input.formatError(error),
|
|
186
|
-
},
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
function editAuthField(state, update) {
|
|
191
|
-
switch (state.status) {
|
|
192
|
-
case "api_profile":
|
|
193
|
-
case "browser_profile":
|
|
194
|
-
return { ...state, profile: update(state.profile), error: "" };
|
|
195
|
-
case "api_base_url":
|
|
196
|
-
case "browser_base_url":
|
|
197
|
-
return { ...state, baseURL: update(state.baseURL), error: "" };
|
|
198
|
-
case "api_key":
|
|
199
|
-
return { ...state, apiKey: update(state.apiKey), error: "" };
|
|
200
|
-
default:
|
|
201
|
-
return state;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
function readyState(profile, message) {
|
|
205
|
-
return {
|
|
206
|
-
status: "ready",
|
|
207
|
-
selectedMethod: 0,
|
|
208
|
-
profile,
|
|
209
|
-
baseURL: process.env.AGENT_API_BASE_URL || defaultBaseURL,
|
|
210
|
-
apiKey: process.env.AGENT_API_KEY || "",
|
|
211
|
-
message,
|
|
212
|
-
error: "",
|
|
213
|
-
browserURL: "",
|
|
214
|
-
browserCode: "",
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
function selectState(profile, message, error = "") {
|
|
218
|
-
return {
|
|
219
|
-
...readyState(profile, message),
|
|
220
|
-
status: "select",
|
|
221
|
-
error,
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
function inputResult(state, ...effects) {
|
|
225
|
-
return { state, effects };
|
|
226
|
-
}
|
|
227
|
-
function userFacingError(error) {
|
|
228
|
-
if (error instanceof Error)
|
|
229
|
-
return error.message;
|
|
230
|
-
return String(error);
|
|
231
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { AgentRunOptions } from "../agent.js";
|
|
2
|
-
import type { WorkbenchCommand } from "../tui/workbench.js";
|
|
3
|
-
import type { WorkbenchAuthController } from "./auth-controller.js";
|
|
4
|
-
import type { WorkbenchConversationController } from "./conversation-controller.js";
|
|
5
|
-
import type { WorkbenchEffect, WorkbenchEngine } from "./engine.js";
|
|
6
|
-
import type { WorkbenchLocalController } from "./local-controller.js";
|
|
7
|
-
import type { WorkbenchSettingsController } from "./settings-controller.js";
|
|
8
|
-
import type { WorkbenchTurnController } from "./turn-controller.js";
|
|
9
|
-
export interface WorkbenchCommandController {
|
|
10
|
-
run(command: WorkbenchCommand): Promise<void>;
|
|
11
|
-
runEffects(effects: WorkbenchEffect[]): Promise<void>;
|
|
12
|
-
}
|
|
13
|
-
export interface WorkbenchCommandControllerOptions {
|
|
14
|
-
authController: WorkbenchAuthController;
|
|
15
|
-
conversationController: WorkbenchConversationController;
|
|
16
|
-
engine: WorkbenchEngine;
|
|
17
|
-
localController: WorkbenchLocalController;
|
|
18
|
-
options: AgentRunOptions;
|
|
19
|
-
profileName: string;
|
|
20
|
-
settingsController: WorkbenchSettingsController;
|
|
21
|
-
turnController: WorkbenchTurnController;
|
|
22
|
-
onDeleteProfile(): Promise<void>;
|
|
23
|
-
onExit(): void;
|
|
24
|
-
onLogin(): void;
|
|
25
|
-
onLogout(): void;
|
|
26
|
-
onSwitchProfile(name?: string): void;
|
|
27
|
-
}
|
|
28
|
-
export declare function createWorkbenchCommandController(options: WorkbenchCommandControllerOptions): WorkbenchCommandController;
|
|
29
|
-
export declare function normalizeOptionalSetting(value: string, clearValues: string[]): string | undefined;
|
|
@@ -1,426 +0,0 @@
|
|
|
1
|
-
import { UnknownPresetError } from "./settings-controller.js";
|
|
2
|
-
export function createWorkbenchCommandController(options) {
|
|
3
|
-
const dispatch = options.engine.dispatch;
|
|
4
|
-
return {
|
|
5
|
-
async run(command) {
|
|
6
|
-
const commandResult = options.engine.handleCommand(command);
|
|
7
|
-
if (commandResult.handled) {
|
|
8
|
-
await runEffects(commandResult.effects);
|
|
9
|
-
return;
|
|
10
|
-
}
|
|
11
|
-
switch (command.kind) {
|
|
12
|
-
case "abort":
|
|
13
|
-
if (!options.engine.snapshot().busy) {
|
|
14
|
-
dispatch({ type: "message.add", role: "system", text: "No agent turn is running." });
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
await options.turnController.abort("Abort requested.");
|
|
18
|
-
return;
|
|
19
|
-
case "config":
|
|
20
|
-
await runConfigCommand(command);
|
|
21
|
-
return;
|
|
22
|
-
case "preset":
|
|
23
|
-
await runPresetCommand(command.value);
|
|
24
|
-
return;
|
|
25
|
-
case "summary":
|
|
26
|
-
await showSummary();
|
|
27
|
-
return;
|
|
28
|
-
case "search":
|
|
29
|
-
await searchWorkdir(command.query);
|
|
30
|
-
return;
|
|
31
|
-
case "new_conversation":
|
|
32
|
-
await startNewConversation(command.name);
|
|
33
|
-
return;
|
|
34
|
-
case "switch_conversation":
|
|
35
|
-
switchConversation(command.name);
|
|
36
|
-
return;
|
|
37
|
-
case "list_conversations":
|
|
38
|
-
await showConversations();
|
|
39
|
-
return;
|
|
40
|
-
case "preview":
|
|
41
|
-
showEditPreview();
|
|
42
|
-
return;
|
|
43
|
-
case "apply":
|
|
44
|
-
await applyPendingEdit(false);
|
|
45
|
-
return;
|
|
46
|
-
case "apply_all":
|
|
47
|
-
await applyPendingEdit(true);
|
|
48
|
-
return;
|
|
49
|
-
case "reject":
|
|
50
|
-
rejectPendingEdit();
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
runEffects,
|
|
55
|
-
};
|
|
56
|
-
async function runEffects(effects) {
|
|
57
|
-
for (const effect of effects) {
|
|
58
|
-
switch (effect.type) {
|
|
59
|
-
case "exit":
|
|
60
|
-
options.onExit();
|
|
61
|
-
break;
|
|
62
|
-
case "login":
|
|
63
|
-
options.onLogin();
|
|
64
|
-
break;
|
|
65
|
-
case "logout":
|
|
66
|
-
dispatch({ type: "activity.add", text: `Logged out: ${options.profileName}` });
|
|
67
|
-
options.onLogout();
|
|
68
|
-
break;
|
|
69
|
-
case "delete_profile":
|
|
70
|
-
dispatch({ type: "activity.add", level: "warning", text: `Deleting profile: ${options.profileName}` });
|
|
71
|
-
await options.onDeleteProfile();
|
|
72
|
-
break;
|
|
73
|
-
case "switch_profile":
|
|
74
|
-
options.onSwitchProfile(effect.name);
|
|
75
|
-
break;
|
|
76
|
-
case "show_auth_status":
|
|
77
|
-
await showAuthStatus();
|
|
78
|
-
break;
|
|
79
|
-
case "export_transcript":
|
|
80
|
-
await exportTranscript(effect);
|
|
81
|
-
break;
|
|
82
|
-
case "clear_preset_tool_catalog_cache":
|
|
83
|
-
options.settingsController.clearPresetToolCatalogCache();
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
async function runConfigCommand(command) {
|
|
89
|
-
const state = options.engine.snapshot();
|
|
90
|
-
if (!command.field) {
|
|
91
|
-
dispatch({
|
|
92
|
-
type: "message.add",
|
|
93
|
-
role: "system",
|
|
94
|
-
text: options.settingsController.configText({
|
|
95
|
-
profileName: options.profileName,
|
|
96
|
-
runPreset: state.runPreset,
|
|
97
|
-
runModel: state.runModel,
|
|
98
|
-
accessMode: state.accessMode,
|
|
99
|
-
contextEnabled: state.contextEnabled,
|
|
100
|
-
defaultPreset: state.defaultPreset,
|
|
101
|
-
renderMode: state.renderMode,
|
|
102
|
-
shellIsolation: state.shellIsolation,
|
|
103
|
-
}),
|
|
104
|
-
});
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
if (command.field === "preset") {
|
|
108
|
-
if (!command.value) {
|
|
109
|
-
dispatch({
|
|
110
|
-
type: "message.add",
|
|
111
|
-
role: "system",
|
|
112
|
-
text: options.settingsController.defaultPresetHelp(state.defaultPreset),
|
|
113
|
-
});
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
try {
|
|
117
|
-
const settings = await options.settingsController.saveDefaultPreset({
|
|
118
|
-
value: command.value,
|
|
119
|
-
profileName: options.profileName,
|
|
120
|
-
options: options.options,
|
|
121
|
-
});
|
|
122
|
-
dispatch({ type: "settings.set", settings: { defaultPreset: settings.defaultPreset, runPreset: settings.runPreset } });
|
|
123
|
-
dispatch({
|
|
124
|
-
type: "message.add",
|
|
125
|
-
role: "system",
|
|
126
|
-
text: settings.message,
|
|
127
|
-
});
|
|
128
|
-
dispatch({ type: "activity.add", level: "success", text: settings.activity });
|
|
129
|
-
}
|
|
130
|
-
catch (error) {
|
|
131
|
-
if (error instanceof UnknownPresetError) {
|
|
132
|
-
dispatch({
|
|
133
|
-
type: "message.add",
|
|
134
|
-
role: "system",
|
|
135
|
-
text: await presetListText(`Unknown preset: ${error.preset}`),
|
|
136
|
-
});
|
|
137
|
-
dispatch({ type: "activity.add", level: "warning", text: `Unknown preset: ${error.preset}` });
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
dispatch({ type: "message.add", role: "system", text: `Could not save default preset: ${userFacingError(error)}` });
|
|
141
|
-
dispatch({ type: "activity.add", level: "error", text: "Default preset save failed" });
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
if (command.field === "isolation") {
|
|
145
|
-
if (!command.value) {
|
|
146
|
-
dispatch({
|
|
147
|
-
type: "message.add",
|
|
148
|
-
role: "system",
|
|
149
|
-
text: options.settingsController.shellIsolationHelp(state.shellIsolation),
|
|
150
|
-
});
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
try {
|
|
154
|
-
const settings = await options.settingsController.saveShellIsolationMode(command.value);
|
|
155
|
-
dispatch({ type: "settings.set", settings: { shellIsolation: settings.shellIsolation } });
|
|
156
|
-
dispatch({ type: "message.add", role: "system", text: settings.message });
|
|
157
|
-
dispatch({ type: "activity.add", level: "success", text: settings.activity });
|
|
158
|
-
}
|
|
159
|
-
catch (error) {
|
|
160
|
-
dispatch({ type: "message.add", role: "system", text: `Could not save shell isolation mode: ${userFacingError(error)}` });
|
|
161
|
-
dispatch({ type: "activity.add", level: "error", text: "Shell isolation save failed" });
|
|
162
|
-
}
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
if (command.field === "isolator") {
|
|
166
|
-
if (!command.value) {
|
|
167
|
-
dispatch({
|
|
168
|
-
type: "message.add",
|
|
169
|
-
role: "system",
|
|
170
|
-
text: options.settingsController.isolatorPathHelp(state.shellIsolation),
|
|
171
|
-
});
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
try {
|
|
175
|
-
const [subcommand = "", ...rest] = command.value.split(/\s+/);
|
|
176
|
-
const value = rest.join(" ").trim();
|
|
177
|
-
const settings = subcommand === "source"
|
|
178
|
-
? await options.settingsController.saveIsolatorSource(value)
|
|
179
|
-
: subcommand === "path"
|
|
180
|
-
? await options.settingsController.saveIsolatorPath(value)
|
|
181
|
-
: await options.settingsController.saveIsolatorPath(command.value);
|
|
182
|
-
dispatch({ type: "settings.set", settings: { shellIsolation: settings.shellIsolation } });
|
|
183
|
-
dispatch({ type: "message.add", role: "system", text: settings.message });
|
|
184
|
-
dispatch({ type: "activity.add", level: "success", text: settings.activity });
|
|
185
|
-
}
|
|
186
|
-
catch (error) {
|
|
187
|
-
dispatch({ type: "message.add", role: "system", text: `Could not save isolator path: ${userFacingError(error)}` });
|
|
188
|
-
dispatch({ type: "activity.add", level: "error", text: "Isolator path save failed" });
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
async function runPresetCommand(value) {
|
|
193
|
-
const state = options.engine.snapshot();
|
|
194
|
-
if (!value) {
|
|
195
|
-
dispatch({
|
|
196
|
-
type: "message.add",
|
|
197
|
-
role: "system",
|
|
198
|
-
text: await presetListText(`Preset: ${state.runPreset || "none"}. Use /preset <name> or /preset none.`),
|
|
199
|
-
});
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
const normalized = normalizeOptionalSetting(value, ["none", "off", "clear"]);
|
|
203
|
-
if (normalized && !(await validatePresetName(normalized))) {
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
dispatch({ type: "settings.set", settings: { runPreset: normalized } });
|
|
207
|
-
dispatch({ type: "activity.add", text: `Preset: ${normalized || "none"}` });
|
|
208
|
-
}
|
|
209
|
-
async function validatePresetName(preset) {
|
|
210
|
-
try {
|
|
211
|
-
if (await options.settingsController.validatePreset(options.profileName, preset))
|
|
212
|
-
return true;
|
|
213
|
-
dispatch({
|
|
214
|
-
type: "message.add",
|
|
215
|
-
role: "system",
|
|
216
|
-
text: await presetListText(`Unknown preset: ${preset}`),
|
|
217
|
-
});
|
|
218
|
-
dispatch({ type: "activity.add", level: "warning", text: `Unknown preset: ${preset}` });
|
|
219
|
-
return false;
|
|
220
|
-
}
|
|
221
|
-
catch (error) {
|
|
222
|
-
dispatch({ type: "message.add", role: "system", text: `Could not validate preset "${preset}": ${userFacingError(error)}` });
|
|
223
|
-
dispatch({ type: "activity.add", level: "error", text: "Preset catalog unavailable" });
|
|
224
|
-
return false;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
async function presetListText(prefix) {
|
|
228
|
-
return options.settingsController.presetListText({
|
|
229
|
-
profileName: options.profileName,
|
|
230
|
-
currentPreset: options.engine.snapshot().runPreset,
|
|
231
|
-
prefix,
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
async function showAuthStatus() {
|
|
235
|
-
dispatch({ type: "activity.add", text: "Checking auth status" });
|
|
236
|
-
try {
|
|
237
|
-
dispatch({ type: "message.add", role: "system", text: await options.authController.statusText(options.profileName) });
|
|
238
|
-
dispatch({ type: "activity.add", level: "success", text: "Auth status ready" });
|
|
239
|
-
}
|
|
240
|
-
catch (error) {
|
|
241
|
-
dispatch({ type: "message.add", role: "system", text: userFacingError(error) });
|
|
242
|
-
dispatch({ type: "activity.add", level: "error", text: "Auth status failed" });
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
async function exportTranscript(effect) {
|
|
246
|
-
try {
|
|
247
|
-
const file = await options.conversationController.exportTranscript(effect);
|
|
248
|
-
dispatch({ type: "message.add", role: "system", text: `Transcript exported:\n${file}` });
|
|
249
|
-
dispatch({ type: "activity.add", level: "success", text: "Transcript exported" });
|
|
250
|
-
}
|
|
251
|
-
catch (error) {
|
|
252
|
-
dispatch({ type: "message.add", role: "system", text: `Transcript export failed: ${userFacingError(error)}` });
|
|
253
|
-
dispatch({ type: "activity.add", level: "error", text: "Transcript export failed" });
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
async function showSummary() {
|
|
257
|
-
if (!options.localController.isLoaded()) {
|
|
258
|
-
dispatch({ type: "message.add", role: "system", text: unavailableWorkdirText() });
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
dispatch({ type: "activity.add", text: "Summarizing workdir" });
|
|
262
|
-
try {
|
|
263
|
-
dispatch({
|
|
264
|
-
type: "message.add",
|
|
265
|
-
role: "system",
|
|
266
|
-
text: await options.localController.summaryText(),
|
|
267
|
-
});
|
|
268
|
-
dispatch({ type: "activity.add", level: "success", text: "Workdir summary ready" });
|
|
269
|
-
}
|
|
270
|
-
catch (error) {
|
|
271
|
-
dispatch({
|
|
272
|
-
type: "activity.add",
|
|
273
|
-
level: "error",
|
|
274
|
-
text: userFacingError(error),
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
async function startNewConversation(name) {
|
|
279
|
-
const conversation = await options.conversationController.startNewConversation(name, options.options.profile);
|
|
280
|
-
dispatch({ type: "messages.clear" });
|
|
281
|
-
dispatch({ type: "conversation.set", name: conversation.name });
|
|
282
|
-
dispatch({
|
|
283
|
-
type: "message.add",
|
|
284
|
-
role: "system",
|
|
285
|
-
text: conversation.message,
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
function switchConversation(name) {
|
|
289
|
-
const conversation = options.conversationController.switchConversation(name);
|
|
290
|
-
dispatch({ type: "messages.clear" });
|
|
291
|
-
dispatch({ type: "conversation.set", name: conversation.name });
|
|
292
|
-
dispatch({
|
|
293
|
-
type: "message.add",
|
|
294
|
-
role: "system",
|
|
295
|
-
text: conversation.message,
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
async function showConversations() {
|
|
299
|
-
try {
|
|
300
|
-
dispatch({
|
|
301
|
-
type: "message.add",
|
|
302
|
-
role: "system",
|
|
303
|
-
text: await options.conversationController.listConversations(options.options.profile),
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
catch (error) {
|
|
307
|
-
dispatch({ type: "message.add", role: "system", text: userFacingError(error) });
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
async function searchWorkdir(query) {
|
|
311
|
-
if (!query) {
|
|
312
|
-
dispatch({ type: "message.add", role: "system", text: "Usage: /search <query>" });
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
if (!options.localController.isLoaded()) {
|
|
316
|
-
dispatch({ type: "message.add", role: "system", text: unavailableWorkdirText() });
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
dispatch({ type: "activity.add", text: `Searching workdir: ${query}` });
|
|
320
|
-
try {
|
|
321
|
-
const result = await options.localController.searchText(query);
|
|
322
|
-
dispatch({
|
|
323
|
-
type: "message.add",
|
|
324
|
-
role: "system",
|
|
325
|
-
text: result.text,
|
|
326
|
-
});
|
|
327
|
-
dispatch({
|
|
328
|
-
type: "activity.add",
|
|
329
|
-
level: "success",
|
|
330
|
-
text: `Search complete: ${result.count} matches`,
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
catch (error) {
|
|
334
|
-
dispatch({
|
|
335
|
-
type: "activity.add",
|
|
336
|
-
level: "error",
|
|
337
|
-
text: userFacingError(error),
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
function showEditPreview() {
|
|
342
|
-
const state = options.engine.snapshot();
|
|
343
|
-
if (state.pendingLocalTool) {
|
|
344
|
-
dispatch({ type: "message.add", role: "system", text: options.localController.approvalPreview(state.pendingLocalTool) });
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
dispatch({ type: "message.add", role: "system", text: "No pending local action." });
|
|
348
|
-
}
|
|
349
|
-
async function applyPendingEdit(allowFutureLocalActions) {
|
|
350
|
-
const state = options.engine.snapshot();
|
|
351
|
-
if (!options.localController.isLoaded()) {
|
|
352
|
-
dispatch({ type: "message.add", role: "system", text: unavailableWorkdirText() });
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
if (state.pendingLocalTool) {
|
|
356
|
-
dispatch({
|
|
357
|
-
type: "activity.add",
|
|
358
|
-
level: "warning",
|
|
359
|
-
text: `Applying local action: ${state.pendingLocalTool.name}${state.pendingLocalTool.action ? `.${state.pendingLocalTool.action}` : ""}`,
|
|
360
|
-
});
|
|
361
|
-
try {
|
|
362
|
-
const result = await options.localController.applyApproval(state.pendingLocalTool);
|
|
363
|
-
const nextAccessMode = allowFutureLocalActions ? "full" : state.accessMode;
|
|
364
|
-
if (allowFutureLocalActions) {
|
|
365
|
-
dispatch({ type: "access.set", mode: "full" });
|
|
366
|
-
}
|
|
367
|
-
dispatch({
|
|
368
|
-
type: "message.add",
|
|
369
|
-
role: "system",
|
|
370
|
-
text: [
|
|
371
|
-
allowFutureLocalActions
|
|
372
|
-
? "Applied local action. Future local actions in this workbench conversation are now allowed."
|
|
373
|
-
: "Applied local action once. Future local actions still require approval.",
|
|
374
|
-
"Continuing agent turn with the local result.",
|
|
375
|
-
"Result:",
|
|
376
|
-
JSON.stringify(result, null, 2),
|
|
377
|
-
].join("\n"),
|
|
378
|
-
});
|
|
379
|
-
dispatch({ type: "activity.add", level: "success", text: "Local action applied" });
|
|
380
|
-
const approval = state.pendingLocalTool;
|
|
381
|
-
dispatch({ type: "local_tool.pending.clear" });
|
|
382
|
-
await options.turnController.continueAfterLocalApproval({
|
|
383
|
-
approval,
|
|
384
|
-
result,
|
|
385
|
-
accessMode: nextAccessMode,
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
catch (error) {
|
|
389
|
-
dispatch({ type: "message.add", role: "system", text: userFacingError(error) });
|
|
390
|
-
dispatch({ type: "activity.add", level: "error", text: userFacingError(error) });
|
|
391
|
-
}
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
dispatch({ type: "message.add", role: "system", text: "No pending local action." });
|
|
395
|
-
}
|
|
396
|
-
function rejectPendingEdit() {
|
|
397
|
-
const state = options.engine.snapshot();
|
|
398
|
-
if (state.pendingLocalTool) {
|
|
399
|
-
dispatch({
|
|
400
|
-
type: "activity.add",
|
|
401
|
-
text: `Rejected local action: ${state.pendingLocalTool.name}${state.pendingLocalTool.action ? `.${state.pendingLocalTool.action}` : ""}`,
|
|
402
|
-
});
|
|
403
|
-
dispatch({ type: "local_tool.pending.clear" });
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
dispatch({ type: "message.add", role: "system", text: "No pending local action." });
|
|
407
|
-
}
|
|
408
|
-
function unavailableWorkdirText() {
|
|
409
|
-
const state = options.engine.snapshot();
|
|
410
|
-
if (!state.contextEnabled || state.accessMode === "off") {
|
|
411
|
-
return "Local workdir tools are off. Use /workdir on, /access approval, or /access full to load and expose the current workdir.";
|
|
412
|
-
}
|
|
413
|
-
return "Workdir is still loading.";
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
export function normalizeOptionalSetting(value, clearValues) {
|
|
417
|
-
const trimmed = value.trim();
|
|
418
|
-
if (!trimmed)
|
|
419
|
-
return undefined;
|
|
420
|
-
return clearValues.includes(trimmed.toLowerCase()) ? undefined : trimmed;
|
|
421
|
-
}
|
|
422
|
-
function userFacingError(error) {
|
|
423
|
-
if (error instanceof Error)
|
|
424
|
-
return error.message;
|
|
425
|
-
return String(error);
|
|
426
|
-
}
|