@agent-api/cli 0.1.2 → 0.2.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/dist/agent/runner.d.ts +2 -0
- package/dist/agent/runner.js +3 -0
- package/dist/config.d.ts +24 -2
- package/dist/config.js +101 -11
- package/dist/conversation/index.js +8 -8
- package/dist/index.js +3 -2
- package/dist/runtime/index.d.ts +2 -2
- package/dist/runtime/index.js +94 -2
- package/dist/tui/ink/app.js +25 -1
- package/dist/tui/ink/components.d.ts +2 -1
- package/dist/tui/ink/components.js +4 -4
- package/dist/tui/workbench.d.ts +5 -2
- package/dist/tui/workbench.js +4 -1
- package/dist/workbench/command-controller.js +48 -0
- package/dist/workbench/isolator-installer.d.ts +29 -0
- package/dist/workbench/isolator-installer.js +208 -0
- package/dist/workbench/local-controller.d.ts +2 -0
- package/dist/workbench/local-controller.js +6 -1
- package/dist/workbench/session.js +3 -1
- package/dist/workbench/settings-controller.d.ts +27 -0
- package/dist/workbench/settings-controller.js +170 -2
- package/dist/workbench/shell-isolation.d.ts +20 -0
- package/dist/workbench/shell-isolation.js +13 -0
- package/dist/workbench/turn-controller.js +2 -0
- package/package.json +2 -2
package/dist/agent/runner.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type AgentResponse, type PresetToolCatalogClient, type ResponseStreamEvent, type Tool } from "@agent-api/sdk";
|
|
2
|
+
import type { ShellIsolationPreferences } from "../workbench/shell-isolation.js";
|
|
2
3
|
export interface AgentRunOptions {
|
|
3
4
|
profile?: string;
|
|
4
5
|
promptParts: string[];
|
|
@@ -19,6 +20,7 @@ export interface AgentRunOptions {
|
|
|
19
20
|
maxContextFiles?: number;
|
|
20
21
|
maxContextBytes?: number;
|
|
21
22
|
accessMode?: WorkdirAccessMode;
|
|
23
|
+
shellIsolation?: ShellIsolationPreferences;
|
|
22
24
|
abortSignal?: AbortSignal;
|
|
23
25
|
}
|
|
24
26
|
export type WorkdirAccessMode = "off" | "approval" | "full";
|
package/dist/agent/runner.js
CHANGED
|
@@ -4,6 +4,7 @@ import { createLocalShellToolRegistry, createLocalWorkdirToolRegistry, localShel
|
|
|
4
4
|
import { resolvePreviousResponseID, updateConversation } from "../conversation/index.js";
|
|
5
5
|
import { resolveRuntimeProfile } from "../profile.js";
|
|
6
6
|
import { buildWorkdirContextBlock, openWorkdir } from "../workdir/index.js";
|
|
7
|
+
import { localShellIsolationOptions } from "../workbench/shell-isolation.js";
|
|
7
8
|
const defaultCatalogCacheTTLMS = 10 * 60_000;
|
|
8
9
|
const presetCatalogCache = new Map();
|
|
9
10
|
const toolCatalogCache = new Map();
|
|
@@ -381,6 +382,7 @@ async function prepareLocalWorkdirTools(options) {
|
|
|
381
382
|
const shellRegistry = createLocalShellToolRegistry({
|
|
382
383
|
accessMode: localToolAccessMode(options),
|
|
383
384
|
workdir: service.workdir,
|
|
385
|
+
...localShellIsolationOptions(options.shellIsolation),
|
|
384
386
|
});
|
|
385
387
|
return {
|
|
386
388
|
registry: combineLocalToolRegistries(registry, shellRegistry),
|
|
@@ -389,6 +391,7 @@ async function prepareLocalWorkdirTools(options) {
|
|
|
389
391
|
localShellToolInstructions({
|
|
390
392
|
accessMode: localToolAccessMode(options),
|
|
391
393
|
cwd: service.workdir.root,
|
|
394
|
+
...localShellIsolationOptions(options.shellIsolation),
|
|
392
395
|
}),
|
|
393
396
|
"Use local_workdir for selected local workdir operations. Prefer summarize/list/search/grep before read/read_lines. Prefer preview_edits/apply_edits for source edits. Use local_shell for command/process tasks. In approval mode, local actions return requires_approval and must be explained to the user instead of retried blindly.",
|
|
394
397
|
].join("\n\n"),
|
package/dist/config.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import type { ShellIsolationMode, ShellIsolationPreferences } from "./workbench/shell-isolation.js";
|
|
1
2
|
export declare const defaultBaseURL = "https://api.agentsway.dev";
|
|
2
3
|
export declare const configFile = "profiles.json";
|
|
4
|
+
export declare const appConfigurationFile = "configuration.json";
|
|
5
|
+
export declare const conversationsFile = "conversations.json";
|
|
3
6
|
export type AuthProfile = {
|
|
4
7
|
type: "api_key";
|
|
5
8
|
apiKey: string;
|
|
@@ -20,8 +23,6 @@ export interface Profile {
|
|
|
20
23
|
export interface CLIConfig {
|
|
21
24
|
activeProfile: string;
|
|
22
25
|
profiles: Record<string, Profile>;
|
|
23
|
-
conversations: Record<string, ConversationState>;
|
|
24
|
-
workbench: WorkbenchPreferences;
|
|
25
26
|
}
|
|
26
27
|
export interface ConversationState {
|
|
27
28
|
name: string;
|
|
@@ -31,14 +32,35 @@ export interface ConversationState {
|
|
|
31
32
|
}
|
|
32
33
|
export interface WorkbenchPreferences {
|
|
33
34
|
defaultPreset?: string | null;
|
|
35
|
+
isolation?: ShellIsolationPreferences;
|
|
36
|
+
}
|
|
37
|
+
export interface AppConfiguration {
|
|
38
|
+
workbench: WorkbenchPreferences;
|
|
39
|
+
}
|
|
40
|
+
export interface ConversationConfiguration {
|
|
41
|
+
conversations: Record<string, ConversationState>;
|
|
34
42
|
}
|
|
35
43
|
export declare function loadConfig(): Promise<CLIConfig>;
|
|
36
44
|
export declare function saveConfig(config: CLIConfig): Promise<void>;
|
|
45
|
+
export declare function loadAppConfiguration(): Promise<AppConfiguration>;
|
|
46
|
+
export declare function saveAppConfiguration(config: AppConfiguration): Promise<void>;
|
|
47
|
+
export declare function loadConversationConfiguration(): Promise<ConversationConfiguration>;
|
|
48
|
+
export declare function saveConversationConfiguration(config: ConversationConfiguration): Promise<void>;
|
|
37
49
|
export declare function upsertProfile(profile: Omit<Profile, "createdAt" | "updatedAt">): Promise<Profile>;
|
|
38
50
|
export declare function activeProfile(profileName?: string): Promise<Profile>;
|
|
39
51
|
export declare function emptyConfig(): CLIConfig;
|
|
52
|
+
export declare function emptyAppConfiguration(): AppConfiguration;
|
|
53
|
+
export declare function emptyConversationConfiguration(): ConversationConfiguration;
|
|
40
54
|
export declare function loadWorkbenchPreferences(): Promise<WorkbenchPreferences>;
|
|
41
55
|
export declare function updateWorkbenchPreferences(patch: {
|
|
42
56
|
defaultPreset?: string | null | undefined;
|
|
57
|
+
isolation?: {
|
|
58
|
+
mode?: ShellIsolationMode | null | undefined;
|
|
59
|
+
executablePath?: string | null | undefined;
|
|
60
|
+
version?: string | null | undefined;
|
|
61
|
+
sourceURL?: string | null | undefined;
|
|
62
|
+
sha256?: string | null | undefined;
|
|
63
|
+
installSkipped?: boolean | null | undefined;
|
|
64
|
+
};
|
|
43
65
|
}): Promise<WorkbenchPreferences>;
|
|
44
66
|
export declare function redactSecret(value: string): string;
|
package/dist/config.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { runtime } from "./runtime/index.js";
|
|
2
|
+
import { ensureRuntime, runtime } from "./runtime/index.js";
|
|
3
3
|
export const defaultBaseURL = "https://api.agentsway.dev";
|
|
4
4
|
export const configFile = "profiles.json";
|
|
5
|
+
export const appConfigurationFile = "configuration.json";
|
|
6
|
+
export const conversationsFile = "conversations.json";
|
|
5
7
|
const authProfileSchema = z.discriminatedUnion("type", [
|
|
6
8
|
z.object({
|
|
7
9
|
type: z.literal("api_key"),
|
|
@@ -30,15 +32,28 @@ const conversationSchema = z.object({
|
|
|
30
32
|
});
|
|
31
33
|
const workbenchPreferencesSchema = z.object({
|
|
32
34
|
defaultPreset: z.string().nullable().optional(),
|
|
35
|
+
isolation: z.object({
|
|
36
|
+
mode: z.enum(["none", "auto", "required"]).optional(),
|
|
37
|
+
executablePath: z.string().nullable().optional(),
|
|
38
|
+
version: z.string().nullable().optional(),
|
|
39
|
+
sourceURL: z.string().nullable().optional(),
|
|
40
|
+
sha256: z.string().nullable().optional(),
|
|
41
|
+
installSkipped: z.boolean().nullable().optional(),
|
|
42
|
+
}).optional(),
|
|
33
43
|
}).default({});
|
|
34
44
|
const cliConfigSchema = z.object({
|
|
35
45
|
activeProfile: z.string().default("default"),
|
|
36
46
|
profiles: z.record(z.string(), profileSchema).default({}),
|
|
37
47
|
conversations: z.record(z.string(), conversationSchema).default({}),
|
|
48
|
+
});
|
|
49
|
+
const appConfigurationSchema = z.object({
|
|
38
50
|
workbench: workbenchPreferencesSchema,
|
|
39
51
|
});
|
|
52
|
+
const conversationConfigurationSchema = z.object({
|
|
53
|
+
conversations: z.record(z.string(), conversationSchema).default({}),
|
|
54
|
+
});
|
|
40
55
|
export async function loadConfig() {
|
|
41
|
-
await
|
|
56
|
+
await ensureRuntime();
|
|
42
57
|
const loaded = await runtime.config.read(configFile, emptyConfig());
|
|
43
58
|
const parsed = cliConfigSchema.safeParse(loaded);
|
|
44
59
|
if (!parsed.success) {
|
|
@@ -47,8 +62,37 @@ export async function loadConfig() {
|
|
|
47
62
|
return parsed.data;
|
|
48
63
|
}
|
|
49
64
|
export async function saveConfig(config) {
|
|
50
|
-
await
|
|
51
|
-
await runtime.config.write(configFile,
|
|
65
|
+
await ensureRuntime();
|
|
66
|
+
await runtime.config.write(configFile, {
|
|
67
|
+
activeProfile: config.activeProfile,
|
|
68
|
+
profiles: config.profiles,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
export async function loadAppConfiguration() {
|
|
72
|
+
await ensureRuntime();
|
|
73
|
+
const loaded = await runtime.config.read(appConfigurationFile, emptyAppConfiguration());
|
|
74
|
+
const parsed = appConfigurationSchema.safeParse(loaded);
|
|
75
|
+
if (!parsed.success) {
|
|
76
|
+
throw new Error(`Invalid app configuration: ${parsed.error.issues.map((issue) => issue.message).join("; ")}`);
|
|
77
|
+
}
|
|
78
|
+
return parsed.data;
|
|
79
|
+
}
|
|
80
|
+
export async function saveAppConfiguration(config) {
|
|
81
|
+
await ensureRuntime();
|
|
82
|
+
await runtime.config.write(appConfigurationFile, config);
|
|
83
|
+
}
|
|
84
|
+
export async function loadConversationConfiguration() {
|
|
85
|
+
await ensureRuntime();
|
|
86
|
+
const loaded = await runtime.config.read(conversationsFile, emptyConversationConfiguration());
|
|
87
|
+
const parsed = conversationConfigurationSchema.safeParse(loaded);
|
|
88
|
+
if (!parsed.success) {
|
|
89
|
+
throw new Error(`Invalid conversation configuration: ${parsed.error.issues.map((issue) => issue.message).join("; ")}`);
|
|
90
|
+
}
|
|
91
|
+
return parsed.data;
|
|
92
|
+
}
|
|
93
|
+
export async function saveConversationConfiguration(config) {
|
|
94
|
+
await ensureRuntime();
|
|
95
|
+
await runtime.config.write(conversationsFile, config);
|
|
52
96
|
}
|
|
53
97
|
export async function upsertProfile(profile) {
|
|
54
98
|
const config = await loadConfig();
|
|
@@ -74,15 +118,21 @@ export async function activeProfile(profileName) {
|
|
|
74
118
|
return profile;
|
|
75
119
|
}
|
|
76
120
|
export function emptyConfig() {
|
|
77
|
-
return { activeProfile: "default", profiles: {}
|
|
121
|
+
return { activeProfile: "default", profiles: {} };
|
|
122
|
+
}
|
|
123
|
+
export function emptyAppConfiguration() {
|
|
124
|
+
return { workbench: {} };
|
|
125
|
+
}
|
|
126
|
+
export function emptyConversationConfiguration() {
|
|
127
|
+
return { conversations: {} };
|
|
78
128
|
}
|
|
79
129
|
export async function loadWorkbenchPreferences() {
|
|
80
|
-
const
|
|
81
|
-
return
|
|
130
|
+
const appConfig = await loadAppConfiguration();
|
|
131
|
+
return appConfig.workbench;
|
|
82
132
|
}
|
|
83
133
|
export async function updateWorkbenchPreferences(patch) {
|
|
84
|
-
const
|
|
85
|
-
const next = { ...
|
|
134
|
+
const appConfig = await loadAppConfiguration();
|
|
135
|
+
const next = { ...appConfig.workbench };
|
|
86
136
|
if ("defaultPreset" in patch) {
|
|
87
137
|
if (patch.defaultPreset === undefined) {
|
|
88
138
|
delete next.defaultPreset;
|
|
@@ -100,10 +150,50 @@ export async function updateWorkbenchPreferences(patch) {
|
|
|
100
150
|
}
|
|
101
151
|
}
|
|
102
152
|
}
|
|
103
|
-
|
|
104
|
-
|
|
153
|
+
if ("isolation" in patch) {
|
|
154
|
+
next.isolation = updateIsolationPreferences(next.isolation, patch.isolation);
|
|
155
|
+
}
|
|
156
|
+
appConfig.workbench = next;
|
|
157
|
+
await saveAppConfiguration(appConfig);
|
|
105
158
|
return next;
|
|
106
159
|
}
|
|
160
|
+
function updateIsolationPreferences(current, patch) {
|
|
161
|
+
const next = { ...(current ?? {}) };
|
|
162
|
+
if (!patch)
|
|
163
|
+
return Object.keys(next).length > 0 ? next : undefined;
|
|
164
|
+
if ("mode" in patch) {
|
|
165
|
+
if (patch.mode === null || patch.mode === undefined) {
|
|
166
|
+
delete next.mode;
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
next.mode = patch.mode;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
for (const key of ["executablePath", "version", "sourceURL", "sha256"]) {
|
|
173
|
+
if (key in patch) {
|
|
174
|
+
const value = patch[key];
|
|
175
|
+
if (value === undefined || value === null) {
|
|
176
|
+
delete next[key];
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
const trimmed = value.trim();
|
|
180
|
+
if (trimmed)
|
|
181
|
+
next[key] = trimmed;
|
|
182
|
+
else
|
|
183
|
+
delete next[key];
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if ("installSkipped" in patch) {
|
|
188
|
+
if (patch.installSkipped === undefined || patch.installSkipped === null) {
|
|
189
|
+
delete next.installSkipped;
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
next.installSkipped = patch.installSkipped;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return Object.keys(next).length > 0 ? next : undefined;
|
|
196
|
+
}
|
|
107
197
|
export function redactSecret(value) {
|
|
108
198
|
if (value.length <= 10)
|
|
109
199
|
return "***";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { activeProfile,
|
|
1
|
+
import { activeProfile, loadConversationConfiguration, saveConversationConfiguration } from "../config.js";
|
|
2
2
|
export function conversationKey(profile, name) {
|
|
3
3
|
return `${profile}:${name}`;
|
|
4
4
|
}
|
|
@@ -10,32 +10,32 @@ export async function resolvePreviousResponseID(options) {
|
|
|
10
10
|
if (!options.continueConversation)
|
|
11
11
|
return undefined;
|
|
12
12
|
const profile = await activeProfile(options.profile);
|
|
13
|
-
const config = await
|
|
13
|
+
const config = await loadConversationConfiguration();
|
|
14
14
|
return config.conversations[conversationKey(profile.name, options.conversation)]?.previousResponseId;
|
|
15
15
|
}
|
|
16
16
|
export async function updateConversation(options, responseID) {
|
|
17
17
|
if (!options.conversation)
|
|
18
18
|
return;
|
|
19
19
|
const profile = await activeProfile(options.profile);
|
|
20
|
-
const config = await
|
|
20
|
+
const config = await loadConversationConfiguration();
|
|
21
21
|
config.conversations[conversationKey(profile.name, options.conversation)] = {
|
|
22
22
|
name: options.conversation,
|
|
23
23
|
profile: profile.name,
|
|
24
24
|
previousResponseId: responseID,
|
|
25
25
|
updatedAt: Math.floor(Date.now() / 1000),
|
|
26
26
|
};
|
|
27
|
-
await
|
|
27
|
+
await saveConversationConfiguration(config);
|
|
28
28
|
}
|
|
29
29
|
export async function listConversations(profileName) {
|
|
30
30
|
const profile = await activeProfile(profileName);
|
|
31
|
-
const config = await
|
|
31
|
+
const config = await loadConversationConfiguration();
|
|
32
32
|
return Object.values(config.conversations)
|
|
33
33
|
.filter((conversation) => conversation.profile === profile.name)
|
|
34
34
|
.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
35
35
|
}
|
|
36
36
|
export async function getConversation(name, profileName) {
|
|
37
37
|
const profile = await activeProfile(profileName);
|
|
38
|
-
const config = await
|
|
38
|
+
const config = await loadConversationConfiguration();
|
|
39
39
|
const conversation = config.conversations[conversationKey(profile.name, name)];
|
|
40
40
|
if (!conversation)
|
|
41
41
|
throw new Error(`Conversation not found: ${name}`);
|
|
@@ -43,9 +43,9 @@ export async function getConversation(name, profileName) {
|
|
|
43
43
|
}
|
|
44
44
|
export async function deleteConversation(name, profileName) {
|
|
45
45
|
const profile = await activeProfile(profileName);
|
|
46
|
-
const config = await
|
|
46
|
+
const config = await loadConversationConfiguration();
|
|
47
47
|
delete config.conversations[conversationKey(profile.name, name)];
|
|
48
|
-
await
|
|
48
|
+
await saveConversationConfiguration(config);
|
|
49
49
|
}
|
|
50
50
|
export function conversationSummary(conversation) {
|
|
51
51
|
const updated = new Date(conversation.updatedAt * 1000).toISOString();
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import React from "react";
|
|
|
7
7
|
import { conversationSummary, deleteConversation, getConversation, listConversations, runAgent } from "./agent.js";
|
|
8
8
|
import { normalizeChatOptions } from "./chat-options.js";
|
|
9
9
|
import { ChatApp } from "./tui/chat.js";
|
|
10
|
-
import { activeProfile, loadConfig, redactSecret } from "./config.js";
|
|
10
|
+
import { activeProfile, loadConfig, loadConversationConfiguration, redactSecret } from "./config.js";
|
|
11
11
|
import { cliVersion, runtime } from "./runtime/index.js";
|
|
12
12
|
import { openWorkdir } from "./workdir/index.js";
|
|
13
13
|
import { deleteProfile, listProfiles, loginWithAPIKey, loginWithBrowser, profileSummary, resolveRuntimeProfile, useProfile, } from "./profile.js";
|
|
@@ -64,12 +64,13 @@ program
|
|
|
64
64
|
.description("Print local CLI diagnostics")
|
|
65
65
|
.action(async () => {
|
|
66
66
|
const config = await loadConfig();
|
|
67
|
+
const conversations = await loadConversationConfiguration();
|
|
67
68
|
const { profiles } = await listProfiles();
|
|
68
69
|
console.log(JSON.stringify({
|
|
69
70
|
version: cliVersion,
|
|
70
71
|
activeProfile: config.activeProfile,
|
|
71
72
|
profileCount: profiles.length,
|
|
72
|
-
conversationCount: Object.keys(
|
|
73
|
+
conversationCount: Object.keys(conversations.conversations).length,
|
|
73
74
|
configDir: runtime.dirs.config,
|
|
74
75
|
dataDir: runtime.dirs.data,
|
|
75
76
|
node: process.version,
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export declare const cliName = "agent-
|
|
1
|
+
export declare const cliName = "agent-tui";
|
|
2
2
|
export declare const cliAuthor = "AgentsWay";
|
|
3
|
-
export declare const cliVersion = "0.
|
|
3
|
+
export declare const cliVersion = "0.2.0";
|
|
4
4
|
export declare const runtime: import("@agent-api/sdk/local").LocalRuntime;
|
|
5
5
|
export declare function ensureRuntime(): Promise<import("@agent-api/sdk/local").LocalRuntime>;
|
package/dist/runtime/index.js
CHANGED
|
@@ -1,12 +1,104 @@
|
|
|
1
1
|
import { createLocalRuntime } from "@agent-api/sdk/local";
|
|
2
|
-
|
|
2
|
+
import { cp, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
export const cliName = "agent-tui";
|
|
3
5
|
export const cliAuthor = "AgentsWay";
|
|
4
|
-
export const cliVersion = "0.
|
|
6
|
+
export const cliVersion = "0.2.0";
|
|
7
|
+
const legacyCliName = "agent-api-cli";
|
|
5
8
|
export const runtime = createLocalRuntime({
|
|
6
9
|
appName: cliName,
|
|
7
10
|
appAuthor: cliAuthor,
|
|
8
11
|
});
|
|
12
|
+
const legacyRuntime = createLocalRuntime({
|
|
13
|
+
appName: legacyCliName,
|
|
14
|
+
appAuthor: cliAuthor,
|
|
15
|
+
});
|
|
16
|
+
let migrationPromise = null;
|
|
9
17
|
export async function ensureRuntime() {
|
|
18
|
+
migrationPromise ??= migrateLegacyRuntime();
|
|
19
|
+
await migrationPromise;
|
|
10
20
|
await runtime.ensure();
|
|
21
|
+
await splitMonolithicConfig();
|
|
11
22
|
return runtime;
|
|
12
23
|
}
|
|
24
|
+
async function migrateLegacyRuntime() {
|
|
25
|
+
for (const key of ["config", "data", "cache", "logs"]) {
|
|
26
|
+
await moveDirectoryIfNeeded(legacyRuntime.dirs[key], runtime.dirs[key]);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async function moveDirectoryIfNeeded(from, to) {
|
|
30
|
+
if (from === to || !(await isDirectory(from)) || await pathExists(to))
|
|
31
|
+
return;
|
|
32
|
+
await mkdir(path.dirname(to), { recursive: true });
|
|
33
|
+
try {
|
|
34
|
+
await rename(from, to);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (error?.code !== "EXDEV")
|
|
38
|
+
throw error;
|
|
39
|
+
await cp(from, to, { recursive: true, errorOnExist: false });
|
|
40
|
+
await rm(from, { recursive: true, force: true });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function splitMonolithicConfig() {
|
|
44
|
+
const profilesPath = path.join(runtime.dirs.config, "profiles.json");
|
|
45
|
+
const raw = await readJSONRecord(profilesPath);
|
|
46
|
+
if (!raw)
|
|
47
|
+
return;
|
|
48
|
+
let changed = false;
|
|
49
|
+
if ("workbench" in raw && !(await pathExists(path.join(runtime.dirs.config, "configuration.json")))) {
|
|
50
|
+
await writeJSON(path.join(runtime.dirs.config, "configuration.json"), { workbench: raw.workbench && typeof raw.workbench === "object" ? raw.workbench : {} });
|
|
51
|
+
}
|
|
52
|
+
if ("workbench" in raw) {
|
|
53
|
+
delete raw.workbench;
|
|
54
|
+
changed = true;
|
|
55
|
+
}
|
|
56
|
+
if ("conversations" in raw && !(await pathExists(path.join(runtime.dirs.config, "conversations.json")))) {
|
|
57
|
+
await writeJSON(path.join(runtime.dirs.config, "conversations.json"), {
|
|
58
|
+
conversations: raw.conversations && typeof raw.conversations === "object" ? raw.conversations : {},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if ("conversations" in raw) {
|
|
62
|
+
delete raw.conversations;
|
|
63
|
+
changed = true;
|
|
64
|
+
}
|
|
65
|
+
if (changed) {
|
|
66
|
+
await writeJSON(profilesPath, raw);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async function readJSONRecord(file) {
|
|
70
|
+
try {
|
|
71
|
+
const value = JSON.parse(await readFile(file, "utf8"));
|
|
72
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error?.code === "ENOENT")
|
|
76
|
+
return null;
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async function writeJSON(file, value) {
|
|
81
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
82
|
+
await writeFile(file, `${JSON.stringify(value, null, 2)}\n`);
|
|
83
|
+
}
|
|
84
|
+
async function pathExists(file) {
|
|
85
|
+
try {
|
|
86
|
+
await stat(file);
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (error?.code === "ENOENT")
|
|
91
|
+
return false;
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async function isDirectory(file) {
|
|
96
|
+
try {
|
|
97
|
+
return (await stat(file)).isDirectory();
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
if (error?.code === "ENOENT")
|
|
101
|
+
return false;
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
package/dist/tui/ink/app.js
CHANGED
|
@@ -25,6 +25,7 @@ function AuthenticatedChatApp({ options }) {
|
|
|
25
25
|
}
|
|
26
26
|
const authGateController = authGateControllerRef.current;
|
|
27
27
|
const [currentProfile, setCurrentProfile] = useState(options.profile || "default");
|
|
28
|
+
const [authCursorVisible, setAuthCursorVisible] = useState(true);
|
|
28
29
|
const [auth, setAuth] = useState(() => authGateController.initialState({
|
|
29
30
|
apiKey: process.env.AGENT_API_KEY || "",
|
|
30
31
|
baseURL: process.env.AGENT_API_BASE_URL || defaultBaseURL,
|
|
@@ -44,6 +45,16 @@ function AuthenticatedChatApp({ options }) {
|
|
|
44
45
|
mounted = false;
|
|
45
46
|
};
|
|
46
47
|
}, [authGateController, options.profile]);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (!isAuthInputStatus(auth.status)) {
|
|
50
|
+
setAuthCursorVisible(true);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const interval = setInterval(() => {
|
|
54
|
+
setAuthCursorVisible((visible) => !visible);
|
|
55
|
+
}, 500);
|
|
56
|
+
return () => clearInterval(interval);
|
|
57
|
+
}, [auth.status]);
|
|
47
58
|
useInput((input, key) => {
|
|
48
59
|
const result = authGateController.handleInput(input, key, auth);
|
|
49
60
|
if (result.state !== auth)
|
|
@@ -87,7 +98,14 @@ function AuthenticatedChatApp({ options }) {
|
|
|
87
98
|
setAuth((current) => authGateController.requestSwitchProfile(current, currentProfile, name));
|
|
88
99
|
}, options: { ...options, profile: currentProfile }, profileName: currentProfile, authController: authController }));
|
|
89
100
|
}
|
|
90
|
-
return _jsx(InkAuthGate, { state: auth });
|
|
101
|
+
return _jsx(InkAuthGate, { cursorVisible: authCursorVisible, state: auth });
|
|
102
|
+
}
|
|
103
|
+
function isAuthInputStatus(status) {
|
|
104
|
+
return status === "api_profile"
|
|
105
|
+
|| status === "api_base_url"
|
|
106
|
+
|| status === "api_key"
|
|
107
|
+
|| status === "browser_profile"
|
|
108
|
+
|| status === "browser_base_url";
|
|
91
109
|
}
|
|
92
110
|
function WorkbenchApp({ authController, onLogin, onLogout, onDeleteProfile, onSwitchProfile, options, profileName, }) {
|
|
93
111
|
const app = useApp();
|
|
@@ -167,6 +185,12 @@ function WorkbenchApp({ authController, onLogin, onLogout, onDeleteProfile, onSw
|
|
|
167
185
|
if (!mounted)
|
|
168
186
|
return;
|
|
169
187
|
dispatch({ type: "settings.set", settings });
|
|
188
|
+
if (settings.activity) {
|
|
189
|
+
dispatch({ type: "activity.add", level: "success", text: settings.activity });
|
|
190
|
+
}
|
|
191
|
+
if (settings.warning) {
|
|
192
|
+
dispatch({ type: "activity.add", level: "warning", text: settings.warning });
|
|
193
|
+
}
|
|
170
194
|
})
|
|
171
195
|
.catch((error) => {
|
|
172
196
|
if (!mounted)
|
|
@@ -5,6 +5,7 @@ export declare function InkWorkbenchScreen({ renderModel, spinnerFrame, }: {
|
|
|
5
5
|
renderModel: WorkbenchRenderModel;
|
|
6
6
|
spinnerFrame: number;
|
|
7
7
|
}): React.JSX.Element;
|
|
8
|
-
export declare function InkAuthGate({ state }: {
|
|
8
|
+
export declare function InkAuthGate({ cursorVisible, state }: {
|
|
9
|
+
cursorVisible: boolean;
|
|
9
10
|
state: AuthGateState;
|
|
10
11
|
}): React.JSX.Element;
|
|
@@ -6,11 +6,11 @@ import { activityColor, } from "../workbench.js";
|
|
|
6
6
|
export function InkWorkbenchScreen({ renderModel, spinnerFrame, }) {
|
|
7
7
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Header, { contextEnabled: renderModel.header.contextEnabled, conversation: renderModel.header.conversation, model: renderModel.header.model, accessMode: renderModel.header.accessMode, pendingLocalLabel: renderModel.header.pendingLocalLabel, preset: renderModel.header.preset, profile: renderModel.header.profile, renderMode: renderModel.header.renderMode, workdir: renderModel.header.workdir }), _jsxs(Box, { marginTop: 1, height: renderModel.viewportHeight, children: [_jsxs(Box, { flexDirection: "column", width: "72%", paddingRight: 1, children: [renderModel.transcript.visibleLines.map((line) => (_jsx(Text, { bold: line.bold, color: line.color, inverse: line.inverse, wrap: "truncate", children: line.text || " " }, line.id))), renderModel.transcript.visibleLines.length === 0 && _jsx(Text, { color: "gray", children: "No transcript lines." })] }), _jsxs(Box, { flexDirection: "column", width: "28%", height: renderModel.activityHeight, borderStyle: "single", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { bold: true, wrap: "truncate", children: "Activity" }), renderModel.visibleActivities.map((activity) => (_jsxs(Text, { color: activityColor(activity.level), wrap: "truncate", children: [new Date(activity.timestamp).toLocaleTimeString(), " ", activity.text] }, activity.id)))] })] }), _jsxs(Box, { borderStyle: "single", borderColor: renderModel.input.busy ? "yellow" : "green", paddingX: 1, children: [renderModel.input.fullAccess && (_jsx(Text, { color: "red", bold: true, inverse: true, children: "FULL ACCESS" })), renderModel.input.fullAccess && _jsx(Text, { children: " " }), _jsxs(Text, { color: renderModel.input.busy ? "yellow" : "green", children: [renderModel.input.label, " "] }), renderModel.input.busy ? (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: "yellow", children: busySpinner(spinnerFrame) }), " ", renderModel.input.waitingText] })) : (_jsxs(Text, { wrap: "truncate", children: [renderModel.input.draft, _jsx(Cursor, { visible: true })] }))] }), _jsx(Box, { paddingX: 1, children: _jsx(Text, { color: "gray", wrap: "truncate", children: renderModel.footerText }) })] }));
|
|
8
8
|
}
|
|
9
|
-
export function InkAuthGate({ state }) {
|
|
10
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { borderStyle: "round", borderColor: "cyan", paddingX: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Agent API Workbench" }), _jsx(Text, { color: "gray", children: "Authentication required before starting the conversation UI." })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: state.error ? "red" : "gray", children: state.error || state.message }), state.status === "checking" && _jsx(Text, { color: "yellow", children: "Checking..." }), state.status === "select" && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [authMethods.map((method, index) => (_jsxs(Text, { color: index === state.selectedMethod ? "green" : "gray", children: [index === state.selectedMethod ? "›" : " ", " ", method.label, " - ", method.description] }, method.method))), _jsx(Text, { color: "gray", children: "Use \u2191/\u2193 and Enter." })] })), state.status === "api_profile" && _jsx(AuthPrompt, { label: "Profile", value: state.profile }), state.status === "api_base_url" && _jsx(AuthPrompt, { label: "Base URL", value: state.baseURL }), state.status === "api_key" && _jsx(AuthPrompt, { label: "API key", value: state.apiKey ? "•".repeat(Math.min(state.apiKey.length, 32)) : "" }), state.status === "browser_profile" && _jsx(AuthPrompt, { label: "Profile", value: state.profile }), state.status === "browser_base_url" && _jsx(AuthPrompt, { label: "Base URL", value: state.baseURL }), state.status === "browser_waiting" && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [state.browserURL && _jsxs(Text, { children: ["URL: ", state.browserURL] }), state.browserCode && _jsxs(Text, { children: ["Code: ", state.browserCode] }), _jsx(Text, { color: "yellow", children: "Waiting for browser approval..." })] }))] })] }));
|
|
9
|
+
export function InkAuthGate({ cursorVisible, state }) {
|
|
10
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { borderStyle: "round", borderColor: "cyan", paddingX: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Agent API Workbench" }), _jsx(Text, { color: "gray", children: "Authentication required before starting the conversation UI." })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: state.error ? "red" : "gray", children: state.error || state.message }), state.status === "checking" && _jsx(Text, { color: "yellow", children: "Checking..." }), state.status === "select" && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [authMethods.map((method, index) => (_jsxs(Text, { color: index === state.selectedMethod ? "green" : "gray", children: [index === state.selectedMethod ? "›" : " ", " ", method.label, " - ", method.description] }, method.method))), _jsx(Text, { color: "gray", children: "Use \u2191/\u2193 and Enter." })] })), state.status === "api_profile" && _jsx(AuthPrompt, { cursorVisible: cursorVisible, label: "Profile", value: state.profile }), state.status === "api_base_url" && _jsx(AuthPrompt, { cursorVisible: cursorVisible, label: "Base URL", value: state.baseURL }), state.status === "api_key" && _jsx(AuthPrompt, { cursorVisible: cursorVisible, label: "API key", value: state.apiKey ? "•".repeat(Math.min(state.apiKey.length, 32)) : "" }), state.status === "browser_profile" && _jsx(AuthPrompt, { cursorVisible: cursorVisible, label: "Profile", value: state.profile }), state.status === "browser_base_url" && _jsx(AuthPrompt, { cursorVisible: cursorVisible, label: "Base URL", value: state.baseURL }), state.status === "browser_waiting" && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [state.browserURL && _jsxs(Text, { children: ["URL: ", state.browserURL] }), state.browserCode && _jsxs(Text, { children: ["Code: ", state.browserCode] }), _jsx(Text, { color: "yellow", children: "Waiting for browser approval..." })] }))] })] }));
|
|
11
11
|
}
|
|
12
|
-
function AuthPrompt({ label, value }) {
|
|
13
|
-
return (_jsxs(Box, { borderStyle: "single", borderColor: "green", paddingX: 1, marginTop: 1, children: [_jsxs(Text, { color: "green", children: [label, ": "] }),
|
|
12
|
+
function AuthPrompt({ cursorVisible, label, value }) {
|
|
13
|
+
return (_jsxs(Box, { borderStyle: "single", borderColor: "green", paddingX: 1, marginTop: 1, children: [_jsxs(Text, { color: "green", children: [label, ": "] }), _jsxs(Text, { children: [value, _jsx(Cursor, { visible: cursorVisible })] })] }));
|
|
14
14
|
}
|
|
15
15
|
function Header({ contextEnabled, conversation, accessMode, model, pendingLocalLabel, preset, profile, renderMode, workdir, }) {
|
|
16
16
|
return (_jsxs(Box, { borderStyle: "round", borderColor: "cyan", paddingX: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Agent API Workbench" }), _jsxs(Text, { color: "gray", wrap: "truncate", children: ["profile=", profile, " conversation=", conversation, " preset=", preset, " model=", model] }), _jsxs(Text, { color: "gray", wrap: "truncate", children: ["workdir=", workdir, " access=", accessMode, " local_tools=", contextEnabled ? "on" : "off", " render=", renderMode, " pending=", pendingLocalLabel] })] }));
|
package/dist/tui/workbench.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { LocalToolApprovalRequest, WorkdirAccessMode } from "../agent.js";
|
|
2
|
+
import type { ShellIsolationPreferences } from "../workbench/shell-isolation.js";
|
|
2
3
|
export type WorkbenchRole = "user" | "assistant" | "system";
|
|
3
4
|
export interface WorkbenchMessage {
|
|
4
5
|
id: string;
|
|
@@ -38,6 +39,7 @@ export interface WorkbenchState {
|
|
|
38
39
|
runModel?: string;
|
|
39
40
|
renderMode: RenderMode;
|
|
40
41
|
defaultPreset?: string | null;
|
|
42
|
+
shellIsolation?: ShellIsolationPreferences;
|
|
41
43
|
}
|
|
42
44
|
export interface InputHistory {
|
|
43
45
|
record(value: string): void;
|
|
@@ -88,7 +90,7 @@ export type WorkbenchAction = {
|
|
|
88
90
|
name: string;
|
|
89
91
|
} | {
|
|
90
92
|
type: "settings.set";
|
|
91
|
-
settings: Partial<Pick<WorkbenchState, "runPreset" | "runModel" | "renderMode" | "defaultPreset">>;
|
|
93
|
+
settings: Partial<Pick<WorkbenchState, "runPreset" | "runModel" | "renderMode" | "defaultPreset" | "shellIsolation">>;
|
|
92
94
|
};
|
|
93
95
|
export type WorkbenchCommand = {
|
|
94
96
|
kind: "invalid";
|
|
@@ -114,7 +116,7 @@ export type WorkbenchCommand = {
|
|
|
114
116
|
kind: "auth_status";
|
|
115
117
|
} | {
|
|
116
118
|
kind: "config";
|
|
117
|
-
field?: "preset";
|
|
119
|
+
field?: "preset" | "isolation" | "isolator";
|
|
118
120
|
value?: string;
|
|
119
121
|
} | {
|
|
120
122
|
kind: "render";
|
|
@@ -171,6 +173,7 @@ export declare function createInitialWorkbenchState(options: {
|
|
|
171
173
|
model?: string;
|
|
172
174
|
renderMode?: RenderMode;
|
|
173
175
|
defaultPreset?: string | null;
|
|
176
|
+
shellIsolation?: ShellIsolationPreferences;
|
|
174
177
|
}): WorkbenchState;
|
|
175
178
|
export declare function createInputHistory(limit?: number): InputHistory;
|
|
176
179
|
export declare function workbenchReducer(state: WorkbenchState, action: WorkbenchAction): WorkbenchState;
|
package/dist/tui/workbench.js
CHANGED
|
@@ -18,6 +18,7 @@ export function createInitialWorkbenchState(options) {
|
|
|
18
18
|
runModel: options.model,
|
|
19
19
|
renderMode: options.renderMode ?? "markdown",
|
|
20
20
|
defaultPreset: options.defaultPreset,
|
|
21
|
+
shellIsolation: options.shellIsolation,
|
|
21
22
|
};
|
|
22
23
|
}
|
|
23
24
|
export function createInputHistory(limit = 100) {
|
|
@@ -188,7 +189,7 @@ export function parseWorkbenchCommand(input) {
|
|
|
188
189
|
const [field, ...valueParts] = rest;
|
|
189
190
|
if (!field)
|
|
190
191
|
return { kind: "config" };
|
|
191
|
-
if (field === "preset") {
|
|
192
|
+
if (field === "preset" || field === "isolation" || field === "isolator") {
|
|
192
193
|
return { kind: "config", field, value: valueParts.join(" ").trim() || undefined };
|
|
193
194
|
}
|
|
194
195
|
return { kind: "invalid", command: `${name} ${field}` };
|
|
@@ -290,6 +291,8 @@ export function helpText() {
|
|
|
290
291
|
"/transcript show a plain-text transcript preview",
|
|
291
292
|
"/export [file] save the plain-text transcript to a file",
|
|
292
293
|
"/config preset save default preset; use none/off for no preset, reset for built-in",
|
|
294
|
+
"/config isolation save shell isolation mode: none, auto, or required",
|
|
295
|
+
"/config isolator save agent-isolator path; use none/off to clear",
|
|
293
296
|
"/preset [name] show or set preset; use none/off to clear",
|
|
294
297
|
"/model [name] show or set explicit model; use auto/none/off to clear",
|
|
295
298
|
"/access [mode] show or set local tool access: off, approval, or full",
|