@getollie/cli 0.2.0 → 0.2.2
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/cli.js +272 -77
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +106 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -7,8 +7,12 @@ import { CommanderError } from "commander";
|
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
|
|
9
9
|
// src/config.ts
|
|
10
|
-
import { access, stat } from "fs/promises";
|
|
11
|
-
import { resolve } from "path";
|
|
10
|
+
import { access, realpath, stat } from "fs/promises";
|
|
11
|
+
import { posix as posix2, resolve as resolve2 } from "path";
|
|
12
|
+
|
|
13
|
+
// src/sandbox/mounts.ts
|
|
14
|
+
import { realpathSync, statSync } from "fs";
|
|
15
|
+
import { posix, resolve } from "path";
|
|
12
16
|
|
|
13
17
|
// src/utils/errors.ts
|
|
14
18
|
var CliError = class extends Error {
|
|
@@ -26,6 +30,68 @@ function getErrorMessage(error) {
|
|
|
26
30
|
return String(error);
|
|
27
31
|
}
|
|
28
32
|
|
|
33
|
+
// src/sandbox/mounts.ts
|
|
34
|
+
var SANDBOX_WORKSPACE_PATH = "/workspace";
|
|
35
|
+
function sandboxPathsOverlap(left, right) {
|
|
36
|
+
return left === right || left.startsWith(`${right}/`) || right.startsWith(`${left}/`);
|
|
37
|
+
}
|
|
38
|
+
function sandboxPathContains(parent, child) {
|
|
39
|
+
return parent === child || child.startsWith(`${parent}/`);
|
|
40
|
+
}
|
|
41
|
+
function validateSandboxPath(path, label = "Sandbox path") {
|
|
42
|
+
if (!posix.isAbsolute(path) || path.includes("\\") || path.includes("\0") || path.split("/").includes("..") || posix.normalize(path) !== path) {
|
|
43
|
+
throw new CliError(`${label} must be a normalized absolute sandbox path: ${path}`);
|
|
44
|
+
}
|
|
45
|
+
return path;
|
|
46
|
+
}
|
|
47
|
+
function validateMountPoint(mountPoint) {
|
|
48
|
+
validateSandboxPath(mountPoint, "Mount point");
|
|
49
|
+
if (mountPoint === "/") {
|
|
50
|
+
throw new CliError(`Mount point must be a normalized absolute sandbox path other than /: ${mountPoint}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function validateSandboxMounts(mounts, options = {}) {
|
|
54
|
+
const reservedMountPoint = options.reservedMountPoint === void 0 ? SANDBOX_WORKSPACE_PATH : options.reservedMountPoint;
|
|
55
|
+
const validated = [];
|
|
56
|
+
for (const mount of mounts) {
|
|
57
|
+
validateMountPoint(mount.mountPoint);
|
|
58
|
+
if (reservedMountPoint && sandboxPathsOverlap(mount.mountPoint, reservedMountPoint)) {
|
|
59
|
+
throw new CliError(`Mount point must be outside ${reservedMountPoint}: ${mount.mountPoint}`);
|
|
60
|
+
}
|
|
61
|
+
let hostPath;
|
|
62
|
+
try {
|
|
63
|
+
hostPath = realpathSync(resolve(mount.hostPath));
|
|
64
|
+
if (!statSync(hostPath).isDirectory()) throw new Error("not a directory");
|
|
65
|
+
} catch {
|
|
66
|
+
throw new CliError(
|
|
67
|
+
`Mount source host path does not exist or is not a directory: ${resolve(mount.hostPath)}`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
const conflict = validated.find(
|
|
71
|
+
(candidate) => sandboxPathsOverlap(candidate.mountPoint, mount.mountPoint)
|
|
72
|
+
);
|
|
73
|
+
if (conflict) {
|
|
74
|
+
throw new CliError(
|
|
75
|
+
`Mount points must not overlap: ${conflict.mountPoint} and ${mount.mountPoint}`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
validated.push({ hostPath, mountPoint: mount.mountPoint });
|
|
79
|
+
}
|
|
80
|
+
return validated;
|
|
81
|
+
}
|
|
82
|
+
function parseSandboxMounts(specs = [], options) {
|
|
83
|
+
const mounts = specs.map((spec) => {
|
|
84
|
+
const separator = spec.lastIndexOf(":");
|
|
85
|
+
if (separator <= 0 || separator === spec.length - 1) {
|
|
86
|
+
throw new CliError(
|
|
87
|
+
`Invalid mount ${JSON.stringify(spec)}. Expected <host-path>:<sandbox-path>`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return { hostPath: spec.slice(0, separator), mountPoint: spec.slice(separator + 1) };
|
|
91
|
+
});
|
|
92
|
+
return validateSandboxMounts(mounts, options);
|
|
93
|
+
}
|
|
94
|
+
|
|
29
95
|
// src/config.ts
|
|
30
96
|
var DEFAULT_PROVIDER = "openai";
|
|
31
97
|
var DEFAULT_MODEL = "gpt-5.6";
|
|
@@ -34,9 +100,7 @@ var DEFAULT_COMMAND_TIMEOUT_MS = 3e4;
|
|
|
34
100
|
var DEFAULT_MAX_OUTPUT_BYTES = 65536;
|
|
35
101
|
var DEFAULT_EXECUTOR = "none";
|
|
36
102
|
function parseNumber(name, value, fallback) {
|
|
37
|
-
if (value === void 0 || value === "")
|
|
38
|
-
return fallback;
|
|
39
|
-
}
|
|
103
|
+
if (value === void 0 || value === "") return fallback;
|
|
40
104
|
const parsed = typeof value === "number" ? value : Number.parseInt(value, 10);
|
|
41
105
|
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
42
106
|
throw new CliError(`${name} must be a non-negative integer`);
|
|
@@ -53,39 +117,82 @@ function parseExecutor(value) {
|
|
|
53
117
|
}
|
|
54
118
|
return executor;
|
|
55
119
|
}
|
|
56
|
-
async function
|
|
57
|
-
const
|
|
58
|
-
const stats = await stat(
|
|
120
|
+
async function resolveHostDirectory(value, label) {
|
|
121
|
+
const absolute = resolve2(value);
|
|
122
|
+
const stats = await stat(absolute).catch(() => null);
|
|
59
123
|
if (!stats?.isDirectory()) {
|
|
60
|
-
throw new CliError(
|
|
124
|
+
throw new CliError(`${label} host path does not exist or is not a directory: ${absolute}`);
|
|
61
125
|
}
|
|
62
|
-
await access(
|
|
126
|
+
await access(absolute);
|
|
127
|
+
return realpath(absolute);
|
|
128
|
+
}
|
|
129
|
+
async function resolveRunConfig(input = {}, env = process.env) {
|
|
130
|
+
const configuredHostCwd = input.cwd ?? env.OLLIE_CWD;
|
|
131
|
+
const configuredSandboxCwd = input.sandboxCwd ?? env.OLLIE_SANDBOX_CWD;
|
|
132
|
+
const usedImplicitCwd = configuredHostCwd === void 0 && configuredSandboxCwd === void 0;
|
|
133
|
+
const hostCwdValue = configuredHostCwd ?? (usedImplicitCwd ? process.cwd() : void 0);
|
|
134
|
+
const explicitMounts = parseSandboxMounts(input.mount, {
|
|
135
|
+
reservedMountPoint: hostCwdValue ? "/workspace" : null
|
|
136
|
+
});
|
|
137
|
+
let workspace;
|
|
138
|
+
let mounts;
|
|
139
|
+
let sandboxCwd;
|
|
140
|
+
if (hostCwdValue) {
|
|
141
|
+
workspace = {
|
|
142
|
+
hostPath: await resolveHostDirectory(hostCwdValue, "Working directory"),
|
|
143
|
+
mountPoint: "/workspace"
|
|
144
|
+
};
|
|
145
|
+
mounts = explicitMounts;
|
|
146
|
+
sandboxCwd = configuredSandboxCwd ?? "/workspace";
|
|
147
|
+
} else {
|
|
148
|
+
if (!configuredSandboxCwd) {
|
|
149
|
+
throw new CliError("Either --cwd or --sandbox-cwd must be specified.");
|
|
150
|
+
}
|
|
151
|
+
sandboxCwd = configuredSandboxCwd;
|
|
152
|
+
validateSandboxPath(sandboxCwd, "Sandbox cwd");
|
|
153
|
+
const containing2 = explicitMounts.find(
|
|
154
|
+
(mount) => sandboxPathContains(mount.mountPoint, sandboxCwd)
|
|
155
|
+
);
|
|
156
|
+
if (!containing2) {
|
|
157
|
+
const destinations = explicitMounts.map((mount) => mount.mountPoint).join(", ") || "(none)";
|
|
158
|
+
throw new CliError(
|
|
159
|
+
`Sandbox cwd ${sandboxCwd} is not contained by the primary workspace or any configured mount. Configured mount destinations: ${destinations}`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
workspace = containing2;
|
|
163
|
+
mounts = explicitMounts.filter((mount) => mount !== containing2);
|
|
164
|
+
}
|
|
165
|
+
validateSandboxPath(sandboxCwd, "Sandbox cwd");
|
|
166
|
+
const allMounts = [workspace, ...mounts];
|
|
167
|
+
const containing = allMounts.find((mount) => sandboxPathContains(mount.mountPoint, sandboxCwd));
|
|
168
|
+
if (!containing) {
|
|
169
|
+
const destinations = allMounts.map((mount) => mount.mountPoint).join(", ");
|
|
170
|
+
throw new CliError(
|
|
171
|
+
`Sandbox cwd ${sandboxCwd} is not contained by the primary workspace or any configured mount. Configured mount destinations: ${destinations}`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
const mappedRelative = posix2.relative(containing.mountPoint, sandboxCwd);
|
|
175
|
+
await resolveHostDirectory(resolve2(containing.hostPath, mappedRelative), "Sandbox cwd mapped");
|
|
176
|
+
const workspaceRelativeCwd = sandboxPathContains(workspace.mountPoint, sandboxCwd) ? posix2.relative(workspace.mountPoint, sandboxCwd) || "." : null;
|
|
63
177
|
const executor = parseExecutor(input.executor ?? env.OLLIE_EXECUTOR);
|
|
64
178
|
return {
|
|
65
|
-
cwd,
|
|
179
|
+
cwd: workspace.hostPath,
|
|
180
|
+
workspaceMountPoint: workspace.mountPoint,
|
|
181
|
+
sandboxCwd,
|
|
182
|
+
workspaceRelativeCwd,
|
|
183
|
+
mounts,
|
|
184
|
+
usedImplicitCwd,
|
|
66
185
|
provider: input.provider ?? env.OLLIE_PROVIDER ?? DEFAULT_PROVIDER,
|
|
67
186
|
model: input.model ?? env.OLLIE_MODEL ?? DEFAULT_MODEL,
|
|
68
187
|
executor,
|
|
69
|
-
maxSteps: clamp(
|
|
70
|
-
parseNumber("max steps", input.maxSteps ?? env.OLLIE_MAX_STEPS, DEFAULT_MAX_STEPS),
|
|
71
|
-
1,
|
|
72
|
-
100
|
|
73
|
-
),
|
|
188
|
+
maxSteps: clamp(parseNumber("max steps", input.maxSteps ?? env.OLLIE_MAX_STEPS, DEFAULT_MAX_STEPS), 1, 100),
|
|
74
189
|
commandTimeoutMs: clamp(
|
|
75
|
-
parseNumber(
|
|
76
|
-
"command timeout",
|
|
77
|
-
input.timeout ?? env.OLLIE_COMMAND_TIMEOUT_MS,
|
|
78
|
-
DEFAULT_COMMAND_TIMEOUT_MS
|
|
79
|
-
),
|
|
190
|
+
parseNumber("command timeout", input.timeout ?? env.OLLIE_COMMAND_TIMEOUT_MS, DEFAULT_COMMAND_TIMEOUT_MS),
|
|
80
191
|
1,
|
|
81
192
|
3e5
|
|
82
193
|
),
|
|
83
194
|
maxOutputBytes: clamp(
|
|
84
|
-
parseNumber(
|
|
85
|
-
"max output",
|
|
86
|
-
input.maxOutput ?? env.OLLIE_MAX_OUTPUT_BYTES,
|
|
87
|
-
DEFAULT_MAX_OUTPUT_BYTES
|
|
88
|
-
),
|
|
195
|
+
parseNumber("max output", input.maxOutput ?? env.OLLIE_MAX_OUTPUT_BYTES, DEFAULT_MAX_OUTPUT_BYTES),
|
|
89
196
|
1,
|
|
90
197
|
2e6
|
|
91
198
|
),
|
|
@@ -99,23 +206,23 @@ async function resolveRunConfig(input = {}, env = process.env) {
|
|
|
99
206
|
import { access as access2, stat as stat2 } from "fs/promises";
|
|
100
207
|
|
|
101
208
|
// src/sandbox/just-bash.ts
|
|
102
|
-
import { realpathSync, statSync } from "fs";
|
|
209
|
+
import { realpathSync as realpathSync2, statSync as statSync2 } from "fs";
|
|
103
210
|
import { Bash, InMemoryFs, MountableFs, ReadWriteFs } from "just-bash";
|
|
104
211
|
|
|
105
212
|
// src/sandbox/git/index.ts
|
|
106
|
-
import { posix as
|
|
213
|
+
import { posix as posix4 } from "path";
|
|
107
214
|
import { createTwoFilesPatch } from "diff";
|
|
108
215
|
import * as git from "isomorphic-git";
|
|
109
216
|
import { defineCommand } from "just-bash";
|
|
110
217
|
|
|
111
218
|
// src/sandbox/git/fs-adapter.ts
|
|
112
|
-
import { posix } from "path";
|
|
219
|
+
import { posix as posix3 } from "path";
|
|
113
220
|
var ROOT = "/workspace";
|
|
114
221
|
function safePath(path) {
|
|
115
222
|
if (path.includes("\0") || path.includes("\\")) {
|
|
116
223
|
throw Object.assign(new Error("Invalid virtual Git path"), { code: "EINVAL" });
|
|
117
224
|
}
|
|
118
|
-
const normalized =
|
|
225
|
+
const normalized = posix3.resolve(path);
|
|
119
226
|
if (normalized !== ROOT && !normalized.startsWith(`${ROOT}/`)) {
|
|
120
227
|
throw Object.assign(new Error("Git path is outside /workspace"), { code: "EACCES" });
|
|
121
228
|
}
|
|
@@ -135,7 +242,7 @@ function normalizeError(error) {
|
|
|
135
242
|
throw Object.assign(new Error("Virtual filesystem operation failed"), { code: "EIO" });
|
|
136
243
|
}
|
|
137
244
|
async function ensureParent(fs, path) {
|
|
138
|
-
const parent =
|
|
245
|
+
const parent = posix3.dirname(path);
|
|
139
246
|
if (!await fs.exists(parent)) await fs.mkdir(parent, { recursive: true });
|
|
140
247
|
}
|
|
141
248
|
function statShape(stat4) {
|
|
@@ -370,9 +477,9 @@ function virtualPath(ctx, input = ".") {
|
|
|
370
477
|
async function repositoryDir(ctx) {
|
|
371
478
|
let current = virtualPath(ctx);
|
|
372
479
|
while (current === WORKSPACE || current.startsWith(`${WORKSPACE}/`)) {
|
|
373
|
-
if (await ctx.fs.exists(
|
|
480
|
+
if (await ctx.fs.exists(posix4.join(current, ".git"))) return current;
|
|
374
481
|
if (current === WORKSPACE) break;
|
|
375
|
-
current =
|
|
482
|
+
current = posix4.dirname(current);
|
|
376
483
|
}
|
|
377
484
|
throw new Error("not a git repository (or any parent up to /workspace)");
|
|
378
485
|
}
|
|
@@ -455,7 +562,7 @@ async function writeGlobalConfig(ctx, values) {
|
|
|
455
562
|
([section, entries]) => `[${section}]
|
|
456
563
|
${entries.map(([key, value]) => ` ${key} = ${value}`).join("\n")}`
|
|
457
564
|
).join("\n");
|
|
458
|
-
await ctx.fs.mkdir(
|
|
565
|
+
await ctx.fs.mkdir(posix4.dirname(GLOBAL_CONFIG_PATH), { recursive: true });
|
|
459
566
|
await ctx.fs.writeFile(GLOBAL_CONFIG_PATH, content ? `${content}
|
|
460
567
|
` : "");
|
|
461
568
|
}
|
|
@@ -469,7 +576,7 @@ async function handleInit(context, args) {
|
|
|
469
576
|
throw new Error("cannot create repository directory");
|
|
470
577
|
});
|
|
471
578
|
await git.init({ fs: context.fs, dir, defaultBranch: branchOpt.value ?? "main" });
|
|
472
|
-
return ok(`Initialized empty Git repository in ${
|
|
579
|
+
return ok(`Initialized empty Git repository in ${posix4.join(dir, ".git")}/
|
|
473
580
|
`);
|
|
474
581
|
}
|
|
475
582
|
async function handleStatus(context, args) {
|
|
@@ -622,7 +729,7 @@ async function handleDiff(context, args) {
|
|
|
622
729
|
const changed = cachedFlag.enabled ? headStatus !== stageStatus : workdirStatus !== stageStatus;
|
|
623
730
|
if (!changed) continue;
|
|
624
731
|
const oldBytes = cachedFlag.enabled ? head && headStatus !== 0 ? (await git.readBlob({ fs: context.fs, dir, oid: head, filepath })).blob : empty : await readStage(filepath);
|
|
625
|
-
const newBytes = cachedFlag.enabled ? await readStage(filepath) : workdirStatus === 0 ? empty : await context.command.fs.readFileBuffer(
|
|
732
|
+
const newBytes = cachedFlag.enabled ? await readStage(filepath) : workdirStatus === 0 ? empty : await context.command.fs.readFileBuffer(posix4.join(dir, filepath));
|
|
626
733
|
patches.push(patch(filepath, oldBytes, newBytes));
|
|
627
734
|
}
|
|
628
735
|
return ok(patches.join(""));
|
|
@@ -689,7 +796,7 @@ async function handleCheckout(context, args) {
|
|
|
689
796
|
const oid = await git.resolveRef({ fs: context.fs, dir, ref });
|
|
690
797
|
for (const filepath of filepaths) {
|
|
691
798
|
const blob = await readCheckoutBlob(context, dir, oid, filepath);
|
|
692
|
-
await context.command.fs.writeFile(
|
|
799
|
+
await context.command.fs.writeFile(posix4.join(dir, filepath), blob);
|
|
693
800
|
}
|
|
694
801
|
return ok();
|
|
695
802
|
}
|
|
@@ -816,7 +923,7 @@ async function handleClone(context, args) {
|
|
|
816
923
|
const [rawUrl, destination, ...rest] = noCheckoutFlag.rest;
|
|
817
924
|
if (!rawUrl || rest.length) usage("clone requires a URL and optional directory");
|
|
818
925
|
const url = validateRemoteUrl(rawUrl);
|
|
819
|
-
const inferred =
|
|
926
|
+
const inferred = posix4.basename(new URL(url).pathname).replace(/\.git$/, "") || "repository";
|
|
820
927
|
const dir = virtualPath(context.command, destination ?? inferred);
|
|
821
928
|
const depth = positiveInteger(depthOpt.value, "--depth");
|
|
822
929
|
await git.clone({
|
|
@@ -980,34 +1087,49 @@ Supported commands: ${COMMANDS.join(", ")}
|
|
|
980
1087
|
}
|
|
981
1088
|
|
|
982
1089
|
// src/sandbox/just-bash.ts
|
|
983
|
-
var
|
|
1090
|
+
var SANDBOX_WORKSPACE_PATH2 = "/workspace";
|
|
984
1091
|
function canonicalizeWorkspaceRoot(workspaceRoot) {
|
|
985
|
-
const canonicalRoot =
|
|
986
|
-
if (!
|
|
1092
|
+
const canonicalRoot = realpathSync2(workspaceRoot);
|
|
1093
|
+
if (!statSync2(canonicalRoot).isDirectory()) {
|
|
987
1094
|
throw new Error("The sandbox workspace root must be a directory");
|
|
988
1095
|
}
|
|
989
1096
|
return canonicalRoot;
|
|
990
1097
|
}
|
|
991
|
-
function createWorkspaceFs(workspaceRoot) {
|
|
1098
|
+
function createWorkspaceFs(workspaceRoot, options) {
|
|
992
1099
|
const root = canonicalizeWorkspaceRoot(workspaceRoot);
|
|
1100
|
+
const workspaceMountPoint = options.workspaceMountPoint ?? SANDBOX_WORKSPACE_PATH2;
|
|
1101
|
+
validateSandboxPath(workspaceMountPoint, "Workspace mount point");
|
|
1102
|
+
const additionalMounts = validateSandboxMounts(options.mounts ?? [], {
|
|
1103
|
+
reservedMountPoint: workspaceMountPoint
|
|
1104
|
+
}).map((mount) => ({
|
|
1105
|
+
mountPoint: mount.mountPoint,
|
|
1106
|
+
filesystem: new ReadWriteFs({ root: mount.hostPath, allowSymlinks: false })
|
|
1107
|
+
}));
|
|
993
1108
|
return new MountableFs({
|
|
994
1109
|
base: new InMemoryFs(),
|
|
995
1110
|
mounts: [
|
|
996
1111
|
{
|
|
997
|
-
mountPoint:
|
|
1112
|
+
mountPoint: workspaceMountPoint,
|
|
998
1113
|
filesystem: new ReadWriteFs({ root, allowSymlinks: false })
|
|
999
|
-
}
|
|
1114
|
+
},
|
|
1115
|
+
...additionalMounts
|
|
1000
1116
|
]
|
|
1001
1117
|
});
|
|
1002
1118
|
}
|
|
1003
1119
|
function createJustBash(workspaceRoot, options = {}) {
|
|
1120
|
+
const workspaceMountPoint = options.workspaceMountPoint ?? SANDBOX_WORKSPACE_PATH2;
|
|
1121
|
+
const sandboxCwd = options.sandboxCwd ?? workspaceMountPoint;
|
|
1122
|
+
validateSandboxPath(sandboxCwd, "Sandbox cwd");
|
|
1123
|
+
const configuredMountPoints = [workspaceMountPoint, ...(options.mounts ?? []).map((m) => m.mountPoint)];
|
|
1124
|
+
if (!configuredMountPoints.some((mountPoint) => sandboxPathContains(mountPoint, sandboxCwd))) {
|
|
1125
|
+
throw new CliError(
|
|
1126
|
+
`Sandbox cwd ${sandboxCwd} is not contained by the primary workspace or any configured mount.`
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1004
1129
|
return new Bash({
|
|
1005
|
-
fs: createWorkspaceFs(workspaceRoot),
|
|
1006
|
-
cwd:
|
|
1007
|
-
env: {
|
|
1008
|
-
HOME: "/home/user",
|
|
1009
|
-
TERM: "xterm-256color"
|
|
1010
|
-
},
|
|
1130
|
+
fs: createWorkspaceFs(workspaceRoot, options),
|
|
1131
|
+
cwd: sandboxCwd,
|
|
1132
|
+
env: { HOME: "/home/user", TERM: "xterm-256color" },
|
|
1011
1133
|
python: true,
|
|
1012
1134
|
javascript: true,
|
|
1013
1135
|
customCommands: options.git === false ? [] : [createGitCommand(options.git)],
|
|
@@ -1104,7 +1226,11 @@ async function runExecCommand(cwd, command, options, signal) {
|
|
|
1104
1226
|
const combinedSignal = combineSignals([signal, timeout.signal]);
|
|
1105
1227
|
const timedOut = () => timeout.signal.aborted && !signal?.aborted;
|
|
1106
1228
|
try {
|
|
1107
|
-
const bash = createJustBash(cwd
|
|
1229
|
+
const bash = createJustBash(cwd, {
|
|
1230
|
+
workspaceMountPoint: options.workspaceMountPoint,
|
|
1231
|
+
sandboxCwd: options.sandboxCwd,
|
|
1232
|
+
mounts: options.mounts
|
|
1233
|
+
});
|
|
1108
1234
|
const result = await bash.exec(command, { env: options.env, signal: combinedSignal });
|
|
1109
1235
|
return {
|
|
1110
1236
|
...result,
|
|
@@ -1353,7 +1479,7 @@ var ExecutorController = class {
|
|
|
1353
1479
|
// src/executor/daytona.ts
|
|
1354
1480
|
import { randomUUID } from "crypto";
|
|
1355
1481
|
import { mkdir, readdir, readFile, rm, stat as stat3, writeFile } from "fs/promises";
|
|
1356
|
-
import { dirname, isAbsolute, relative, resolve as
|
|
1482
|
+
import { dirname, isAbsolute, relative, resolve as resolve3, sep } from "path";
|
|
1357
1483
|
import {
|
|
1358
1484
|
Daytona,
|
|
1359
1485
|
DaytonaProcessExecutionTimeoutError
|
|
@@ -1388,7 +1514,7 @@ async function listLocalFiles(root) {
|
|
|
1388
1514
|
const files = /* @__PURE__ */ new Map();
|
|
1389
1515
|
async function visit(directory) {
|
|
1390
1516
|
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
1391
|
-
const absolutePath =
|
|
1517
|
+
const absolutePath = resolve3(directory, entry.name);
|
|
1392
1518
|
if (entry.isDirectory()) {
|
|
1393
1519
|
await visit(absolutePath);
|
|
1394
1520
|
} else if (entry.isFile()) {
|
|
@@ -1403,12 +1529,14 @@ async function listLocalFiles(root) {
|
|
|
1403
1529
|
var DaytonaSandbox = class {
|
|
1404
1530
|
#sandbox;
|
|
1405
1531
|
#workspacePath;
|
|
1532
|
+
#workingDirectory;
|
|
1406
1533
|
#commandTimeoutMs;
|
|
1407
1534
|
#signal;
|
|
1408
1535
|
#disposed = false;
|
|
1409
1536
|
constructor(sandbox, options) {
|
|
1410
1537
|
this.#sandbox = sandbox;
|
|
1411
1538
|
this.#workspacePath = options.workspacePath;
|
|
1539
|
+
this.#workingDirectory = options.workingDirectory ?? options.workspacePath;
|
|
1412
1540
|
this.#commandTimeoutMs = options.commandTimeoutMs;
|
|
1413
1541
|
this.#signal = options.signal;
|
|
1414
1542
|
}
|
|
@@ -1425,7 +1553,7 @@ ${marker}
|
|
|
1425
1553
|
const response = await abortable(
|
|
1426
1554
|
this.#sandbox.process.executeCommand(
|
|
1427
1555
|
wrapped,
|
|
1428
|
-
this.#
|
|
1556
|
+
this.#workingDirectory,
|
|
1429
1557
|
void 0,
|
|
1430
1558
|
timeoutSeconds(this.#commandTimeoutMs)
|
|
1431
1559
|
),
|
|
@@ -1472,7 +1600,7 @@ ${marker}
|
|
|
1472
1600
|
this.#disposed = true;
|
|
1473
1601
|
}
|
|
1474
1602
|
#resolvePath(path) {
|
|
1475
|
-
const target = isAbsolute(path) ?
|
|
1603
|
+
const target = isAbsolute(path) ? resolve3(path) : resolve3(this.#workingDirectory, path);
|
|
1476
1604
|
if (target !== this.#workspacePath && !target.startsWith(`${this.#workspacePath}/`)) {
|
|
1477
1605
|
throw new Error(`Path is outside the Daytona workspace: ${path}`);
|
|
1478
1606
|
}
|
|
@@ -1488,7 +1616,7 @@ var DaytonaWorkspaceSynchronizer = class {
|
|
|
1488
1616
|
#sandbox;
|
|
1489
1617
|
#knownFiles = /* @__PURE__ */ new Set();
|
|
1490
1618
|
constructor(cwd, remoteWorkspacePath, sandbox) {
|
|
1491
|
-
this.#cwd =
|
|
1619
|
+
this.#cwd = resolve3(cwd);
|
|
1492
1620
|
this.#remoteWorkspacePath = remoteWorkspacePath;
|
|
1493
1621
|
this.#sandbox = sandbox;
|
|
1494
1622
|
}
|
|
@@ -1519,7 +1647,7 @@ var DaytonaWorkspaceSynchronizer = class {
|
|
|
1519
1647
|
for (const [path, size] of localFiles) {
|
|
1520
1648
|
signal?.throwIfAborted();
|
|
1521
1649
|
await abortable(
|
|
1522
|
-
this.#sandbox.fs.uploadFile(
|
|
1650
|
+
this.#sandbox.fs.uploadFile(resolve3(this.#cwd, path), this.#remotePath(path)),
|
|
1523
1651
|
signal
|
|
1524
1652
|
);
|
|
1525
1653
|
if (this.#knownFiles.has(path)) filesUpdated++;
|
|
@@ -1551,7 +1679,7 @@ var DaytonaWorkspaceSynchronizer = class {
|
|
|
1551
1679
|
let bytesTransferred = 0;
|
|
1552
1680
|
for (const path of this.#knownFiles) {
|
|
1553
1681
|
if (remoteFiles.has(path)) continue;
|
|
1554
|
-
await rm(
|
|
1682
|
+
await rm(resolve3(this.#cwd, path), { force: true });
|
|
1555
1683
|
filesDeleted++;
|
|
1556
1684
|
}
|
|
1557
1685
|
for (const path of remoteFiles) {
|
|
@@ -1581,7 +1709,7 @@ var DaytonaWorkspaceSynchronizer = class {
|
|
|
1581
1709
|
return `${this.#remoteWorkspacePath}/${path}`;
|
|
1582
1710
|
}
|
|
1583
1711
|
#localPath(path) {
|
|
1584
|
-
const target =
|
|
1712
|
+
const target = resolve3(this.#cwd, path);
|
|
1585
1713
|
if (target !== this.#cwd && !target.startsWith(`${this.#cwd}${sep}`)) {
|
|
1586
1714
|
throw new Error(`Daytona returned a path outside the workspace: ${path}`);
|
|
1587
1715
|
}
|
|
@@ -1606,16 +1734,24 @@ var DaytonaExecutorSession = class {
|
|
|
1606
1734
|
#disposed = false;
|
|
1607
1735
|
constructor(remote, options, signal) {
|
|
1608
1736
|
const workspacePath = options.remoteWorkspacePath ?? DEFAULT_REMOTE_WORKSPACE;
|
|
1737
|
+
const relativeCwd = options.relativeCwd ?? ".";
|
|
1738
|
+
const workingDirectory = resolve3(workspacePath, relativeCwd);
|
|
1609
1739
|
this.#remote = remote;
|
|
1610
1740
|
this.#synchronizer = new DaytonaWorkspaceSynchronizer(options.cwd, workspacePath, remote);
|
|
1611
1741
|
this.sandbox = new DaytonaSandbox(remote, {
|
|
1612
1742
|
workspacePath,
|
|
1743
|
+
workingDirectory,
|
|
1613
1744
|
commandTimeoutMs: options.commandTimeoutMs,
|
|
1614
1745
|
signal
|
|
1615
1746
|
});
|
|
1616
1747
|
this.context = {
|
|
1617
1748
|
kind: "daytona",
|
|
1618
1749
|
workspacePath,
|
|
1750
|
+
...options.relativeCwd !== void 0 ? {
|
|
1751
|
+
workingDirectory,
|
|
1752
|
+
cwdPreserved: options.relativeCwd !== null,
|
|
1753
|
+
...options.relativeCwd === null ? { cwdLimitation: "The selected sandbox cwd is on an additional mount; starting at the effective workspace root." } : {}
|
|
1754
|
+
} : {},
|
|
1619
1755
|
isolated: true,
|
|
1620
1756
|
networkAccess: "full"
|
|
1621
1757
|
};
|
|
@@ -1715,7 +1851,7 @@ Terminus-2 spirit:
|
|
|
1715
1851
|
- Summarize final findings and important command results clearly.
|
|
1716
1852
|
|
|
1717
1853
|
Runtime context:
|
|
1718
|
-
- Working directory: ${
|
|
1854
|
+
- Working directory: ${options.sandboxCwd ?? "/workspace"}
|
|
1719
1855
|
- Current time: ${options.now.toISOString()}
|
|
1720
1856
|
- Maximum model/tool steps: ${options.maxSteps}
|
|
1721
1857
|
- Per-command timeout: ${options.commandTimeoutMs} ms
|
|
@@ -1738,17 +1874,20 @@ Tool policy:
|
|
|
1738
1874
|
}
|
|
1739
1875
|
function buildDelegatedSystemPrompt(options) {
|
|
1740
1876
|
const executionBoundary = options.context.isolated ? "Commands run natively inside an isolated remote environment." : "Commands run natively with the current user's permissions and are not isolated.";
|
|
1877
|
+
const cwdNote = options.context.cwdLimitation ? `
|
|
1878
|
+
- Working-directory limitation: ${options.context.cwdLimitation}` : "";
|
|
1741
1879
|
return `You are Ollie's delegated execution agent. Complete one bounded task using native workspace tools.
|
|
1742
1880
|
|
|
1743
1881
|
Runtime context:
|
|
1744
1882
|
- Workspace root: ${options.context.workspacePath}
|
|
1883
|
+
- Working directory: ${options.context.workingDirectory ?? options.context.workspacePath}${cwdNote}
|
|
1745
1884
|
- Current time: ${options.now.toISOString()}
|
|
1746
1885
|
- Maximum model/tool steps: ${options.maxSteps}
|
|
1747
1886
|
- Per-command timeout: ${options.commandTimeoutMs} ms
|
|
1748
1887
|
- ${executionBoundary}
|
|
1749
1888
|
|
|
1750
1889
|
Operating rules:
|
|
1751
|
-
- Start at the
|
|
1890
|
+
- Start at the configured working directory and use relative paths for workspace files.
|
|
1752
1891
|
- Inspect the relevant state before making changes.
|
|
1753
1892
|
- Keep changes minimal and directly related to the delegated task.
|
|
1754
1893
|
- Run appropriate native checks or tests and report their actual results.
|
|
@@ -1770,8 +1909,12 @@ async function createWorkspaceTools(options) {
|
|
|
1770
1909
|
}
|
|
1771
1910
|
async function createTools(options) {
|
|
1772
1911
|
return createWorkspaceTools({
|
|
1773
|
-
sandbox: createJustBash(options.cwd
|
|
1774
|
-
|
|
1912
|
+
sandbox: createJustBash(options.cwd, {
|
|
1913
|
+
mounts: options.mounts,
|
|
1914
|
+
workspaceMountPoint: options.workspaceMountPoint,
|
|
1915
|
+
sandboxCwd: options.sandboxCwd
|
|
1916
|
+
}),
|
|
1917
|
+
destination: options.sandboxCwd ?? options.workspaceMountPoint ?? SANDBOX_WORKSPACE_PATH2,
|
|
1775
1918
|
maxOutputBytes: options.maxOutputBytes,
|
|
1776
1919
|
extraInstructions: "Use bash commands to inspect files, search, run tests, and gather terminal state. Sandbox-native git is available; local Git changes persist in the workspace, and git push has permanent remote effects, so destructive actions and pushes must only be used when explicitly requested. Keep commands bounded."
|
|
1777
1920
|
});
|
|
@@ -1796,9 +1939,9 @@ var ModelTaskDelegator = class {
|
|
|
1796
1939
|
});
|
|
1797
1940
|
const tools = await createWorkspaceTools({
|
|
1798
1941
|
sandbox: session.sandbox,
|
|
1799
|
-
destination: session.context.workspacePath,
|
|
1942
|
+
destination: session.context.workingDirectory ?? session.context.workspacePath,
|
|
1800
1943
|
maxOutputBytes: this.options.maxOutputBytes,
|
|
1801
|
-
extraInstructions: "Use the native workspace tools to complete the delegated task.
|
|
1944
|
+
extraInstructions: "Use the native workspace tools to complete the delegated task. Use relative paths from the configured working directory, keep changes focused, and verify the result before reporting it."
|
|
1802
1945
|
});
|
|
1803
1946
|
const result = await generateText({
|
|
1804
1947
|
model: this.options.model,
|
|
@@ -1846,7 +1989,7 @@ var ModelTaskDelegator = class {
|
|
|
1846
1989
|
// src/executor/local.ts
|
|
1847
1990
|
import { spawn } from "child_process";
|
|
1848
1991
|
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
1849
|
-
import { dirname as dirname2, isAbsolute as isAbsolute2, resolve as
|
|
1992
|
+
import { dirname as dirname2, isAbsolute as isAbsolute2, resolve as resolve4 } from "path";
|
|
1850
1993
|
var LocalSandbox = class {
|
|
1851
1994
|
#cwd;
|
|
1852
1995
|
#commandTimeoutMs;
|
|
@@ -1855,7 +1998,7 @@ var LocalSandbox = class {
|
|
|
1855
1998
|
#processes = /* @__PURE__ */ new Set();
|
|
1856
1999
|
#disposed = false;
|
|
1857
2000
|
constructor(options, signal) {
|
|
1858
|
-
this.#cwd =
|
|
2001
|
+
this.#cwd = resolve4(options.cwd, options.relativeCwd ?? ".");
|
|
1859
2002
|
this.#commandTimeoutMs = options.commandTimeoutMs;
|
|
1860
2003
|
this.#env = options.env ?? process.env;
|
|
1861
2004
|
this.#signal = signal;
|
|
@@ -1968,7 +2111,7 @@ var LocalSandbox = class {
|
|
|
1968
2111
|
this.#processes.clear();
|
|
1969
2112
|
}
|
|
1970
2113
|
#resolvePath(path) {
|
|
1971
|
-
return isAbsolute2(path) ? path :
|
|
2114
|
+
return isAbsolute2(path) ? path : resolve4(this.#cwd, path);
|
|
1972
2115
|
}
|
|
1973
2116
|
#assertActive() {
|
|
1974
2117
|
if (this.#disposed) throw new Error("Local executor session has been disposed");
|
|
@@ -1979,11 +2122,17 @@ var LocalExecutorSession = class {
|
|
|
1979
2122
|
context;
|
|
1980
2123
|
#disposed = false;
|
|
1981
2124
|
constructor(options, signal) {
|
|
1982
|
-
const workspacePath =
|
|
2125
|
+
const workspacePath = resolve4(options.cwd);
|
|
2126
|
+
const workingDirectory = resolve4(workspacePath, options.relativeCwd ?? ".");
|
|
1983
2127
|
this.sandbox = new LocalSandbox(options, signal);
|
|
1984
2128
|
this.context = {
|
|
1985
2129
|
kind: "local",
|
|
1986
2130
|
workspacePath,
|
|
2131
|
+
...options.relativeCwd !== void 0 ? {
|
|
2132
|
+
workingDirectory,
|
|
2133
|
+
cwdPreserved: options.relativeCwd !== null,
|
|
2134
|
+
...options.relativeCwd === null ? { cwdLimitation: "The selected sandbox cwd is on an additional mount; starting at the effective workspace root." } : {}
|
|
2135
|
+
} : {},
|
|
1987
2136
|
isolated: false,
|
|
1988
2137
|
networkAccess: "full"
|
|
1989
2138
|
};
|
|
@@ -2077,11 +2226,13 @@ function createExecutorProvider(options) {
|
|
|
2077
2226
|
case "local":
|
|
2078
2227
|
return new LocalExecutorProvider({
|
|
2079
2228
|
cwd: options.cwd,
|
|
2229
|
+
relativeCwd: options.workspaceRelativeCwd,
|
|
2080
2230
|
commandTimeoutMs: options.commandTimeoutMs
|
|
2081
2231
|
});
|
|
2082
2232
|
case "daytona":
|
|
2083
2233
|
return new DaytonaExecutorProvider({
|
|
2084
2234
|
cwd: options.cwd,
|
|
2235
|
+
relativeCwd: options.workspaceRelativeCwd,
|
|
2085
2236
|
commandTimeoutMs: options.commandTimeoutMs,
|
|
2086
2237
|
snapshot: process.env.OLLIE_DAYTONA_SNAPSHOT
|
|
2087
2238
|
});
|
|
@@ -2096,6 +2247,9 @@ async function runOneShot(options) {
|
|
|
2096
2247
|
});
|
|
2097
2248
|
const workspaceTools = await createTools({
|
|
2098
2249
|
cwd: options.cwd,
|
|
2250
|
+
workspaceMountPoint: options.workspaceMountPoint,
|
|
2251
|
+
sandboxCwd: options.sandboxCwd,
|
|
2252
|
+
mounts: options.mounts,
|
|
2099
2253
|
maxOutputBytes: options.maxOutputBytes
|
|
2100
2254
|
});
|
|
2101
2255
|
const executorProvider = createExecutorProvider(options);
|
|
@@ -2122,6 +2276,7 @@ async function runOneShot(options) {
|
|
|
2122
2276
|
now: /* @__PURE__ */ new Date(),
|
|
2123
2277
|
maxSteps: options.maxSteps,
|
|
2124
2278
|
commandTimeoutMs: options.commandTimeoutMs,
|
|
2279
|
+
sandboxCwd: options.sandboxCwd,
|
|
2125
2280
|
executorInstructions: executorProvider?.systemPrompt
|
|
2126
2281
|
}),
|
|
2127
2282
|
prompt: options.prompt,
|
|
@@ -2173,7 +2328,7 @@ async function runOneShot(options) {
|
|
|
2173
2328
|
|
|
2174
2329
|
// src/sandbox/runtime.ts
|
|
2175
2330
|
import { once } from "events";
|
|
2176
|
-
import { posix as
|
|
2331
|
+
import { posix as posix5 } from "path";
|
|
2177
2332
|
import { createInterface } from "readline";
|
|
2178
2333
|
function isRecord(value) {
|
|
2179
2334
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -2211,7 +2366,7 @@ function requireString(params, name) {
|
|
|
2211
2366
|
return value;
|
|
2212
2367
|
}
|
|
2213
2368
|
function validateVirtualPath(path) {
|
|
2214
|
-
if (!
|
|
2369
|
+
if (!posix5.isAbsolute(path) || path.includes("\\") || path.includes("\0") || posix5.normalize(path) !== path || path.split("/").some((component) => component === "." || component === "..")) {
|
|
2215
2370
|
throw new Error("Path must be a normalized absolute virtual path");
|
|
2216
2371
|
}
|
|
2217
2372
|
return path;
|
|
@@ -2272,7 +2427,11 @@ async function runWorker(options = {}) {
|
|
|
2272
2427
|
const workspaceRoot = options.cwd ?? process.cwd();
|
|
2273
2428
|
const input = options.input ?? process.stdin;
|
|
2274
2429
|
const output = options.output ?? process.stdout;
|
|
2275
|
-
const bash = createJustBash(workspaceRoot
|
|
2430
|
+
const bash = createJustBash(workspaceRoot, {
|
|
2431
|
+
mounts: options.mounts,
|
|
2432
|
+
workspaceMountPoint: options.workspaceMountPoint,
|
|
2433
|
+
sandboxCwd: options.sandboxCwd
|
|
2434
|
+
});
|
|
2276
2435
|
const lines = createInterface({ input, crlfDelay: Number.POSITIVE_INFINITY });
|
|
2277
2436
|
const close = () => lines.close();
|
|
2278
2437
|
if (options.signal?.aborted) {
|
|
@@ -2321,7 +2480,17 @@ function truncateText(input, maxBytes) {
|
|
|
2321
2480
|
|
|
2322
2481
|
// src/cli-program.ts
|
|
2323
2482
|
function addExecOptions(command, ndjsonDescription = "emit one JSON result record") {
|
|
2324
|
-
return command.option("--cwd <path>", "working directory").option("--timeout <ms>", "command timeout in milliseconds").option("--max-output <bytes>", "maximum retained output").option("--ndjson", ndjsonDescription);
|
|
2483
|
+
return addMountOption(command.option("--cwd <host-path>", "host workspace mounted at /workspace").option("--sandbox-cwd <sandbox-path>", "initial working directory inside the sandbox")).option("--timeout <ms>", "command timeout in milliseconds").option("--max-output <bytes>", "maximum retained output").option("--ndjson", ndjsonDescription);
|
|
2484
|
+
}
|
|
2485
|
+
function collectOption(value, previous) {
|
|
2486
|
+
return [...previous ?? [], value];
|
|
2487
|
+
}
|
|
2488
|
+
function addMountOption(command) {
|
|
2489
|
+
return command.option(
|
|
2490
|
+
"--mount <host-path:sandbox-path>",
|
|
2491
|
+
"mount a host directory read-write (repeatable)",
|
|
2492
|
+
collectOption
|
|
2493
|
+
);
|
|
2325
2494
|
}
|
|
2326
2495
|
function addAgentOptions(command) {
|
|
2327
2496
|
return addExecOptions(command, "emit newline-delimited JSON event records").option("--provider <provider>", "model provider", "openai").option("--model <model>", "model id").option(
|
|
@@ -2330,12 +2499,20 @@ function addAgentOptions(command) {
|
|
|
2330
2499
|
"none"
|
|
2331
2500
|
).option("--max-steps <number>", "maximum model/tool steps").option("--no-color", "disable color output");
|
|
2332
2501
|
}
|
|
2502
|
+
function warnImplicitCwd(usedImplicitCwd) {
|
|
2503
|
+
if (usedImplicitCwd) {
|
|
2504
|
+
process.stderr.write(
|
|
2505
|
+
"Warning: omitting --cwd and --sandbox-cwd is deprecated; currently using --cwd .\n"
|
|
2506
|
+
);
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2333
2509
|
async function runAgent(promptParts, options) {
|
|
2334
2510
|
const prompt = promptParts.join(" ").trim();
|
|
2335
2511
|
if (!prompt) {
|
|
2336
2512
|
throw new CliError('A prompt is required. Example: ollie agent "summarize this repository"');
|
|
2337
2513
|
}
|
|
2338
2514
|
const config = await resolveRunConfig(options);
|
|
2515
|
+
warnImplicitCwd(config.usedImplicitCwd);
|
|
2339
2516
|
const renderer = createRenderer({ ndjson: config.ndjson, color: config.color });
|
|
2340
2517
|
const controller = new AbortController();
|
|
2341
2518
|
const onSigint = () => controller.abort();
|
|
@@ -2358,6 +2535,7 @@ async function runExec(commandParts, options) {
|
|
|
2358
2535
|
throw new CliError('A command is required. Example: ollie "pwd && ls -la"');
|
|
2359
2536
|
}
|
|
2360
2537
|
const config = await resolveRunConfig(options);
|
|
2538
|
+
warnImplicitCwd(config.usedImplicitCwd);
|
|
2361
2539
|
const controller = new AbortController();
|
|
2362
2540
|
let signalExitCode;
|
|
2363
2541
|
const onSigint = () => {
|
|
@@ -2376,7 +2554,10 @@ async function runExec(commandParts, options) {
|
|
|
2376
2554
|
shellCommand,
|
|
2377
2555
|
{
|
|
2378
2556
|
timeoutMs: config.commandTimeoutMs,
|
|
2379
|
-
env: process.env
|
|
2557
|
+
env: process.env,
|
|
2558
|
+
workspaceMountPoint: config.workspaceMountPoint,
|
|
2559
|
+
sandboxCwd: config.sandboxCwd,
|
|
2560
|
+
mounts: config.mounts
|
|
2380
2561
|
},
|
|
2381
2562
|
controller.signal
|
|
2382
2563
|
);
|
|
@@ -2415,6 +2596,8 @@ async function runExec(commandParts, options) {
|
|
|
2415
2596
|
}
|
|
2416
2597
|
}
|
|
2417
2598
|
async function runSandbox(options) {
|
|
2599
|
+
const config = await resolveRunConfig(options);
|
|
2600
|
+
warnImplicitCwd(config.usedImplicitCwd);
|
|
2418
2601
|
const controller = new AbortController();
|
|
2419
2602
|
let signalExitCode;
|
|
2420
2603
|
const onSigint = () => {
|
|
@@ -2429,7 +2612,10 @@ async function runSandbox(options) {
|
|
|
2429
2612
|
process.once("SIGTERM", onSigterm);
|
|
2430
2613
|
try {
|
|
2431
2614
|
await runWorker({
|
|
2432
|
-
cwd:
|
|
2615
|
+
cwd: config.cwd,
|
|
2616
|
+
workspaceMountPoint: config.workspaceMountPoint,
|
|
2617
|
+
sandboxCwd: config.sandboxCwd,
|
|
2618
|
+
mounts: config.mounts,
|
|
2433
2619
|
signal: controller.signal
|
|
2434
2620
|
});
|
|
2435
2621
|
if (signalExitCode !== void 0) {
|
|
@@ -2458,7 +2644,10 @@ function createProgram() {
|
|
|
2458
2644
|
agent.action(async (prompt, options) => {
|
|
2459
2645
|
await runAgent(prompt, options);
|
|
2460
2646
|
});
|
|
2461
|
-
|
|
2647
|
+
const sandbox = addMountOption(
|
|
2648
|
+
program.command("sandbox").description("run the persistent sandbox NDJSON command interface").option("--cwd <host-path>", "host workspace mounted at /workspace").option("--sandbox-cwd <sandbox-path>", "initial working directory inside the sandbox")
|
|
2649
|
+
);
|
|
2650
|
+
sandbox.action(async (options) => {
|
|
2462
2651
|
await runSandbox(options);
|
|
2463
2652
|
});
|
|
2464
2653
|
program.command("doctor").description("validate the local Ollie setup without making model calls").option("--cwd <path>", "working directory").option("--provider <provider>", "model provider", "openai").option("--model <model>", "model id").option("--json", "emit JSON report").action(async (options) => {
|
|
@@ -2468,7 +2657,13 @@ function createProgram() {
|
|
|
2468
2657
|
return program;
|
|
2469
2658
|
}
|
|
2470
2659
|
var RESERVED_SUBCOMMANDS = /* @__PURE__ */ new Set(["agent", "sandbox", "doctor"]);
|
|
2471
|
-
var ROOT_OPTIONS_WITH_VALUES = /* @__PURE__ */ new Set([
|
|
2660
|
+
var ROOT_OPTIONS_WITH_VALUES = /* @__PURE__ */ new Set([
|
|
2661
|
+
"--cwd",
|
|
2662
|
+
"--sandbox-cwd",
|
|
2663
|
+
"--mount",
|
|
2664
|
+
"--timeout",
|
|
2665
|
+
"--max-output"
|
|
2666
|
+
]);
|
|
2472
2667
|
function rootBoundaryPrecedesCommand(args) {
|
|
2473
2668
|
for (let index = 0; index < args.length; index += 1) {
|
|
2474
2669
|
const token = args[index] ?? "";
|