@agent-api/cli 0.0.3 → 0.1.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/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/tui/chat.d.ts +1 -6
- package/dist/tui/chat.js +1 -1292
- package/dist/tui/ink/app.d.ts +5 -0
- package/dist/tui/ink/app.js +311 -0
- package/dist/tui/ink/components.d.ts +10 -0
- package/dist/tui/ink/components.js +20 -0
- package/dist/tui/workbench.d.ts +22 -0
- package/dist/tui/workbench.js +51 -0
- package/dist/workbench/auth-controller.d.ts +43 -0
- package/dist/workbench/auth-controller.js +84 -0
- package/dist/workbench/auth-gate-controller.d.ts +62 -0
- package/dist/workbench/auth-gate-controller.js +231 -0
- package/dist/workbench/command-controller.d.ts +29 -0
- package/dist/workbench/command-controller.js +378 -0
- package/dist/workbench/conversation-controller.d.ts +32 -0
- package/dist/workbench/conversation-controller.js +53 -0
- package/dist/workbench/engine.d.ts +66 -0
- package/dist/workbench/engine.js +291 -0
- package/dist/workbench/input-controller.d.ts +44 -0
- package/dist/workbench/input-controller.js +71 -0
- package/dist/workbench/lifecycle-controller.d.ts +30 -0
- package/dist/workbench/lifecycle-controller.js +75 -0
- package/dist/workbench/local-controller.d.ts +19 -0
- package/dist/workbench/local-controller.js +89 -0
- package/dist/workbench/render-model.d.ts +46 -0
- package/dist/workbench/render-model.js +61 -0
- package/dist/workbench/runtime-controller.d.ts +12 -0
- package/dist/workbench/runtime-controller.js +57 -0
- package/dist/workbench/session.d.ts +27 -0
- package/dist/workbench/session.js +40 -0
- package/dist/workbench/settings-controller.d.ts +52 -0
- package/dist/workbench/settings-controller.js +120 -0
- package/dist/workbench/turn-controller.d.ts +25 -0
- package/dist/workbench/turn-controller.js +162 -0
- package/dist/workbench/view-model.d.ts +34 -0
- package/dist/workbench/view-model.js +121 -0
- package/package.json +1 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { buildTranscriptViewModel, elapsedDots, spinnerGlyph, } from "./view-model.js";
|
|
2
|
+
export function buildWorkbenchRenderModel(input) {
|
|
3
|
+
const terminalRows = Math.max(18, input.viewport.rows || 32);
|
|
4
|
+
const terminalColumns = Math.max(80, input.viewport.columns || 100);
|
|
5
|
+
const viewportHeight = Math.max(6, terminalRows - 11);
|
|
6
|
+
const activityHeight = viewportHeight;
|
|
7
|
+
const transcriptWidth = Math.max(36, Math.floor(terminalColumns * 0.72) - 4);
|
|
8
|
+
const transcript = buildTranscriptViewModel({
|
|
9
|
+
activeAssistantMessageId: input.state.activeAssistantMessageId,
|
|
10
|
+
busy: input.state.busy,
|
|
11
|
+
messages: input.state.messages,
|
|
12
|
+
offset: input.transcriptOffset,
|
|
13
|
+
renderMode: input.state.renderMode,
|
|
14
|
+
spinnerFrame: input.spinnerFrame,
|
|
15
|
+
viewportHeight,
|
|
16
|
+
width: transcriptWidth,
|
|
17
|
+
});
|
|
18
|
+
return {
|
|
19
|
+
activityHeight,
|
|
20
|
+
footerText: [
|
|
21
|
+
"PgUp/PgDn scroll",
|
|
22
|
+
"End live",
|
|
23
|
+
"/export save",
|
|
24
|
+
"/transcript preview",
|
|
25
|
+
transcript.offset > 0 ? `${transcript.offset} rows from latest` : "live",
|
|
26
|
+
].join(" · "),
|
|
27
|
+
header: {
|
|
28
|
+
accessMode: input.state.accessMode,
|
|
29
|
+
contextEnabled: input.state.contextEnabled,
|
|
30
|
+
conversation: input.state.currentConversation,
|
|
31
|
+
model: input.state.runModel || "auto",
|
|
32
|
+
pendingLocalLabel: pendingLocalLabel(input.state),
|
|
33
|
+
preset: input.state.runPreset || "none",
|
|
34
|
+
profile: input.profileName,
|
|
35
|
+
renderMode: input.state.renderMode,
|
|
36
|
+
workdir: input.state.workdir?.root || input.workdirFallback,
|
|
37
|
+
},
|
|
38
|
+
input: {
|
|
39
|
+
busy: input.state.busy,
|
|
40
|
+
draft: input.draft,
|
|
41
|
+
fullAccess: input.state.accessMode === "full",
|
|
42
|
+
label: input.state.busy ? "working" : "you",
|
|
43
|
+
waitingText: `waiting for agent ${elapsedDots(input.spinnerFrame)}`,
|
|
44
|
+
},
|
|
45
|
+
terminalColumns,
|
|
46
|
+
terminalRows,
|
|
47
|
+
transcript,
|
|
48
|
+
transcriptWidth,
|
|
49
|
+
viewportHeight,
|
|
50
|
+
visibleActivities: input.state.activities.slice(-Math.max(1, activityHeight - 2)),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export function pendingLocalLabel(state) {
|
|
54
|
+
if (state.pendingLocalTool) {
|
|
55
|
+
return `${state.pendingLocalTool.name}${state.pendingLocalTool.action ? `.${state.pendingLocalTool.action}` : ""}`;
|
|
56
|
+
}
|
|
57
|
+
return "none";
|
|
58
|
+
}
|
|
59
|
+
export function busySpinner(frame) {
|
|
60
|
+
return spinnerGlyph(frame);
|
|
61
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { WorkbenchAction } from "../tui/workbench.js";
|
|
2
|
+
import type { WorkbenchRuntimeEffect } from "./engine.js";
|
|
3
|
+
export interface WorkbenchRuntimeController {
|
|
4
|
+
dispose(): void;
|
|
5
|
+
flushTextDeltaBuffer(): void;
|
|
6
|
+
runEffects(effects: WorkbenchRuntimeEffect[], assistantId: string): void;
|
|
7
|
+
}
|
|
8
|
+
export interface WorkbenchRuntimeControllerOptions {
|
|
9
|
+
dispatch(action: WorkbenchAction): void;
|
|
10
|
+
flushDelayMs?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function createWorkbenchRuntimeController(options: WorkbenchRuntimeControllerOptions): WorkbenchRuntimeController;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export function createWorkbenchRuntimeController(options) {
|
|
2
|
+
let textDeltaBuffer = null;
|
|
3
|
+
let textDeltaFlushTimer = null;
|
|
4
|
+
const flushDelayMs = options.flushDelayMs ?? 80;
|
|
5
|
+
return {
|
|
6
|
+
dispose() {
|
|
7
|
+
if (textDeltaFlushTimer) {
|
|
8
|
+
clearTimeout(textDeltaFlushTimer);
|
|
9
|
+
textDeltaFlushTimer = null;
|
|
10
|
+
}
|
|
11
|
+
textDeltaBuffer = null;
|
|
12
|
+
},
|
|
13
|
+
flushTextDeltaBuffer,
|
|
14
|
+
runEffects(effects, assistantId) {
|
|
15
|
+
for (const effect of effects) {
|
|
16
|
+
switch (effect.type) {
|
|
17
|
+
case "append_text_delta":
|
|
18
|
+
appendTextDeltaBuffered(assistantId, effect.delta);
|
|
19
|
+
break;
|
|
20
|
+
case "set_active_response_id":
|
|
21
|
+
break;
|
|
22
|
+
case "flush_text_delta_buffer":
|
|
23
|
+
flushTextDeltaBuffer();
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
function appendTextDeltaBuffered(id, delta) {
|
|
30
|
+
if (!delta)
|
|
31
|
+
return;
|
|
32
|
+
if (!textDeltaBuffer || textDeltaBuffer.id !== id) {
|
|
33
|
+
flushTextDeltaBuffer();
|
|
34
|
+
textDeltaBuffer = { id, text: delta };
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
textDeltaBuffer.text += delta;
|
|
38
|
+
}
|
|
39
|
+
if (textDeltaFlushTimer)
|
|
40
|
+
return;
|
|
41
|
+
textDeltaFlushTimer = setTimeout(() => {
|
|
42
|
+
textDeltaFlushTimer = null;
|
|
43
|
+
flushTextDeltaBuffer();
|
|
44
|
+
}, flushDelayMs);
|
|
45
|
+
}
|
|
46
|
+
function flushTextDeltaBuffer() {
|
|
47
|
+
if (textDeltaFlushTimer) {
|
|
48
|
+
clearTimeout(textDeltaFlushTimer);
|
|
49
|
+
textDeltaFlushTimer = null;
|
|
50
|
+
}
|
|
51
|
+
if (!textDeltaBuffer || !textDeltaBuffer.text)
|
|
52
|
+
return;
|
|
53
|
+
const buffered = textDeltaBuffer;
|
|
54
|
+
textDeltaBuffer = null;
|
|
55
|
+
options.dispatch({ type: "message.append", id: buffered.id, delta: buffered.text });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { AgentRunOptions } from "../agent.js";
|
|
2
|
+
import type { WorkbenchState } from "../tui/workbench.js";
|
|
3
|
+
import type { WorkbenchAuthController } from "./auth-controller.js";
|
|
4
|
+
import { type WorkbenchConversationController } from "./conversation-controller.js";
|
|
5
|
+
import { type WorkbenchEngine } from "./engine.js";
|
|
6
|
+
import { type WorkbenchInputController } from "./input-controller.js";
|
|
7
|
+
import { type WorkbenchLifecycleController } from "./lifecycle-controller.js";
|
|
8
|
+
import { type WorkbenchLocalController } from "./local-controller.js";
|
|
9
|
+
import { type WorkbenchRuntimeController } from "./runtime-controller.js";
|
|
10
|
+
import { type WorkbenchSettingsController } from "./settings-controller.js";
|
|
11
|
+
import { type WorkbenchTurnController } from "./turn-controller.js";
|
|
12
|
+
export interface WorkbenchSession {
|
|
13
|
+
conversation: WorkbenchConversationController;
|
|
14
|
+
engine: WorkbenchEngine;
|
|
15
|
+
input: WorkbenchInputController;
|
|
16
|
+
lifecycle: WorkbenchLifecycleController;
|
|
17
|
+
local: WorkbenchLocalController;
|
|
18
|
+
runtime: WorkbenchRuntimeController;
|
|
19
|
+
settings: WorkbenchSettingsController;
|
|
20
|
+
turn: WorkbenchTurnController;
|
|
21
|
+
}
|
|
22
|
+
export interface WorkbenchSessionOptions {
|
|
23
|
+
authController: WorkbenchAuthController;
|
|
24
|
+
baseOptions: AgentRunOptions;
|
|
25
|
+
}
|
|
26
|
+
export declare function createWorkbenchSession(options: WorkbenchSessionOptions): WorkbenchSession;
|
|
27
|
+
export declare function sessionState(session: Pick<WorkbenchSession, "engine">): WorkbenchState;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createWorkbenchConversationController } from "./conversation-controller.js";
|
|
2
|
+
import { createWorkbenchEngine } from "./engine.js";
|
|
3
|
+
import { createWorkbenchInputController } from "./input-controller.js";
|
|
4
|
+
import { createWorkbenchLifecycleController } from "./lifecycle-controller.js";
|
|
5
|
+
import { createWorkbenchLocalController } from "./local-controller.js";
|
|
6
|
+
import { createWorkbenchRuntimeController } from "./runtime-controller.js";
|
|
7
|
+
import { createWorkbenchSettingsController } from "./settings-controller.js";
|
|
8
|
+
import { createWorkbenchTurnController } from "./turn-controller.js";
|
|
9
|
+
export function createWorkbenchSession(options) {
|
|
10
|
+
const engine = createWorkbenchEngine({
|
|
11
|
+
accessMode: options.baseOptions.accessMode,
|
|
12
|
+
conversation: options.baseOptions.conversation,
|
|
13
|
+
contextEnabled: Boolean(options.baseOptions.includeLocalContext || options.baseOptions.workdir),
|
|
14
|
+
model: options.baseOptions.model,
|
|
15
|
+
preset: options.baseOptions.preset,
|
|
16
|
+
});
|
|
17
|
+
const local = createWorkbenchLocalController();
|
|
18
|
+
const runtime = createWorkbenchRuntimeController({ dispatch: engine.dispatch });
|
|
19
|
+
const turn = createWorkbenchTurnController({
|
|
20
|
+
baseOptions: options.baseOptions,
|
|
21
|
+
dispatch: engine.dispatch,
|
|
22
|
+
engine,
|
|
23
|
+
flushTextDeltaBuffer: runtime.flushTextDeltaBuffer,
|
|
24
|
+
getState: engine.snapshot,
|
|
25
|
+
runRuntimeEffects: runtime.runEffects,
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
conversation: createWorkbenchConversationController(),
|
|
29
|
+
engine,
|
|
30
|
+
input: createWorkbenchInputController(),
|
|
31
|
+
lifecycle: createWorkbenchLifecycleController({ authController: options.authController }),
|
|
32
|
+
local,
|
|
33
|
+
runtime,
|
|
34
|
+
settings: createWorkbenchSettingsController(),
|
|
35
|
+
turn,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function sessionState(session) {
|
|
39
|
+
return session.engine.snapshot();
|
|
40
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { clearPresetToolCatalogCache, isAvailablePreset, listAvailablePresets, type AgentRunOptions } from "../agent.js";
|
|
2
|
+
import { loadWorkbenchPreferences, updateWorkbenchPreferences, type WorkbenchPreferences } from "../config.js";
|
|
3
|
+
import type { RenderMode } from "../tui/workbench.js";
|
|
4
|
+
export interface WorkbenchSettingsSnapshot {
|
|
5
|
+
defaultPreset?: string | null;
|
|
6
|
+
runPreset?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface WorkbenchSettingsController {
|
|
9
|
+
loadInitial(options: Pick<AgentRunOptions, "modelExplicit" | "preset" | "presetExplicit">): Promise<WorkbenchSettingsSnapshot>;
|
|
10
|
+
saveDefaultPreset(input: {
|
|
11
|
+
value: string;
|
|
12
|
+
profileName?: string;
|
|
13
|
+
options: Pick<AgentRunOptions, "modelExplicit" | "preset" | "presetExplicit">;
|
|
14
|
+
}): Promise<WorkbenchSettingsSnapshot & {
|
|
15
|
+
message: string;
|
|
16
|
+
activity: string;
|
|
17
|
+
}>;
|
|
18
|
+
validatePreset(profileName: string | undefined, preset: string): Promise<boolean>;
|
|
19
|
+
presetListText(input: {
|
|
20
|
+
profileName?: string;
|
|
21
|
+
currentPreset?: string;
|
|
22
|
+
prefix: string;
|
|
23
|
+
}): Promise<string>;
|
|
24
|
+
configText(input: {
|
|
25
|
+
accessMode: string;
|
|
26
|
+
contextEnabled: boolean;
|
|
27
|
+
defaultPreset?: string | null;
|
|
28
|
+
profileName: string;
|
|
29
|
+
runModel?: string;
|
|
30
|
+
runPreset?: string;
|
|
31
|
+
renderMode: RenderMode;
|
|
32
|
+
}): string;
|
|
33
|
+
defaultPresetHelp(defaultPreset?: string | null): string;
|
|
34
|
+
clearPresetToolCatalogCache(baseURL?: string): void;
|
|
35
|
+
}
|
|
36
|
+
export interface WorkbenchSettingsControllerOptions {
|
|
37
|
+
loadWorkbenchPreferencesImpl?: typeof loadWorkbenchPreferences;
|
|
38
|
+
updateWorkbenchPreferencesImpl?: typeof updateWorkbenchPreferences;
|
|
39
|
+
isAvailablePresetImpl?: typeof isAvailablePreset;
|
|
40
|
+
listAvailablePresetsImpl?: typeof listAvailablePresets;
|
|
41
|
+
clearPresetToolCatalogCacheImpl?: typeof clearPresetToolCatalogCache;
|
|
42
|
+
formatError?: (error: unknown) => string;
|
|
43
|
+
}
|
|
44
|
+
export declare function createWorkbenchSettingsController(options?: WorkbenchSettingsControllerOptions): WorkbenchSettingsController;
|
|
45
|
+
export declare class UnknownPresetError extends Error {
|
|
46
|
+
readonly preset: string;
|
|
47
|
+
constructor(preset: string);
|
|
48
|
+
}
|
|
49
|
+
export declare function normalizeDefaultPreset(value: string): string | null | undefined;
|
|
50
|
+
export declare function formatDefaultPreset(value: string | null | undefined): string;
|
|
51
|
+
export declare function effectiveDefaultPreset(preferences: WorkbenchPreferences, builtInPreset?: string): string | undefined;
|
|
52
|
+
export declare function formatPresetList(presets: Awaited<ReturnType<typeof listAvailablePresets>>, currentPreset?: string): string[];
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { clearPresetToolCatalogCache, isAvailablePreset, listAvailablePresets, } from "../agent.js";
|
|
2
|
+
import { loadWorkbenchPreferences, updateWorkbenchPreferences, } from "../config.js";
|
|
3
|
+
export function createWorkbenchSettingsController(options = {}) {
|
|
4
|
+
const loadWorkbenchPreferencesImpl = options.loadWorkbenchPreferencesImpl ?? loadWorkbenchPreferences;
|
|
5
|
+
const updateWorkbenchPreferencesImpl = options.updateWorkbenchPreferencesImpl ?? updateWorkbenchPreferences;
|
|
6
|
+
const isAvailablePresetImpl = options.isAvailablePresetImpl ?? isAvailablePreset;
|
|
7
|
+
const listAvailablePresetsImpl = options.listAvailablePresetsImpl ?? listAvailablePresets;
|
|
8
|
+
const clearPresetToolCatalogCacheImpl = options.clearPresetToolCatalogCacheImpl ?? clearPresetToolCatalogCache;
|
|
9
|
+
const formatError = options.formatError ?? userFacingError;
|
|
10
|
+
return {
|
|
11
|
+
async loadInitial(agentOptions) {
|
|
12
|
+
const preferences = await loadWorkbenchPreferencesImpl();
|
|
13
|
+
return {
|
|
14
|
+
defaultPreset: preferences.defaultPreset,
|
|
15
|
+
runPreset: shouldApplyDefaultPreset(agentOptions)
|
|
16
|
+
? effectiveDefaultPreset(preferences, agentOptions.preset)
|
|
17
|
+
: undefined,
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
async saveDefaultPreset(input) {
|
|
21
|
+
const normalized = normalizeDefaultPreset(input.value);
|
|
22
|
+
if (typeof normalized === "string" && !(await isAvailablePresetImpl(input.profileName, normalized))) {
|
|
23
|
+
throw new UnknownPresetError(normalized);
|
|
24
|
+
}
|
|
25
|
+
const preferences = await updateWorkbenchPreferencesImpl({ defaultPreset: normalized });
|
|
26
|
+
return {
|
|
27
|
+
defaultPreset: preferences.defaultPreset,
|
|
28
|
+
runPreset: shouldApplyDefaultPreset(input.options)
|
|
29
|
+
? effectiveDefaultPreset(preferences, input.options.preset)
|
|
30
|
+
: undefined,
|
|
31
|
+
message: `Saved default preset: ${formatDefaultPreset(preferences.defaultPreset)}.`,
|
|
32
|
+
activity: `Default preset saved: ${formatDefaultPreset(preferences.defaultPreset)}`,
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
async validatePreset(profileName, preset) {
|
|
36
|
+
return isAvailablePresetImpl(profileName, preset);
|
|
37
|
+
},
|
|
38
|
+
async presetListText(input) {
|
|
39
|
+
try {
|
|
40
|
+
const presets = await listAvailablePresetsImpl(input.profileName);
|
|
41
|
+
return [
|
|
42
|
+
input.prefix,
|
|
43
|
+
"",
|
|
44
|
+
"Available presets:",
|
|
45
|
+
...formatPresetList(presets, input.currentPreset),
|
|
46
|
+
].join("\n");
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
return [
|
|
50
|
+
input.prefix,
|
|
51
|
+
"",
|
|
52
|
+
`Available presets could not be loaded: ${formatError(error)}`,
|
|
53
|
+
].join("\n");
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
configText: runConfigText,
|
|
57
|
+
defaultPresetHelp(defaultPreset) {
|
|
58
|
+
return `Default preset: ${formatDefaultPreset(defaultPreset)}. Use /config preset <name>, /config preset none, or /config preset reset.`;
|
|
59
|
+
},
|
|
60
|
+
clearPresetToolCatalogCache(baseURL) {
|
|
61
|
+
clearPresetToolCatalogCacheImpl(baseURL);
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export class UnknownPresetError extends Error {
|
|
66
|
+
preset;
|
|
67
|
+
constructor(preset) {
|
|
68
|
+
super(`Unknown preset: ${preset}`);
|
|
69
|
+
this.preset = preset;
|
|
70
|
+
this.name = "UnknownPresetError";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export function normalizeDefaultPreset(value) {
|
|
74
|
+
const trimmed = value.trim();
|
|
75
|
+
const lowered = trimmed.toLowerCase();
|
|
76
|
+
if (!trimmed || lowered === "reset" || lowered === "default" || lowered === "builtin")
|
|
77
|
+
return undefined;
|
|
78
|
+
if (["none", "off", "disable", "disabled"].includes(lowered))
|
|
79
|
+
return null;
|
|
80
|
+
return trimmed;
|
|
81
|
+
}
|
|
82
|
+
export function formatDefaultPreset(value) {
|
|
83
|
+
if (value === undefined)
|
|
84
|
+
return "built-in (pro-search)";
|
|
85
|
+
return value ?? "none";
|
|
86
|
+
}
|
|
87
|
+
export function effectiveDefaultPreset(preferences, builtInPreset) {
|
|
88
|
+
if ("defaultPreset" in preferences)
|
|
89
|
+
return preferences.defaultPreset ?? undefined;
|
|
90
|
+
return builtInPreset;
|
|
91
|
+
}
|
|
92
|
+
export function formatPresetList(presets, currentPreset) {
|
|
93
|
+
if (presets.length === 0)
|
|
94
|
+
return ["- none returned by this endpoint"];
|
|
95
|
+
return presets.map((preset) => {
|
|
96
|
+
const description = preset.description ? ` - ${preset.description}` : "";
|
|
97
|
+
const current = currentPreset && preset.preset === currentPreset;
|
|
98
|
+
return `${current ? "*" : "-"} ${preset.preset}${current ? " (current)" : ""}${description}`;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function shouldApplyDefaultPreset(options) {
|
|
102
|
+
return !options.presetExplicit && !options.modelExplicit;
|
|
103
|
+
}
|
|
104
|
+
function runConfigText({ accessMode, contextEnabled, defaultPreset, profileName, runModel, runPreset, renderMode, }) {
|
|
105
|
+
return [
|
|
106
|
+
`Profile: ${profileName}`,
|
|
107
|
+
`Preset: ${runPreset || "none"}`,
|
|
108
|
+
`Default preset: ${formatDefaultPreset(defaultPreset)}`,
|
|
109
|
+
`Model: ${runModel || "auto"}`,
|
|
110
|
+
`Render mode: ${renderMode}`,
|
|
111
|
+
`local_workdir tool: ${contextEnabled ? "on" : "off"}`,
|
|
112
|
+
`local_shell tool: ${contextEnabled ? "on" : "off"}`,
|
|
113
|
+
`Local access: ${accessMode}`,
|
|
114
|
+
].join("\n");
|
|
115
|
+
}
|
|
116
|
+
function userFacingError(error) {
|
|
117
|
+
if (error instanceof Error)
|
|
118
|
+
return error.message;
|
|
119
|
+
return String(error);
|
|
120
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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;
|
|
@@ -0,0 +1,162 @@
|
|
|
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
|
+
restartConversation: false,
|
|
35
|
+
abortSignal: abortController.signal,
|
|
36
|
+
}, (event) => handleAgentEvent(event, assistantId));
|
|
37
|
+
options.dispatch({
|
|
38
|
+
type: "activity.add",
|
|
39
|
+
level: "success",
|
|
40
|
+
text: result.responseID ? `Agent turn completed: ${result.responseID}` : "Agent turn completed",
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
handleTurnError(error);
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
finishTurn(abortController);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
async continueAfterLocalApproval(input) {
|
|
51
|
+
const state = options.getState();
|
|
52
|
+
const assistantId = `assistant-${Date.now()}`;
|
|
53
|
+
const abortController = new AbortController();
|
|
54
|
+
activeAbortController = abortController;
|
|
55
|
+
activeResponseID = null;
|
|
56
|
+
options.dispatch({ type: "busy.set", busy: true });
|
|
57
|
+
options.dispatch({ type: "message.add", role: "assistant", text: "", id: assistantId });
|
|
58
|
+
options.dispatch({ type: "assistant.active", id: assistantId });
|
|
59
|
+
options.dispatch({ type: "activity.add", text: "Continuing agent turn" });
|
|
60
|
+
try {
|
|
61
|
+
const continuation = await resumeAgentAfterLocalApprovalImpl({
|
|
62
|
+
...options.baseOptions,
|
|
63
|
+
preset: state.runPreset,
|
|
64
|
+
model: state.runModel,
|
|
65
|
+
stream: true,
|
|
66
|
+
file: undefined,
|
|
67
|
+
stdin: false,
|
|
68
|
+
conversation: state.currentConversation,
|
|
69
|
+
includeLocalContext: state.contextEnabled,
|
|
70
|
+
accessMode: input.accessMode,
|
|
71
|
+
restartConversation: false,
|
|
72
|
+
abortSignal: abortController.signal,
|
|
73
|
+
}, input.approval, input.result, (event) => handleAgentEvent(event, assistantId));
|
|
74
|
+
options.dispatch({
|
|
75
|
+
type: "activity.add",
|
|
76
|
+
level: "success",
|
|
77
|
+
text: continuation.responseID ? `Agent turn continued: ${continuation.responseID}` : "Agent turn continued",
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
handleTurnError(error);
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
finishTurn(abortController);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
async abort(reason) {
|
|
88
|
+
const state = options.getState();
|
|
89
|
+
if (!state.busy && !activeAbortController && !activeResponseID) {
|
|
90
|
+
options.dispatch({ type: "message.add", role: "system", text: "No agent turn is running." });
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
activeAbortController?.abort();
|
|
94
|
+
options.dispatch({ type: "activity.add", level: "warning", text: reason });
|
|
95
|
+
if (!activeResponseID) {
|
|
96
|
+
options.dispatch({ type: "message.add", role: "system", text: "Abort requested. No remote response ID is available yet." });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (cancelledResponseIDs.has(activeResponseID))
|
|
100
|
+
return;
|
|
101
|
+
const responseID = activeResponseID;
|
|
102
|
+
cancelledResponseIDs.add(responseID);
|
|
103
|
+
try {
|
|
104
|
+
const runtimeProfile = await resolveRuntimeProfileImpl(options.baseOptions.profile);
|
|
105
|
+
const result = await runtimeProfile.client.responses.cancel(responseID);
|
|
106
|
+
options.dispatch({
|
|
107
|
+
type: "message.add",
|
|
108
|
+
role: "system",
|
|
109
|
+
text: result.interrupted
|
|
110
|
+
? `Abort requested for response ${responseID}.`
|
|
111
|
+
: `Abort requested locally. Remote response ${responseID} was not actively interrupted.`,
|
|
112
|
+
});
|
|
113
|
+
options.dispatch({
|
|
114
|
+
type: "activity.add",
|
|
115
|
+
level: result.interrupted ? "success" : "warning",
|
|
116
|
+
text: result.interrupted ? `Response cancel requested: ${responseID}` : `Response was not active: ${responseID}`,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
options.dispatch({ type: "message.add", role: "system", text: `Abort requested locally, but remote cancel failed: ${userFacingError(error)}` });
|
|
121
|
+
options.dispatch({ type: "activity.add", level: "error", text: "Remote response cancel failed" });
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
function handleAgentEvent(event, assistantId) {
|
|
126
|
+
const result = options.engine.handleAgentEvent(event);
|
|
127
|
+
for (const effect of result.effects) {
|
|
128
|
+
if (effect.type === "set_active_response_id") {
|
|
129
|
+
activeResponseID = effect.responseID;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
options.runRuntimeEffects(result.effects, assistantId);
|
|
133
|
+
}
|
|
134
|
+
function finishTurn(abortController) {
|
|
135
|
+
options.flushTextDeltaBuffer();
|
|
136
|
+
if (activeAbortController === abortController) {
|
|
137
|
+
activeAbortController = null;
|
|
138
|
+
}
|
|
139
|
+
activeResponseID = null;
|
|
140
|
+
options.dispatch({ type: "busy.set", busy: false });
|
|
141
|
+
options.dispatch({ type: "assistant.active", id: null });
|
|
142
|
+
}
|
|
143
|
+
function handleTurnError(error) {
|
|
144
|
+
const message = userFacingError(error);
|
|
145
|
+
const aborted = /aborted/i.test(message);
|
|
146
|
+
options.dispatch({
|
|
147
|
+
type: "message.add",
|
|
148
|
+
role: "system",
|
|
149
|
+
text: aborted ? "Agent turn aborted." : message,
|
|
150
|
+
});
|
|
151
|
+
options.dispatch({
|
|
152
|
+
type: "activity.add",
|
|
153
|
+
level: aborted ? "warning" : "error",
|
|
154
|
+
text: aborted ? "Agent turn aborted" : message,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function userFacingError(error) {
|
|
159
|
+
if (error instanceof Error)
|
|
160
|
+
return error.message;
|
|
161
|
+
return String(error);
|
|
162
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
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;
|