@agent-api/cli 0.2.1 → 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.
Files changed (61) hide show
  1. package/README.md +11 -5
  2. package/dist/index.js +8 -6
  3. package/dist/runtime.d.ts +4 -0
  4. package/dist/runtime.js +4 -0
  5. package/dist/tui/ink/app.d.ts +1 -1
  6. package/dist/tui/ink/app.js +27 -130
  7. package/dist/tui/ink/components.d.ts +1 -2
  8. package/dist/tui/ink/components.js +1 -3
  9. package/package.json +9 -6
  10. package/dist/agent/runner.d.ts +0 -117
  11. package/dist/agent/runner.js +0 -486
  12. package/dist/agent.d.ts +0 -2
  13. package/dist/agent.js +0 -2
  14. package/dist/chat-options.d.ts +0 -37
  15. package/dist/chat-options.js +0 -42
  16. package/dist/config.d.ts +0 -66
  17. package/dist/config.js +0 -201
  18. package/dist/conversation/index.d.ts +0 -17
  19. package/dist/conversation/index.js +0 -54
  20. package/dist/profile.d.ts +0 -57
  21. package/dist/profile.js +0 -211
  22. package/dist/runtime/index.d.ts +0 -5
  23. package/dist/runtime/index.js +0 -152
  24. package/dist/tui/workbench.d.ts +0 -187
  25. package/dist/tui/workbench.js +0 -392
  26. package/dist/update.d.ts +0 -16
  27. package/dist/update.js +0 -74
  28. package/dist/workbench/auth-controller.d.ts +0 -43
  29. package/dist/workbench/auth-controller.js +0 -84
  30. package/dist/workbench/auth-gate-controller.d.ts +0 -62
  31. package/dist/workbench/auth-gate-controller.js +0 -231
  32. package/dist/workbench/command-controller.d.ts +0 -29
  33. package/dist/workbench/command-controller.js +0 -426
  34. package/dist/workbench/conversation-controller.d.ts +0 -32
  35. package/dist/workbench/conversation-controller.js +0 -53
  36. package/dist/workbench/engine.d.ts +0 -66
  37. package/dist/workbench/engine.js +0 -291
  38. package/dist/workbench/input-controller.d.ts +0 -44
  39. package/dist/workbench/input-controller.js +0 -71
  40. package/dist/workbench/isolator-installer.d.ts +0 -29
  41. package/dist/workbench/isolator-installer.js +0 -208
  42. package/dist/workbench/lifecycle-controller.d.ts +0 -30
  43. package/dist/workbench/lifecycle-controller.js +0 -75
  44. package/dist/workbench/local-controller.d.ts +0 -21
  45. package/dist/workbench/local-controller.js +0 -94
  46. package/dist/workbench/render-model.d.ts +0 -46
  47. package/dist/workbench/render-model.js +0 -61
  48. package/dist/workbench/runtime-controller.d.ts +0 -12
  49. package/dist/workbench/runtime-controller.js +0 -57
  50. package/dist/workbench/session.d.ts +0 -27
  51. package/dist/workbench/session.js +0 -42
  52. package/dist/workbench/settings-controller.d.ts +0 -80
  53. package/dist/workbench/settings-controller.js +0 -309
  54. package/dist/workbench/shell-isolation.d.ts +0 -20
  55. package/dist/workbench/shell-isolation.js +0 -13
  56. package/dist/workbench/turn-controller.d.ts +0 -25
  57. package/dist/workbench/turn-controller.js +0 -164
  58. package/dist/workbench/view-model.d.ts +0 -34
  59. package/dist/workbench/view-model.js +0 -121
  60. package/dist/workdir/index.d.ts +0 -22
  61. package/dist/workdir/index.js +0 -46
