@agent-api/cli 0.2.0 → 0.3.0
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 -126
- 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 -104
- 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 -79
- package/dist/workbench/settings-controller.js +0 -288
- 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,288 +0,0 @@
|
|
|
1
|
-
import { clearPresetToolCatalogCache, isAvailablePreset, listAvailablePresets, } from "../agent.js";
|
|
2
|
-
import { loadWorkbenchPreferences, updateWorkbenchPreferences, } from "../config.js";
|
|
3
|
-
import { ensureConfiguredIsolator, installConfiguredIsolator, normalizeSourceURL, relocateInstalledIsolator, validateInstalledIsolator, } from "./isolator-installer.js";
|
|
4
|
-
export function createWorkbenchSettingsController(options = {}) {
|
|
5
|
-
const loadWorkbenchPreferencesImpl = options.loadWorkbenchPreferencesImpl ?? loadWorkbenchPreferences;
|
|
6
|
-
const updateWorkbenchPreferencesImpl = options.updateWorkbenchPreferencesImpl ?? updateWorkbenchPreferences;
|
|
7
|
-
const isAvailablePresetImpl = options.isAvailablePresetImpl ?? isAvailablePreset;
|
|
8
|
-
const listAvailablePresetsImpl = options.listAvailablePresetsImpl ?? listAvailablePresets;
|
|
9
|
-
const clearPresetToolCatalogCacheImpl = options.clearPresetToolCatalogCacheImpl ?? clearPresetToolCatalogCache;
|
|
10
|
-
const isolatorInstallOptions = options.isolatorInstallOptions ?? {};
|
|
11
|
-
const formatError = options.formatError ?? userFacingError;
|
|
12
|
-
return {
|
|
13
|
-
async loadInitial(agentOptions) {
|
|
14
|
-
const loadedPreferences = await loadWorkbenchPreferencesImpl();
|
|
15
|
-
const { preferences, activity, warning } = await reconcileConfiguredIsolator(loadedPreferences, updateWorkbenchPreferencesImpl, isolatorInstallOptions, formatError);
|
|
16
|
-
return {
|
|
17
|
-
defaultPreset: preferences.defaultPreset,
|
|
18
|
-
...(preferences.isolation ? { shellIsolation: preferences.isolation } : {}),
|
|
19
|
-
...(activity ? { activity } : {}),
|
|
20
|
-
...(warning ? { warning } : {}),
|
|
21
|
-
runPreset: shouldApplyDefaultPreset(agentOptions)
|
|
22
|
-
? effectiveDefaultPreset(preferences, agentOptions.preset)
|
|
23
|
-
: undefined,
|
|
24
|
-
};
|
|
25
|
-
},
|
|
26
|
-
async saveShellIsolationMode(value) {
|
|
27
|
-
const mode = normalizeShellIsolationMode(value);
|
|
28
|
-
const preferences = await updateWorkbenchPreferencesImpl({ isolation: { mode } });
|
|
29
|
-
return {
|
|
30
|
-
...settingsSnapshot(preferences),
|
|
31
|
-
message: `Saved shell isolation mode: ${formatShellIsolation(preferences.isolation)}.`,
|
|
32
|
-
activity: `Shell isolation mode saved: ${preferences.isolation?.mode ?? "auto"}`,
|
|
33
|
-
};
|
|
34
|
-
},
|
|
35
|
-
async saveIsolatorPath(value) {
|
|
36
|
-
const executablePath = normalizeIsolatorPath(value);
|
|
37
|
-
if (!executablePath) {
|
|
38
|
-
const preferences = await updateWorkbenchPreferencesImpl({ isolation: { executablePath } });
|
|
39
|
-
return {
|
|
40
|
-
...settingsSnapshot(preferences),
|
|
41
|
-
message: `Saved isolator path: ${formatIsolatorPath(preferences.isolation)}.`,
|
|
42
|
-
activity: "Isolator path cleared",
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
const current = (await loadWorkbenchPreferencesImpl()).isolation;
|
|
46
|
-
if (current?.sourceURL) {
|
|
47
|
-
const result = await installConfiguredIsolator({
|
|
48
|
-
sourceURL: current.sourceURL,
|
|
49
|
-
executablePath,
|
|
50
|
-
sha256: current.sha256,
|
|
51
|
-
}, isolatorInstallOptions);
|
|
52
|
-
const preferences = await updateWorkbenchPreferencesImpl({
|
|
53
|
-
isolation: { executablePath: result.executablePath, sourceURL: result.sourceURL, sha256: result.sha256, installSkipped: false },
|
|
54
|
-
});
|
|
55
|
-
return {
|
|
56
|
-
...settingsSnapshot(preferences),
|
|
57
|
-
message: `Installed isolator to ${result.executablePath}.`,
|
|
58
|
-
activity: result.replaced ? "Isolator refreshed" : "Isolator installed",
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
const validatedPath = current?.executablePath
|
|
62
|
-
? await relocateInstalledIsolator(current.executablePath, executablePath, isolatorInstallOptions)
|
|
63
|
-
: await validateInstalledIsolator(executablePath, isolatorInstallOptions);
|
|
64
|
-
const preferences = await updateWorkbenchPreferencesImpl({ isolation: { executablePath: validatedPath, installSkipped: false } });
|
|
65
|
-
return {
|
|
66
|
-
...settingsSnapshot(preferences),
|
|
67
|
-
message: `Saved verified isolator path: ${formatIsolatorPath(preferences.isolation)}.`,
|
|
68
|
-
activity: "Isolator path verified",
|
|
69
|
-
};
|
|
70
|
-
},
|
|
71
|
-
async saveIsolatorSource(value) {
|
|
72
|
-
const sourceURL = normalizeIsolatorSource(value);
|
|
73
|
-
const current = (await loadWorkbenchPreferencesImpl()).isolation;
|
|
74
|
-
if (!sourceURL) {
|
|
75
|
-
const preferences = await updateWorkbenchPreferencesImpl({ isolation: { sourceURL, sha256: null } });
|
|
76
|
-
return {
|
|
77
|
-
...settingsSnapshot(preferences),
|
|
78
|
-
message: "Cleared isolator source URL.",
|
|
79
|
-
activity: "Isolator source cleared",
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
if (!current?.executablePath) {
|
|
83
|
-
throw new Error("Set a verified isolator path before saving a source URL.");
|
|
84
|
-
}
|
|
85
|
-
const result = await installConfiguredIsolator({
|
|
86
|
-
sourceURL,
|
|
87
|
-
executablePath: current.executablePath,
|
|
88
|
-
sha256: current.sha256,
|
|
89
|
-
}, isolatorInstallOptions);
|
|
90
|
-
const preferences = await updateWorkbenchPreferencesImpl({
|
|
91
|
-
isolation: { sourceURL: result.sourceURL, executablePath: result.executablePath, sha256: result.sha256, installSkipped: false },
|
|
92
|
-
});
|
|
93
|
-
return {
|
|
94
|
-
...settingsSnapshot(preferences),
|
|
95
|
-
message: `Installed isolator from ${result.sourceURL}.`,
|
|
96
|
-
activity: result.replaced ? "Isolator refreshed from source" : "Isolator installed from source",
|
|
97
|
-
};
|
|
98
|
-
},
|
|
99
|
-
async saveDefaultPreset(input) {
|
|
100
|
-
const normalized = normalizeDefaultPreset(input.value);
|
|
101
|
-
if (typeof normalized === "string" && !(await isAvailablePresetImpl(input.profileName, normalized))) {
|
|
102
|
-
throw new UnknownPresetError(normalized);
|
|
103
|
-
}
|
|
104
|
-
const preferences = await updateWorkbenchPreferencesImpl({ defaultPreset: normalized });
|
|
105
|
-
return {
|
|
106
|
-
defaultPreset: preferences.defaultPreset,
|
|
107
|
-
...(preferences.isolation ? { shellIsolation: preferences.isolation } : {}),
|
|
108
|
-
runPreset: shouldApplyDefaultPreset(input.options)
|
|
109
|
-
? effectiveDefaultPreset(preferences, input.options.preset)
|
|
110
|
-
: undefined,
|
|
111
|
-
message: `Saved default preset: ${formatDefaultPreset(preferences.defaultPreset)}.`,
|
|
112
|
-
activity: `Default preset saved: ${formatDefaultPreset(preferences.defaultPreset)}`,
|
|
113
|
-
};
|
|
114
|
-
},
|
|
115
|
-
async validatePreset(profileName, preset) {
|
|
116
|
-
return isAvailablePresetImpl(profileName, preset);
|
|
117
|
-
},
|
|
118
|
-
async presetListText(input) {
|
|
119
|
-
try {
|
|
120
|
-
const presets = await listAvailablePresetsImpl(input.profileName);
|
|
121
|
-
return [
|
|
122
|
-
input.prefix,
|
|
123
|
-
"",
|
|
124
|
-
"Available presets:",
|
|
125
|
-
...formatPresetList(presets, input.currentPreset),
|
|
126
|
-
].join("\n");
|
|
127
|
-
}
|
|
128
|
-
catch (error) {
|
|
129
|
-
return [
|
|
130
|
-
input.prefix,
|
|
131
|
-
"",
|
|
132
|
-
`Available presets could not be loaded: ${formatError(error)}`,
|
|
133
|
-
].join("\n");
|
|
134
|
-
}
|
|
135
|
-
},
|
|
136
|
-
configText: runConfigText,
|
|
137
|
-
defaultPresetHelp(defaultPreset) {
|
|
138
|
-
return `Default preset: ${formatDefaultPreset(defaultPreset)}. Use /config preset <name>, /config preset none, or /config preset reset.`;
|
|
139
|
-
},
|
|
140
|
-
shellIsolationHelp(shellIsolation) {
|
|
141
|
-
return `Shell isolation: ${formatShellIsolation(shellIsolation)}. Use /config isolation none, /config isolation auto, or /config isolation required.`;
|
|
142
|
-
},
|
|
143
|
-
isolatorPathHelp(shellIsolation) {
|
|
144
|
-
return [
|
|
145
|
-
`Isolator path: ${formatIsolatorPath(shellIsolation)}`,
|
|
146
|
-
`Isolator source: ${formatIsolatorSource(shellIsolation)}`,
|
|
147
|
-
"Use /config isolator path <absolute-path>, /config isolator source <https-url>, or /config isolator none to clear the path.",
|
|
148
|
-
].join("\n");
|
|
149
|
-
},
|
|
150
|
-
clearPresetToolCatalogCache(baseURL) {
|
|
151
|
-
clearPresetToolCatalogCacheImpl(baseURL);
|
|
152
|
-
},
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
export class UnknownPresetError extends Error {
|
|
156
|
-
preset;
|
|
157
|
-
constructor(preset) {
|
|
158
|
-
super(`Unknown preset: ${preset}`);
|
|
159
|
-
this.preset = preset;
|
|
160
|
-
this.name = "UnknownPresetError";
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
export function normalizeDefaultPreset(value) {
|
|
164
|
-
const trimmed = value.trim();
|
|
165
|
-
const lowered = trimmed.toLowerCase();
|
|
166
|
-
if (!trimmed || lowered === "reset" || lowered === "default" || lowered === "builtin")
|
|
167
|
-
return undefined;
|
|
168
|
-
if (["none", "off", "disable", "disabled"].includes(lowered))
|
|
169
|
-
return null;
|
|
170
|
-
return trimmed;
|
|
171
|
-
}
|
|
172
|
-
export function formatDefaultPreset(value) {
|
|
173
|
-
if (value === undefined)
|
|
174
|
-
return "built-in (pro-search)";
|
|
175
|
-
return value ?? "none";
|
|
176
|
-
}
|
|
177
|
-
function settingsSnapshot(preferences) {
|
|
178
|
-
return {
|
|
179
|
-
...("defaultPreset" in preferences ? { defaultPreset: preferences.defaultPreset } : {}),
|
|
180
|
-
...(preferences.isolation ? { shellIsolation: preferences.isolation } : {}),
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
export function normalizeShellIsolationMode(value) {
|
|
184
|
-
const lowered = value.trim().toLowerCase();
|
|
185
|
-
if (lowered === "none" || lowered === "off" || lowered === "disable" || lowered === "disabled")
|
|
186
|
-
return "none";
|
|
187
|
-
if (lowered === "auto" || lowered === "default")
|
|
188
|
-
return "auto";
|
|
189
|
-
if (lowered === "required" || lowered === "require" || lowered === "strict")
|
|
190
|
-
return "required";
|
|
191
|
-
throw new Error("Unknown shell isolation mode. Use none, auto, or required.");
|
|
192
|
-
}
|
|
193
|
-
export function normalizeIsolatorPath(value) {
|
|
194
|
-
const trimmed = value.trim();
|
|
195
|
-
if (!trimmed)
|
|
196
|
-
throw new Error("Usage: /config isolator <path>, or /config isolator none.");
|
|
197
|
-
const lowered = trimmed.toLowerCase();
|
|
198
|
-
if (["none", "off", "disable", "disabled", "clear", "reset"].includes(lowered))
|
|
199
|
-
return null;
|
|
200
|
-
return trimmed;
|
|
201
|
-
}
|
|
202
|
-
export function normalizeIsolatorSource(value) {
|
|
203
|
-
const trimmed = value.trim();
|
|
204
|
-
if (!trimmed)
|
|
205
|
-
throw new Error("Usage: /config isolator source <https-url>, or /config isolator source none.");
|
|
206
|
-
const lowered = trimmed.toLowerCase();
|
|
207
|
-
if (["none", "off", "disable", "disabled", "clear", "reset"].includes(lowered))
|
|
208
|
-
return null;
|
|
209
|
-
return normalizeSourceURL(trimmed);
|
|
210
|
-
}
|
|
211
|
-
export function formatShellIsolation(value) {
|
|
212
|
-
return value?.mode ?? "auto";
|
|
213
|
-
}
|
|
214
|
-
export function formatIsolatorPath(value) {
|
|
215
|
-
return value?.executablePath || process.env.AGENT_ISOLATOR_PATH || "not configured";
|
|
216
|
-
}
|
|
217
|
-
export function formatIsolatorSource(value) {
|
|
218
|
-
return value?.sourceURL || "not configured";
|
|
219
|
-
}
|
|
220
|
-
export function effectiveDefaultPreset(preferences, builtInPreset) {
|
|
221
|
-
if ("defaultPreset" in preferences)
|
|
222
|
-
return preferences.defaultPreset ?? undefined;
|
|
223
|
-
return builtInPreset;
|
|
224
|
-
}
|
|
225
|
-
export function formatPresetList(presets, currentPreset) {
|
|
226
|
-
if (presets.length === 0)
|
|
227
|
-
return ["- none returned by this endpoint"];
|
|
228
|
-
return presets.map((preset) => {
|
|
229
|
-
const description = preset.description ? ` - ${preset.description}` : "";
|
|
230
|
-
const current = currentPreset && preset.preset === currentPreset;
|
|
231
|
-
return `${current ? "*" : "-"} ${preset.preset}${current ? " (current)" : ""}${description}`;
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
function shouldApplyDefaultPreset(options) {
|
|
235
|
-
return !options.presetExplicit && !options.modelExplicit;
|
|
236
|
-
}
|
|
237
|
-
function runConfigText({ accessMode, contextEnabled, defaultPreset, profileName, runModel, runPreset, renderMode, shellIsolation, }) {
|
|
238
|
-
return [
|
|
239
|
-
`Profile: ${profileName}`,
|
|
240
|
-
`Preset: ${runPreset || "none"}`,
|
|
241
|
-
`Default preset: ${formatDefaultPreset(defaultPreset)}`,
|
|
242
|
-
`Model: ${runModel || "auto"}`,
|
|
243
|
-
`Render mode: ${renderMode}`,
|
|
244
|
-
`Shell isolation: ${formatShellIsolation(shellIsolation)}`,
|
|
245
|
-
`Isolator path: ${formatIsolatorPath(shellIsolation)}`,
|
|
246
|
-
`Isolator source: ${formatIsolatorSource(shellIsolation)}`,
|
|
247
|
-
`local_workdir tool: ${contextEnabled ? "on" : "off"}`,
|
|
248
|
-
`local_shell tool: ${contextEnabled ? "on" : "off"}`,
|
|
249
|
-
`Local access: ${accessMode}`,
|
|
250
|
-
].join("\n");
|
|
251
|
-
}
|
|
252
|
-
function userFacingError(error) {
|
|
253
|
-
if (error instanceof Error)
|
|
254
|
-
return error.message;
|
|
255
|
-
return String(error);
|
|
256
|
-
}
|
|
257
|
-
async function reconcileConfiguredIsolator(preferences, updatePreferences, installOptions, formatError) {
|
|
258
|
-
const isolation = preferences.isolation;
|
|
259
|
-
if (!isolation?.sourceURL || !isolation.executablePath)
|
|
260
|
-
return { preferences };
|
|
261
|
-
try {
|
|
262
|
-
const result = await ensureConfiguredIsolator({
|
|
263
|
-
sourceURL: isolation.sourceURL,
|
|
264
|
-
executablePath: isolation.executablePath,
|
|
265
|
-
sha256: isolation.sha256,
|
|
266
|
-
}, installOptions);
|
|
267
|
-
if (!result.repaired)
|
|
268
|
-
return { preferences };
|
|
269
|
-
const updated = await updatePreferences({
|
|
270
|
-
isolation: {
|
|
271
|
-
sourceURL: result.sourceURL,
|
|
272
|
-
executablePath: result.executablePath,
|
|
273
|
-
sha256: result.sha256,
|
|
274
|
-
installSkipped: false,
|
|
275
|
-
},
|
|
276
|
-
});
|
|
277
|
-
return {
|
|
278
|
-
preferences: updated,
|
|
279
|
-
activity: `Reinstalled isolator: ${result.executablePath}`,
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
catch (error) {
|
|
283
|
-
return {
|
|
284
|
-
preferences,
|
|
285
|
-
warning: `Configured isolator is unavailable: ${formatError(error)}`,
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export type ShellIsolationMode = "none" | "auto" | "required";
|
|
2
|
-
export interface ShellIsolationPreferences {
|
|
3
|
-
mode?: ShellIsolationMode;
|
|
4
|
-
executablePath?: string | null;
|
|
5
|
-
version?: string | null;
|
|
6
|
-
sourceURL?: string | null;
|
|
7
|
-
sha256?: string | null;
|
|
8
|
-
installSkipped?: boolean | null;
|
|
9
|
-
}
|
|
10
|
-
export declare function localShellIsolationOptions(preferences?: ShellIsolationPreferences): {
|
|
11
|
-
readonly isolator?: {
|
|
12
|
-
executablePath: string;
|
|
13
|
-
} | undefined;
|
|
14
|
-
readonly isolation: ShellIsolationMode;
|
|
15
|
-
readonly isolationOptions: {
|
|
16
|
-
readonly filesystem: "workdir-readwrite";
|
|
17
|
-
readonly network: "allowed";
|
|
18
|
-
readonly env: "inherit";
|
|
19
|
-
};
|
|
20
|
-
};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export function localShellIsolationOptions(preferences = {}) {
|
|
2
|
-
const executablePath = preferences.executablePath?.trim() || process.env.AGENT_ISOLATOR_PATH?.trim();
|
|
3
|
-
const isolation = preferences.mode ?? "auto";
|
|
4
|
-
return {
|
|
5
|
-
isolation,
|
|
6
|
-
isolationOptions: {
|
|
7
|
-
filesystem: "workdir-readwrite",
|
|
8
|
-
network: "allowed",
|
|
9
|
-
env: "inherit",
|
|
10
|
-
},
|
|
11
|
-
...(executablePath ? { isolator: { executablePath } } : {}),
|
|
12
|
-
};
|
|
13
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { resolveRuntimeProfile } from "../profile.js";
|
|
2
|
-
import { resumeAgentAfterLocalApproval, runAgentTurn, type AgentRunOptions, type LocalToolApprovalRequest, type WorkdirAccessMode } from "../agent.js";
|
|
3
|
-
import type { WorkbenchEngine, WorkbenchRuntimeEffect } from "./engine.js";
|
|
4
|
-
import type { WorkbenchAction, WorkbenchState } from "../tui/workbench.js";
|
|
5
|
-
export interface WorkbenchTurnController {
|
|
6
|
-
startPrompt(prompt: string): Promise<void>;
|
|
7
|
-
continueAfterLocalApproval(input: {
|
|
8
|
-
approval: LocalToolApprovalRequest;
|
|
9
|
-
result: string | Record<string, unknown>;
|
|
10
|
-
accessMode: WorkdirAccessMode;
|
|
11
|
-
}): Promise<void>;
|
|
12
|
-
abort(reason: string): Promise<void>;
|
|
13
|
-
}
|
|
14
|
-
export interface WorkbenchTurnControllerOptions {
|
|
15
|
-
engine: WorkbenchEngine;
|
|
16
|
-
baseOptions: AgentRunOptions;
|
|
17
|
-
dispatch(action: WorkbenchAction): void;
|
|
18
|
-
getState(): WorkbenchState;
|
|
19
|
-
runRuntimeEffects(effects: WorkbenchRuntimeEffect[], assistantId: string): void;
|
|
20
|
-
flushTextDeltaBuffer(): void;
|
|
21
|
-
runAgentTurnImpl?: typeof runAgentTurn;
|
|
22
|
-
resumeAgentAfterLocalApprovalImpl?: typeof resumeAgentAfterLocalApproval;
|
|
23
|
-
resolveRuntimeProfileImpl?: typeof resolveRuntimeProfile;
|
|
24
|
-
}
|
|
25
|
-
export declare function createWorkbenchTurnController(options: WorkbenchTurnControllerOptions): WorkbenchTurnController;
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import { resolveRuntimeProfile, } from "../profile.js";
|
|
2
|
-
import { resumeAgentAfterLocalApproval, runAgentTurn, } from "../agent.js";
|
|
3
|
-
export function createWorkbenchTurnController(options) {
|
|
4
|
-
const runAgentTurnImpl = options.runAgentTurnImpl ?? runAgentTurn;
|
|
5
|
-
const resumeAgentAfterLocalApprovalImpl = options.resumeAgentAfterLocalApprovalImpl ?? resumeAgentAfterLocalApproval;
|
|
6
|
-
const resolveRuntimeProfileImpl = options.resolveRuntimeProfileImpl ?? resolveRuntimeProfile;
|
|
7
|
-
let activeAbortController = null;
|
|
8
|
-
let activeResponseID = null;
|
|
9
|
-
const cancelledResponseIDs = new Set();
|
|
10
|
-
return {
|
|
11
|
-
async startPrompt(prompt) {
|
|
12
|
-
const state = options.getState();
|
|
13
|
-
const assistantId = `assistant-${Date.now()}`;
|
|
14
|
-
const abortController = new AbortController();
|
|
15
|
-
activeAbortController = abortController;
|
|
16
|
-
activeResponseID = null;
|
|
17
|
-
options.dispatch({ type: "busy.set", busy: true });
|
|
18
|
-
options.dispatch({ type: "message.add", role: "user", text: prompt });
|
|
19
|
-
options.dispatch({ type: "message.add", role: "assistant", text: "", id: assistantId });
|
|
20
|
-
options.dispatch({ type: "assistant.active", id: assistantId });
|
|
21
|
-
options.dispatch({ type: "activity.add", text: "Agent turn started" });
|
|
22
|
-
try {
|
|
23
|
-
const result = await runAgentTurnImpl({
|
|
24
|
-
...options.baseOptions,
|
|
25
|
-
preset: state.runPreset,
|
|
26
|
-
model: state.runModel,
|
|
27
|
-
promptParts: [prompt],
|
|
28
|
-
stream: true,
|
|
29
|
-
file: undefined,
|
|
30
|
-
stdin: false,
|
|
31
|
-
conversation: state.currentConversation,
|
|
32
|
-
includeLocalContext: state.contextEnabled,
|
|
33
|
-
accessMode: state.accessMode,
|
|
34
|
-
shellIsolation: state.shellIsolation,
|
|
35
|
-
restartConversation: false,
|
|
36
|
-
abortSignal: abortController.signal,
|
|
37
|
-
}, (event) => handleAgentEvent(event, assistantId));
|
|
38
|
-
options.dispatch({
|
|
39
|
-
type: "activity.add",
|
|
40
|
-
level: "success",
|
|
41
|
-
text: result.responseID ? `Agent turn completed: ${result.responseID}` : "Agent turn completed",
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
catch (error) {
|
|
45
|
-
handleTurnError(error);
|
|
46
|
-
}
|
|
47
|
-
finally {
|
|
48
|
-
finishTurn(abortController);
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
async continueAfterLocalApproval(input) {
|
|
52
|
-
const state = options.getState();
|
|
53
|
-
const assistantId = `assistant-${Date.now()}`;
|
|
54
|
-
const abortController = new AbortController();
|
|
55
|
-
activeAbortController = abortController;
|
|
56
|
-
activeResponseID = null;
|
|
57
|
-
options.dispatch({ type: "busy.set", busy: true });
|
|
58
|
-
options.dispatch({ type: "message.add", role: "assistant", text: "", id: assistantId });
|
|
59
|
-
options.dispatch({ type: "assistant.active", id: assistantId });
|
|
60
|
-
options.dispatch({ type: "activity.add", text: "Continuing agent turn" });
|
|
61
|
-
try {
|
|
62
|
-
const continuation = await resumeAgentAfterLocalApprovalImpl({
|
|
63
|
-
...options.baseOptions,
|
|
64
|
-
preset: state.runPreset,
|
|
65
|
-
model: state.runModel,
|
|
66
|
-
stream: true,
|
|
67
|
-
file: undefined,
|
|
68
|
-
stdin: false,
|
|
69
|
-
conversation: state.currentConversation,
|
|
70
|
-
includeLocalContext: state.contextEnabled,
|
|
71
|
-
accessMode: input.accessMode,
|
|
72
|
-
shellIsolation: state.shellIsolation,
|
|
73
|
-
restartConversation: false,
|
|
74
|
-
abortSignal: abortController.signal,
|
|
75
|
-
}, input.approval, input.result, (event) => handleAgentEvent(event, assistantId));
|
|
76
|
-
options.dispatch({
|
|
77
|
-
type: "activity.add",
|
|
78
|
-
level: "success",
|
|
79
|
-
text: continuation.responseID ? `Agent turn continued: ${continuation.responseID}` : "Agent turn continued",
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
catch (error) {
|
|
83
|
-
handleTurnError(error);
|
|
84
|
-
}
|
|
85
|
-
finally {
|
|
86
|
-
finishTurn(abortController);
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
async abort(reason) {
|
|
90
|
-
const state = options.getState();
|
|
91
|
-
if (!state.busy && !activeAbortController && !activeResponseID) {
|
|
92
|
-
options.dispatch({ type: "message.add", role: "system", text: "No agent turn is running." });
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
activeAbortController?.abort();
|
|
96
|
-
options.dispatch({ type: "activity.add", level: "warning", text: reason });
|
|
97
|
-
if (!activeResponseID) {
|
|
98
|
-
options.dispatch({ type: "message.add", role: "system", text: "Abort requested. No remote response ID is available yet." });
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
if (cancelledResponseIDs.has(activeResponseID))
|
|
102
|
-
return;
|
|
103
|
-
const responseID = activeResponseID;
|
|
104
|
-
cancelledResponseIDs.add(responseID);
|
|
105
|
-
try {
|
|
106
|
-
const runtimeProfile = await resolveRuntimeProfileImpl(options.baseOptions.profile);
|
|
107
|
-
const result = await runtimeProfile.client.responses.cancel(responseID);
|
|
108
|
-
options.dispatch({
|
|
109
|
-
type: "message.add",
|
|
110
|
-
role: "system",
|
|
111
|
-
text: result.interrupted
|
|
112
|
-
? `Abort requested for response ${responseID}.`
|
|
113
|
-
: `Abort requested locally. Remote response ${responseID} was not actively interrupted.`,
|
|
114
|
-
});
|
|
115
|
-
options.dispatch({
|
|
116
|
-
type: "activity.add",
|
|
117
|
-
level: result.interrupted ? "success" : "warning",
|
|
118
|
-
text: result.interrupted ? `Response cancel requested: ${responseID}` : `Response was not active: ${responseID}`,
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
catch (error) {
|
|
122
|
-
options.dispatch({ type: "message.add", role: "system", text: `Abort requested locally, but remote cancel failed: ${userFacingError(error)}` });
|
|
123
|
-
options.dispatch({ type: "activity.add", level: "error", text: "Remote response cancel failed" });
|
|
124
|
-
}
|
|
125
|
-
},
|
|
126
|
-
};
|
|
127
|
-
function handleAgentEvent(event, assistantId) {
|
|
128
|
-
const result = options.engine.handleAgentEvent(event);
|
|
129
|
-
for (const effect of result.effects) {
|
|
130
|
-
if (effect.type === "set_active_response_id") {
|
|
131
|
-
activeResponseID = effect.responseID;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
options.runRuntimeEffects(result.effects, assistantId);
|
|
135
|
-
}
|
|
136
|
-
function finishTurn(abortController) {
|
|
137
|
-
options.flushTextDeltaBuffer();
|
|
138
|
-
if (activeAbortController === abortController) {
|
|
139
|
-
activeAbortController = null;
|
|
140
|
-
}
|
|
141
|
-
activeResponseID = null;
|
|
142
|
-
options.dispatch({ type: "busy.set", busy: false });
|
|
143
|
-
options.dispatch({ type: "assistant.active", id: null });
|
|
144
|
-
}
|
|
145
|
-
function handleTurnError(error) {
|
|
146
|
-
const message = userFacingError(error);
|
|
147
|
-
const aborted = /aborted/i.test(message);
|
|
148
|
-
options.dispatch({
|
|
149
|
-
type: "message.add",
|
|
150
|
-
role: "system",
|
|
151
|
-
text: aborted ? "Agent turn aborted." : message,
|
|
152
|
-
});
|
|
153
|
-
options.dispatch({
|
|
154
|
-
type: "activity.add",
|
|
155
|
-
level: aborted ? "warning" : "error",
|
|
156
|
-
text: aborted ? "Agent turn aborted" : message,
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
function userFacingError(error) {
|
|
161
|
-
if (error instanceof Error)
|
|
162
|
-
return error.message;
|
|
163
|
-
return String(error);
|
|
164
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { RenderMode, WorkbenchMessage } from "../tui/workbench.js";
|
|
2
|
-
export type TranscriptLine = {
|
|
3
|
-
id: string;
|
|
4
|
-
text: string;
|
|
5
|
-
color?: string;
|
|
6
|
-
bold?: boolean;
|
|
7
|
-
inverse?: boolean;
|
|
8
|
-
};
|
|
9
|
-
export interface TranscriptViewModel {
|
|
10
|
-
lines: TranscriptLine[];
|
|
11
|
-
visibleLines: TranscriptLine[];
|
|
12
|
-
maxOffset: number;
|
|
13
|
-
offset: number;
|
|
14
|
-
viewportHeight: number;
|
|
15
|
-
}
|
|
16
|
-
export declare function buildTranscriptViewModel(input: {
|
|
17
|
-
activeAssistantMessageId: string | null;
|
|
18
|
-
busy: boolean;
|
|
19
|
-
messages: WorkbenchMessage[];
|
|
20
|
-
offset: number;
|
|
21
|
-
renderMode: RenderMode;
|
|
22
|
-
spinnerFrame: number;
|
|
23
|
-
viewportHeight: number;
|
|
24
|
-
width: number;
|
|
25
|
-
}): TranscriptViewModel;
|
|
26
|
-
export declare function buildTranscriptLines(messages: WorkbenchMessage[], options: {
|
|
27
|
-
activeAssistantMessageId: string | null;
|
|
28
|
-
busy: boolean;
|
|
29
|
-
renderMode: RenderMode;
|
|
30
|
-
spinnerFrame: number;
|
|
31
|
-
width: number;
|
|
32
|
-
}): TranscriptLine[];
|
|
33
|
-
export declare function spinnerGlyph(frame: number): string;
|
|
34
|
-
export declare function elapsedDots(frame: number): string;
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
export function buildTranscriptViewModel(input) {
|
|
2
|
-
const lines = buildTranscriptLines(input.messages, {
|
|
3
|
-
activeAssistantMessageId: input.activeAssistantMessageId,
|
|
4
|
-
busy: input.busy,
|
|
5
|
-
renderMode: input.renderMode,
|
|
6
|
-
spinnerFrame: input.spinnerFrame,
|
|
7
|
-
width: input.width,
|
|
8
|
-
});
|
|
9
|
-
const maxOffset = Math.max(0, lines.length - input.viewportHeight);
|
|
10
|
-
const offset = Math.min(input.offset, maxOffset);
|
|
11
|
-
const start = Math.max(0, lines.length - input.viewportHeight - offset);
|
|
12
|
-
return {
|
|
13
|
-
lines,
|
|
14
|
-
visibleLines: lines.slice(start, start + input.viewportHeight),
|
|
15
|
-
maxOffset,
|
|
16
|
-
offset,
|
|
17
|
-
viewportHeight: input.viewportHeight,
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
export function buildTranscriptLines(messages, options) {
|
|
21
|
-
const lines = [];
|
|
22
|
-
for (const message of messages) {
|
|
23
|
-
const waiting = message.role === "assistant" && options.busy && message.id === options.activeAssistantMessageId && !message.text;
|
|
24
|
-
lines.push({
|
|
25
|
-
id: `${message.id}:role`,
|
|
26
|
-
text: roleLabel(message.role),
|
|
27
|
-
color: roleColor(message.role),
|
|
28
|
-
});
|
|
29
|
-
const content = message.text || (waiting ? `${spinnerGlyph(options.spinnerFrame)} thinking ${elapsedDots(options.spinnerFrame)}` : "");
|
|
30
|
-
const rendered = options.renderMode === "raw"
|
|
31
|
-
? rawTranscriptLines(content, options.width)
|
|
32
|
-
: markdownTranscriptLines(content, options.width);
|
|
33
|
-
rendered.forEach((line, index) => {
|
|
34
|
-
lines.push({
|
|
35
|
-
...line,
|
|
36
|
-
id: `${message.id}:line:${index}`,
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
if (message.role !== "system") {
|
|
40
|
-
lines.push({ id: `${message.id}:space`, text: "" });
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return lines;
|
|
44
|
-
}
|
|
45
|
-
export function spinnerGlyph(frame) {
|
|
46
|
-
return ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"][frame % 10];
|
|
47
|
-
}
|
|
48
|
-
export function elapsedDots(frame) {
|
|
49
|
-
return ".".repeat((Math.floor(frame / 4) % 3) + 1);
|
|
50
|
-
}
|
|
51
|
-
function rawTranscriptLines(text, width) {
|
|
52
|
-
const source = text ? text.split(/\r?\n/) : [""];
|
|
53
|
-
return source.flatMap((line) => wrapTranscriptText(line, width).map((text) => ({ text })));
|
|
54
|
-
}
|
|
55
|
-
function markdownTranscriptLines(text, width) {
|
|
56
|
-
const source = text ? text.split(/\r?\n/) : [""];
|
|
57
|
-
const lines = [];
|
|
58
|
-
let inCode = false;
|
|
59
|
-
for (const sourceLine of source) {
|
|
60
|
-
if (/^\s*```/.test(sourceLine)) {
|
|
61
|
-
inCode = !inCode;
|
|
62
|
-
lines.push(...wrapTranscriptText(sourceLine, width).map((line) => ({ text: line, color: "gray" })));
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
lines.push(...markdownTranscriptLine(sourceLine, { code: inCode, width }));
|
|
66
|
-
}
|
|
67
|
-
return lines;
|
|
68
|
-
}
|
|
69
|
-
function markdownTranscriptLine(line, options) {
|
|
70
|
-
if (line === "")
|
|
71
|
-
return [{ text: "" }];
|
|
72
|
-
if (options.code)
|
|
73
|
-
return wrapTranscriptText(line, options.width).map((text) => ({ text, color: "gray" }));
|
|
74
|
-
const heading = /^(#{1,6})\s+(.+)$/.exec(line);
|
|
75
|
-
if (heading) {
|
|
76
|
-
const color = heading[1].length <= 2 ? "cyan" : "blue";
|
|
77
|
-
return wrapTranscriptText(heading[2], options.width).map((text) => ({ text, bold: true, color }));
|
|
78
|
-
}
|
|
79
|
-
if (/^\s*---+\s*$/.test(line))
|
|
80
|
-
return [{ text: "─".repeat(Math.min(48, options.width)), color: "gray" }];
|
|
81
|
-
const bullet = /^(\s*)[-*]\s+(.+)$/.exec(line);
|
|
82
|
-
if (bullet)
|
|
83
|
-
return wrapTranscriptText(`${bullet[1]}• ${bullet[2]}`, options.width).map((text) => ({ text }));
|
|
84
|
-
const numbered = /^(\s*)(\d+\.)\s+(.+)$/.exec(line);
|
|
85
|
-
if (numbered)
|
|
86
|
-
return wrapTranscriptText(`${numbered[1]}${numbered[2]} ${numbered[3]}`, options.width).map((text) => ({ text }));
|
|
87
|
-
const quote = /^\s*>\s?(.+)$/.exec(line);
|
|
88
|
-
if (quote)
|
|
89
|
-
return wrapTranscriptText(`│ ${quote[1]}`, options.width).map((text) => ({ text, color: "gray" }));
|
|
90
|
-
return wrapTranscriptText(line, options.width).map((text) => ({ text }));
|
|
91
|
-
}
|
|
92
|
-
function wrapTranscriptText(text, width) {
|
|
93
|
-
const max = Math.max(12, width);
|
|
94
|
-
if (text.length === 0)
|
|
95
|
-
return [""];
|
|
96
|
-
const lines = [];
|
|
97
|
-
let rest = text;
|
|
98
|
-
while (rest.length > max) {
|
|
99
|
-
const hard = rest.slice(0, max);
|
|
100
|
-
const softBreak = Math.max(hard.lastIndexOf(" "), hard.lastIndexOf("\t"));
|
|
101
|
-
const index = softBreak > Math.floor(max * 0.45) ? softBreak : max;
|
|
102
|
-
lines.push(rest.slice(0, index).trimEnd());
|
|
103
|
-
rest = rest.slice(index).trimStart();
|
|
104
|
-
}
|
|
105
|
-
lines.push(rest);
|
|
106
|
-
return lines;
|
|
107
|
-
}
|
|
108
|
-
function roleLabel(role) {
|
|
109
|
-
if (role === "user")
|
|
110
|
-
return "You";
|
|
111
|
-
if (role === "assistant")
|
|
112
|
-
return "Agent";
|
|
113
|
-
return "System";
|
|
114
|
-
}
|
|
115
|
-
function roleColor(role) {
|
|
116
|
-
if (role === "user")
|
|
117
|
-
return "green";
|
|
118
|
-
if (role === "assistant")
|
|
119
|
-
return "cyan";
|
|
120
|
-
return "gray";
|
|
121
|
-
}
|