@@ -1,208 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { constants as fsConstants } from "node:fs";
3
- import { access, chmod, copyFile, mkdir, rename, rm, stat, writeFile } from "node:fs/promises";
4
- import { spawn } from "node:child_process";
5
- import path from "node:path";
6
- import { runtime } from "../runtime/index.js";
7
- export async function installConfiguredIsolator(config, options = {}) {
8
- const sourceURL = normalizeSourceURL(config.sourceURL);
9
- const executablePath = normalizeInstallPath(config.executablePath);
10
- const targetDir = path.dirname(executablePath);
11
- const existing = await existingTargetState(executablePath);
12
- await mkdir(targetDir, { recursive: true });
13
- await ensureWritableDirectory(targetDir);
14
- const fetchImpl = options.fetchImpl ?? fetch;
15
- const response = await fetchImpl(sourceURL);
16
- if (!response.ok) {
17
- throw new Error(`isolator download failed: HTTP ${response.status}`);
18
- }
19
- const body = new Uint8Array(await response.arrayBuffer());
20
- if (body.length === 0) {
21
- throw new Error("isolator download failed: empty response body");
22
- }
23
- const actualSha256 = createHash("sha256").update(body).digest("hex");
24
- const expectedSha256 = config.sha256?.trim().toLowerCase();
25
- if (expectedSha256 && actualSha256 !== expectedSha256) {
26
- throw new Error(`isolator checksum mismatch: expected ${expectedSha256}, got ${actualSha256}`);
27
- }
28
- const tempPath = path.join(targetDir, `.${path.basename(executablePath)}.${process.pid}.${Date.now()}.tmp`);
29
- try {
30
- await writeFile(tempPath, body, { mode: 0o700 });
31
- if (process.platform !== "win32") {
32
- await chmod(tempPath, 0o700);
33
- }
34
- await probeIsolator(tempPath, options.probeTimeoutMs ?? 10_000);
35
- await rename(tempPath, executablePath);
36
- }
37
- catch (error) {
38
- await rm(tempPath, { force: true });
39
- throw error;
40
- }
41
- return {
42
- executablePath,
43
- sourceURL,
44
- bytes: body.length,
45
- sha256: actualSha256,
46
- replaced: existing === "file",
47
- };
48
- }
49
- export async function validateInstalledIsolator(executablePath, options = {}) {
50
- const normalized = normalizeInstallPath(executablePath);
51
- const state = await existingTargetState(normalized);
52
- if (state !== "file") {
53
- throw new Error(`isolator executable does not exist: ${normalized}`);
54
- }
55
- await probeIsolator(normalized, options.probeTimeoutMs ?? 10_000);
56
- return normalized;
57
- }
58
- export async function ensureConfiguredIsolator(config, options = {}) {
59
- const sourceURL = normalizeSourceURL(config.sourceURL);
60
- const executablePath = normalizeInstallPath(config.executablePath);
61
- try {
62
- await validateInstalledIsolator(executablePath, options);
63
- return {
64
- executablePath,
65
- sourceURL,
66
- sha256: config.sha256,
67
- repaired: false,
68
- };
69
- }
70
- catch {
71
- const result = await installConfiguredIsolator({ sourceURL, executablePath, sha256: config.sha256 }, options);
72
- return {
73
- executablePath: result.executablePath,
74
- sourceURL: result.sourceURL,
75
- sha256: result.sha256,
76
- repaired: true,
77
- };
78
- }
79
- }
80
- export async function relocateInstalledIsolator(fromPath, toPath, options = {}) {
81
- const source = await validateInstalledIsolator(fromPath, options);
82
- const target = normalizeInstallPath(toPath);
83
- if (source === target)
84
- return target;
85
- const targetDir = path.dirname(target);
86
- await existingTargetState(target);
87
- await mkdir(targetDir, { recursive: true });
88
- await ensureWritableDirectory(targetDir);
89
- const tempPath = path.join(targetDir, `.${path.basename(target)}.${process.pid}.${Date.now()}.tmp`);
90
- try {
91
- await copyFile(source, tempPath);
92
- if (process.platform !== "win32") {
93
- await chmod(tempPath, 0o700);
94
- }
95
- await probeIsolator(tempPath, options.probeTimeoutMs ?? 10_000);
96
- await rename(tempPath, target);
97
- }
98
- catch (error) {
99
- await rm(tempPath, { force: true });
100
- throw error;
101
- }
102
- return target;
103
- }
104
- export function defaultIsolatorInstallPath() {
105
- return path.join(runtime.dirs.data, "bin", process.platform === "win32" ? "agent-isolator.exe" : "agent-isolator");
106
- }
107
- export function normalizeSourceURL(value) {
108
- const trimmed = value?.trim();
109
- if (!trimmed)
110
- throw new Error("isolator sourceURL is required");
111
- let url;
112
- try {
113
- url = new URL(trimmed);
114
- }
115
- catch {
116
- throw new Error("isolator sourceURL must be a valid URL");
117
- }
118
- if (url.protocol !== "https:") {
119
- throw new Error("isolator sourceURL must use https");
120
- }
121
- return url.toString();
122
- }
123
- export function normalizeInstallPath(value) {
124
- const trimmed = value?.trim();
125
- if (!trimmed)
126
- throw new Error("isolator executablePath is required");
127
- if (!path.isAbsolute(trimmed)) {
128
- throw new Error("isolator executablePath must be absolute");
129
- }
130
- const normalized = path.normalize(trimmed);
131
- const root = path.parse(normalized).root;
132
- if (normalized === root) {
133
- throw new Error("isolator executablePath cannot be a filesystem root");
134
- }
135
- if (path.basename(normalized).startsWith(".")) {
136
- throw new Error("isolator executablePath must not be a hidden temp-style filename");
137
- }
138
- return normalized;
139
- }
140
- async function existingTargetState(file) {
141
- try {
142
- const info = await stat(file);
143
- if (!info.isFile()) {
144
- throw new Error(`isolator executablePath exists but is not a file: ${file}`);
145
- }
146
- return "file";
147
- }
148
- catch (error) {
149
- if (error?.code === "ENOENT")
150
- return "missing";
151
- throw error;
152
- }
153
- }
154
- async function ensureWritableDirectory(dir) {
155
- try {
156
- await access(dir, fsConstants.W_OK);
157
- }
158
- catch {
159
- throw new Error(`isolator target directory is not writable: ${dir}`);
160
- }
161
- }
162
- async function probeIsolator(executablePath, timeoutMs) {
163
- const request = JSON.stringify({ id: "status", method: "status", params: {} });
164
- const child = spawn(executablePath, ["--once", "--driver=auto"], {
165
- stdio: ["pipe", "pipe", "pipe"],
166
- windowsHide: true,
167
- });
168
- const chunks = [];
169
- const errors = [];
170
- child.stdout.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
171
- child.stderr.on("data", (chunk) => errors.push(Buffer.from(chunk)));
172
- child.stdin.end(`${request}\n`);
173
- const code = await new Promise((resolve, reject) => {
174
- const timer = setTimeout(() => {
175
- child.kill();
176
- reject(new Error("isolator probe timed out"));
177
- }, timeoutMs);
178
- child.on("error", (error) => {
179
- clearTimeout(timer);
180
- reject(error);
181
- });
182
- child.on("close", (exitCode) => {
183
- clearTimeout(timer);
184
- resolve(exitCode);
185
- });
186
- });
187
- const stdout = Buffer.concat(chunks).toString("utf8").trim();
188
- const stderr = Buffer.concat(errors).toString("utf8").trim();
189
- if (code !== 0) {
190
- throw new Error(stderr || stdout || `isolator probe failed with exit code ${code}`);
191
- }
192
- const line = stdout.split(/\r?\n/).find(Boolean);
193
- if (!line)
194
- throw new Error("isolator probe returned no response");
195
- let parsed;
196
- try {
197
- parsed = JSON.parse(line);
198
- }
199
- catch {
200
- throw new Error("isolator probe returned invalid JSON");
201
- }
202
- if (parsed?.error) {
203
- throw new Error(parsed.error.message || parsed.error.code || "isolator probe failed");
204
- }
205
- if (!parsed?.result?.status?.driver) {
206
- throw new Error("isolator probe response did not include shell isolation status");
207
- }
208
- }
@@ -1,30 +0,0 @@
1
- import type { WorkbenchAuthController } from "./auth-controller.js";
2
- import type { WorkbenchAction, WorkbenchWorkdirStatus } from "../tui/workbench.js";
3
- import { checkForUpdate, formatUpdateNotice, type UpdateCheckResult } from "../update.js";
4
- export type WorkbenchLifecycleEffect = {
5
- type: "dispatch";
6
- action: WorkbenchAction;
7
- } | {
8
- type: "close";
9
- delayMs: number;
10
- };
11
- export interface WorkbenchLifecycleController {
12
- maybeCheckForUpdate(): Promise<WorkbenchLifecycleEffect[]>;
13
- refreshAuth(profile?: string): Promise<WorkbenchLifecycleEffect[]>;
14
- initialPrompt(input: {
15
- busy: boolean;
16
- promptParts: string[];
17
- requiresWorkdir?: boolean;
18
- workdir: WorkbenchWorkdirStatus | null;
19
- }): string | undefined;
20
- }
21
- export interface WorkbenchLifecycleControllerOptions {
22
- authController: WorkbenchAuthController;
23
- checkForUpdateImpl?: typeof checkForUpdate;
24
- formatUpdateNoticeImpl?: typeof formatUpdateNotice;
25
- formatError?: (error: unknown) => string;
26
- refreshWindowMs?: number;
27
- updateCheckEnabled?: boolean;
28
- }
29
- export declare function createWorkbenchLifecycleController(options: WorkbenchLifecycleControllerOptions): WorkbenchLifecycleController;
30
- export declare function updateNoticeEffects(result: UpdateCheckResult | null | undefined, formatNotice?: typeof formatUpdateNotice): WorkbenchLifecycleEffect[];
@@ -1,75 +0,0 @@
1
- import { checkForUpdate, formatUpdateNotice } from "../update.js";
2
- export function createWorkbenchLifecycleController(options) {
3
- const checkForUpdateImpl = options.checkForUpdateImpl ?? checkForUpdate;
4
- const formatUpdateNoticeImpl = options.formatUpdateNoticeImpl ?? formatUpdateNotice;
5
- const formatError = options.formatError ?? userFacingError;
6
- const refreshWindowMs = options.refreshWindowMs ?? 5 * 60_000;
7
- const updateCheckEnabled = options.updateCheckEnabled ?? process.env.AGENT_TUI_UPDATE_CHECK !== "0";
8
- let updateNoticeShown = false;
9
- let authRefreshWarningShown = false;
10
- let initialPromptSubmitted = false;
11
- return {
12
- async maybeCheckForUpdate() {
13
- if (updateNoticeShown || !updateCheckEnabled)
14
- return [];
15
- updateNoticeShown = true;
16
- try {
17
- const result = await checkForUpdateImpl();
18
- return updateNoticeEffects(result, formatUpdateNoticeImpl);
19
- }
20
- catch {
21
- return [];
22
- }
23
- },
24
- async refreshAuth(profile) {
25
- try {
26
- const result = await options.authController.refresh(profile, refreshWindowMs);
27
- if (!result.refreshed)
28
- return [];
29
- authRefreshWarningShown = false;
30
- return [
31
- { type: "dispatch", action: { type: "activity.add", level: "success", text: "Auth session refreshed" } },
32
- ];
33
- }
34
- catch (error) {
35
- if (authRefreshWarningShown)
36
- return [];
37
- authRefreshWarningShown = true;
38
- return [
39
- {
40
- type: "dispatch",
41
- action: {
42
- type: "message.add",
43
- role: "system",
44
- text: `${formatError(error)}\n\nClosing the workbench because authenticated agent conversations are unavailable.`,
45
- },
46
- },
47
- { type: "dispatch", action: { type: "activity.add", level: "error", text: "Auth session needs login; closing" } },
48
- { type: "close", delayMs: 1500 },
49
- ];
50
- }
51
- },
52
- initialPrompt(input) {
53
- if (initialPromptSubmitted || input.busy || (input.requiresWorkdir && !input.workdir))
54
- return undefined;
55
- const prompt = input.promptParts.join(" ").trim();
56
- if (!prompt)
57
- return undefined;
58
- initialPromptSubmitted = true;
59
- return prompt;
60
- },
61
- };
62
- }
63
- export function updateNoticeEffects(result, formatNotice = formatUpdateNotice) {
64
- if (!result?.updateAvailable)
65
- return [];
66
- return [
67
- { type: "dispatch", action: { type: "activity.add", level: "warning", text: `Update available: ${result.latest}` } },
68
- { type: "dispatch", action: { type: "message.add", role: "system", text: formatNotice(result) } },
69
- ];
70
- }
71
- function userFacingError(error) {
72
- if (error instanceof Error)
73
- return error.message;
74
- return String(error);
75
- }
@@ -1,21 +0,0 @@
1
- import { openWorkdir } from "../workdir/index.js";
2
- import type { WorkbenchState, WorkbenchWorkdirStatus } from "../tui/workbench.js";
3
- import type { ShellIsolationPreferences } from "./shell-isolation.js";
4
- export interface WorkbenchLocalController {
5
- load(path?: string): Promise<WorkbenchWorkdirStatus>;
6
- isLoaded(): boolean;
7
- summaryText(): Promise<string>;
8
- searchText(query: string): Promise<{
9
- text: string;
10
- count: number;
11
- }>;
12
- approvalPreview(approval: LocalApprovalLike): string;
13
- applyApproval(approval: LocalApprovalLike): Promise<string | Record<string, unknown>>;
14
- }
15
- export interface WorkbenchLocalControllerOptions {
16
- openWorkdirImpl?: typeof openWorkdir;
17
- getShellIsolation?: () => ShellIsolationPreferences | undefined;
18
- }
19
- type LocalApprovalLike = NonNullable<WorkbenchState["pendingLocalTool"]>;
20
- export declare function createWorkbenchLocalController(options?: WorkbenchLocalControllerOptions): WorkbenchLocalController;
21
- export {};
@@ -1,94 +0,0 @@
1
- import { createLocalShellToolRegistry, createLocalWorkdirToolRegistry } from "@agent-api/sdk/local";
2
- import { openWorkdir } from "../workdir/index.js";
3
- import { localShellIsolationOptions } from "./shell-isolation.js";
4
- export function createWorkbenchLocalController(options = {}) {
5
- const openWorkdirImpl = options.openWorkdirImpl ?? openWorkdir;
6
- let workdir = null;
7
- return {
8
- async load(path) {
9
- const next = await openWorkdirImpl({ path });
10
- const summary = await next.summarize();
11
- workdir = next;
12
- return {
13
- root: next.root,
14
- name: next.name,
15
- fileCount: summary.file_count,
16
- totalBytes: summary.total_bytes,
17
- scanTruncated: summary.scan_truncated,
18
- };
19
- },
20
- isLoaded() {
21
- return Boolean(workdir);
22
- },
23
- async summaryText() {
24
- const current = requireWorkdir();
25
- const summary = await current.summarize();
26
- const previews = summary.text_previews
27
- .slice(0, 5)
28
- .map((preview) => `- ${preview.path} (${formatBytes(preview.size)})`)
29
- .join("\n");
30
- return [
31
- `Workdir summary for ${current.name}`,
32
- `Files: ${summary.file_count}`,
33
- `Size: ${formatBytes(summary.total_bytes)}`,
34
- previews ? `Previews:\n${previews}` : "No text previews available.",
35
- ].join("\n");
36
- },
37
- async searchText(query) {
38
- const trimmed = query.trim();
39
- if (!trimmed) {
40
- return { text: "Usage: /search <query>", count: 0 };
41
- }
42
- const current = requireWorkdir();
43
- const results = await current.workdir.grep({ pattern: trimmed, limit: 12 });
44
- const matches = results.matches
45
- .map((match) => `${match.path}:${match.line_number}: ${match.line.trim()}`)
46
- .join("\n");
47
- return {
48
- text: matches || `No matches for "${trimmed}".`,
49
- count: results.matches.length,
50
- };
51
- },
52
- approvalPreview(approval) {
53
- return formatLocalToolApproval(approval);
54
- },
55
- async applyApproval(approval) {
56
- const current = requireWorkdir();
57
- const workdirRegistry = createLocalWorkdirToolRegistry(current.workdir, { accessMode: "full" });
58
- const shellRegistry = createLocalShellToolRegistry({
59
- workdir: current.workdir,
60
- accessMode: "full",
61
- ...localShellIsolationOptions(options.getShellIsolation?.()),
62
- });
63
- if (approval.name === workdirRegistry.toolName) {
64
- return await workdirRegistry.execute(approval.name, approval.arguments);
65
- }
66
- if (approval.name === shellRegistry.toolName) {
67
- return await shellRegistry.execute(approval.name, approval.arguments);
68
- }
69
- throw new Error(`no local handler registered for function ${approval.name}`);
70
- },
71
- };
72
- function requireWorkdir() {
73
- if (!workdir)
74
- throw new Error("Workdir is still loading.");
75
- return workdir;
76
- }
77
- }
78
- function formatLocalToolApproval(approval) {
79
- return [
80
- `Local approval requested: ${approval.name}${approval.action ? `.${approval.action}` : ""}`,
81
- "Arguments:",
82
- JSON.stringify(approval.arguments, null, 2),
83
- approval.preview ? ["Preview:", JSON.stringify(approval.preview, null, 2)].join("\n") : "",
84
- "",
85
- "Use /apply to execute this action once, /apply-all to allow future local actions, or /reject to discard it.",
86
- ].filter(Boolean).join("\n");
87
- }
88
- function formatBytes(bytes) {
89
- if (bytes < 1024)
90
- return `${bytes} B`;
91
- if (bytes < 1024 * 1024)
92
- return `${(bytes / 1024).toFixed(1)} KB`;
93
- return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
94
- }
@@ -1,46 +0,0 @@
1
- import type { RenderMode, WorkbenchState } from "../tui/workbench.js";
2
- import { type TranscriptViewModel } from "./view-model.js";
3
- export interface WorkbenchRendererViewport {
4
- rows: number;
5
- columns: number;
6
- }
7
- export interface WorkbenchRenderModel {
8
- activityHeight: number;
9
- footerText: string;
10
- header: {
11
- accessMode: string;
12
- contextEnabled: boolean;
13
- conversation: string;
14
- model: string;
15
- pendingLocalLabel: string;
16
- preset: string;
17
- profile: string;
18
- renderMode: RenderMode;
19
- workdir: string;
20
- };
21
- input: {
22
- busy: boolean;
23
- draft: string;
24
- fullAccess: boolean;
25
- label: string;
26
- waitingText: string;
27
- };
28
- terminalColumns: number;
29
- terminalRows: number;
30
- transcript: TranscriptViewModel;
31
- transcriptWidth: number;
32
- viewportHeight: number;
33
- visibleActivities: WorkbenchState["activities"];
34
- }
35
- export interface BuildWorkbenchRenderModelInput {
36
- draft: string;
37
- profileName: string;
38
- spinnerFrame: number;
39
- state: WorkbenchState;
40
- transcriptOffset: number;
41
- viewport: Partial<WorkbenchRendererViewport>;
42
- workdirFallback: string;
43
- }
44
- export declare function buildWorkbenchRenderModel(input: BuildWorkbenchRenderModelInput): WorkbenchRenderModel;
45
- export declare function pendingLocalLabel(state: WorkbenchState): string;
46
- export declare function busySpinner(frame: number): string;
@@ -1,61 +0,0 @@
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
- }
@@ -1,12 +0,0 @@
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;
@@ -1,57 +0,0 @@
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
- }
@@ -1,27 +0,0 @@
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;
@@ -1,42 +0,0 @@
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
- getShellIsolation: () => engine.snapshot().shellIsolation,
19
- });
20
- const runtime = createWorkbenchRuntimeController({ dispatch: engine.dispatch });
21
- const turn = createWorkbenchTurnController({
22
- baseOptions: options.baseOptions,
23
- dispatch: engine.dispatch,
24
- engine,
25
- flushTextDeltaBuffer: runtime.flushTextDeltaBuffer,
26
- getState: engine.snapshot,
27
- runRuntimeEffects: runtime.runEffects,
28
- });
29
- return {
30
- conversation: createWorkbenchConversationController(),
31
- engine,
32
- input: createWorkbenchInputController(),
33
- lifecycle: createWorkbenchLifecycleController({ authController: options.authController }),
34
- local,
35
- runtime,
36
- settings: createWorkbenchSettingsController(),
37
- turn,
38
- };
39
- }
40
- export function sessionState(session) {
41
- return session.engine.snapshot();
42
- }