@locusai/cli 0.22.1 → 0.22.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/bin/locus.js +193 -127
- package/package.json +2 -2
package/bin/locus.js
CHANGED
|
@@ -896,9 +896,10 @@ __export(exports_sandbox, {
|
|
|
896
896
|
getModelSandboxName: () => getModelSandboxName,
|
|
897
897
|
displaySandboxWarning: () => displaySandboxWarning,
|
|
898
898
|
detectSandboxSupport: () => detectSandboxSupport,
|
|
899
|
+
detectContainerWorkdir: () => detectContainerWorkdir,
|
|
899
900
|
checkProviderSandboxMismatch: () => checkProviderSandboxMismatch
|
|
900
901
|
});
|
|
901
|
-
import { execFile } from "node:child_process";
|
|
902
|
+
import { execFile, execSync as execSync2 } from "node:child_process";
|
|
902
903
|
import { createInterface } from "node:readline";
|
|
903
904
|
function getProviderSandboxName(config, provider) {
|
|
904
905
|
return config.providers[provider];
|
|
@@ -1033,6 +1034,34 @@ ${yellow2("⚠")} Docker sandbox not available. Install Docker Desktop 4.58+ fo
|
|
|
1033
1034
|
}
|
|
1034
1035
|
return true;
|
|
1035
1036
|
}
|
|
1037
|
+
function detectContainerWorkdir(sandboxName, hostProjectRoot) {
|
|
1038
|
+
const log = getLogger();
|
|
1039
|
+
try {
|
|
1040
|
+
execSync2(`docker sandbox exec ${sandboxName} test -d ${JSON.stringify(hostProjectRoot)}`, { stdio: ["pipe", "pipe", "pipe"], timeout: 5000 });
|
|
1041
|
+
log.debug("Container workdir matches host path", { hostProjectRoot });
|
|
1042
|
+
return null;
|
|
1043
|
+
} catch {}
|
|
1044
|
+
try {
|
|
1045
|
+
const result = execSync2(`docker sandbox exec ${sandboxName} find / -maxdepth 5 -path '*/.locus/config.json' -type f 2>/dev/null`, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 15000 }).trim();
|
|
1046
|
+
if (result) {
|
|
1047
|
+
const configPath = result.split(`
|
|
1048
|
+
`)[0].trim();
|
|
1049
|
+
const workdir = configPath.replace(/\/.locus\/config\.json$/, "");
|
|
1050
|
+
if (workdir) {
|
|
1051
|
+
log.debug("Detected container workdir", {
|
|
1052
|
+
hostProjectRoot,
|
|
1053
|
+
containerWorkdir: workdir
|
|
1054
|
+
});
|
|
1055
|
+
return workdir;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
} catch (err) {
|
|
1059
|
+
log.debug("Container workdir probe failed", {
|
|
1060
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
return null;
|
|
1064
|
+
}
|
|
1036
1065
|
function waitForEnter() {
|
|
1037
1066
|
return new Promise((resolve) => {
|
|
1038
1067
|
const rl = createInterface({
|
|
@@ -1062,7 +1091,7 @@ __export(exports_upgrade, {
|
|
|
1062
1091
|
fetchLatestVersion: () => fetchLatestVersion,
|
|
1063
1092
|
compareSemver: () => compareSemver
|
|
1064
1093
|
});
|
|
1065
|
-
import { execSync as
|
|
1094
|
+
import { execSync as execSync3 } from "node:child_process";
|
|
1066
1095
|
function compareSemver(a, b) {
|
|
1067
1096
|
const partsA = a.replace(/^v/, "").split(".").map(Number);
|
|
1068
1097
|
const partsB = b.replace(/^v/, "").split(".").map(Number);
|
|
@@ -1078,7 +1107,7 @@ function compareSemver(a, b) {
|
|
|
1078
1107
|
}
|
|
1079
1108
|
function fetchLatestVersion() {
|
|
1080
1109
|
try {
|
|
1081
|
-
const result =
|
|
1110
|
+
const result = execSync3(`npm view ${PACKAGE_NAME} version`, {
|
|
1082
1111
|
encoding: "utf-8",
|
|
1083
1112
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1084
1113
|
timeout: 15000
|
|
@@ -1090,14 +1119,14 @@ function fetchLatestVersion() {
|
|
|
1090
1119
|
}
|
|
1091
1120
|
function installVersion(version) {
|
|
1092
1121
|
try {
|
|
1093
|
-
|
|
1122
|
+
execSync3("npm cache clean --force", {
|
|
1094
1123
|
encoding: "utf-8",
|
|
1095
1124
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1096
1125
|
timeout: 30000
|
|
1097
1126
|
});
|
|
1098
1127
|
} catch {}
|
|
1099
1128
|
try {
|
|
1100
|
-
|
|
1129
|
+
execSync3(`npm install -g ${PACKAGE_NAME}@${version}`, {
|
|
1101
1130
|
encoding: "utf-8",
|
|
1102
1131
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1103
1132
|
timeout: 120000
|
|
@@ -1109,7 +1138,7 @@ function installVersion(version) {
|
|
|
1109
1138
|
}
|
|
1110
1139
|
function verifyInstalled(expectedVersion) {
|
|
1111
1140
|
try {
|
|
1112
|
-
const installed =
|
|
1141
|
+
const installed = execSync3(`npm list -g ${PACKAGE_NAME} --depth=0 --json`, {
|
|
1113
1142
|
encoding: "utf-8",
|
|
1114
1143
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1115
1144
|
timeout: 1e4
|
|
@@ -1473,7 +1502,7 @@ var init_ecosystem = __esm(() => {
|
|
|
1473
1502
|
// src/core/github.ts
|
|
1474
1503
|
import {
|
|
1475
1504
|
execFileSync,
|
|
1476
|
-
execSync as
|
|
1505
|
+
execSync as execSync4
|
|
1477
1506
|
} from "node:child_process";
|
|
1478
1507
|
function gh(args, options = {}) {
|
|
1479
1508
|
const log = getLogger();
|
|
@@ -1482,7 +1511,7 @@ function gh(args, options = {}) {
|
|
|
1482
1511
|
log.debug(`gh ${args}`, { cwd });
|
|
1483
1512
|
const startTime = Date.now();
|
|
1484
1513
|
try {
|
|
1485
|
-
const result =
|
|
1514
|
+
const result = execSync4(`gh ${args}`, {
|
|
1486
1515
|
cwd,
|
|
1487
1516
|
encoding: "utf-8",
|
|
1488
1517
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -2231,7 +2260,7 @@ var exports_create = {};
|
|
|
2231
2260
|
__export(exports_create, {
|
|
2232
2261
|
createCommand: () => createCommand
|
|
2233
2262
|
});
|
|
2234
|
-
import { execSync as
|
|
2263
|
+
import { execSync as execSync5 } from "node:child_process";
|
|
2235
2264
|
import { existsSync as existsSync7, mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
2236
2265
|
import { join as join7 } from "node:path";
|
|
2237
2266
|
function validateName(name) {
|
|
@@ -2250,7 +2279,7 @@ function capitalize(str) {
|
|
|
2250
2279
|
}
|
|
2251
2280
|
function checkNpmExists(fullName) {
|
|
2252
2281
|
try {
|
|
2253
|
-
|
|
2282
|
+
execSync5(`npm view ${fullName} version 2>/dev/null`, {
|
|
2254
2283
|
stdio: "pipe",
|
|
2255
2284
|
timeout: 1e4
|
|
2256
2285
|
});
|
|
@@ -2390,7 +2419,7 @@ function printHelp(): void {
|
|
|
2390
2419
|
}
|
|
2391
2420
|
`;
|
|
2392
2421
|
}
|
|
2393
|
-
function generateReadme(name,
|
|
2422
|
+
function generateReadme(name, description) {
|
|
2394
2423
|
return `# @locusai/locus-${name}
|
|
2395
2424
|
|
|
2396
2425
|
${description}
|
|
@@ -2545,7 +2574,7 @@ ${bold2("Creating package:")} ${cyan2(fullNpmName)}
|
|
|
2545
2574
|
writeFileSync5(join7(packagesDir, "src", "index.ts"), generateIndexTs(name), "utf-8");
|
|
2546
2575
|
process.stderr.write(`${green("✓")} Generated src/index.ts
|
|
2547
2576
|
`);
|
|
2548
|
-
writeFileSync5(join7(packagesDir, "README.md"), generateReadme(name,
|
|
2577
|
+
writeFileSync5(join7(packagesDir, "README.md"), generateReadme(name, description), "utf-8");
|
|
2549
2578
|
process.stderr.write(`${green("✓")} Generated README.md
|
|
2550
2579
|
`);
|
|
2551
2580
|
process.stderr.write(`
|
|
@@ -3690,7 +3719,7 @@ var init_stream_renderer = __esm(() => {
|
|
|
3690
3719
|
});
|
|
3691
3720
|
|
|
3692
3721
|
// src/repl/clipboard.ts
|
|
3693
|
-
import { execSync as
|
|
3722
|
+
import { execSync as execSync6 } from "node:child_process";
|
|
3694
3723
|
import { existsSync as existsSync12, mkdirSync as mkdirSync8 } from "node:fs";
|
|
3695
3724
|
import { tmpdir } from "node:os";
|
|
3696
3725
|
import { join as join11 } from "node:path";
|
|
@@ -3729,7 +3758,7 @@ function readMacOSClipboardImage() {
|
|
|
3729
3758
|
`return "ok"`
|
|
3730
3759
|
].join(`
|
|
3731
3760
|
`);
|
|
3732
|
-
const result =
|
|
3761
|
+
const result = execSync6("osascript", {
|
|
3733
3762
|
input: script,
|
|
3734
3763
|
encoding: "utf-8",
|
|
3735
3764
|
timeout: 5000,
|
|
@@ -3743,13 +3772,13 @@ function readMacOSClipboardImage() {
|
|
|
3743
3772
|
}
|
|
3744
3773
|
function readLinuxClipboardImage() {
|
|
3745
3774
|
try {
|
|
3746
|
-
const targets =
|
|
3775
|
+
const targets = execSync6("xclip -selection clipboard -t TARGETS -o 2>/dev/null", { encoding: "utf-8", timeout: 3000 });
|
|
3747
3776
|
if (!targets.includes("image/png")) {
|
|
3748
3777
|
return null;
|
|
3749
3778
|
}
|
|
3750
3779
|
ensureStableDir();
|
|
3751
3780
|
const destPath = join11(STABLE_DIR, `clipboard-${Date.now()}.png`);
|
|
3752
|
-
|
|
3781
|
+
execSync6(`xclip -selection clipboard -t image/png -o > "${destPath}" 2>/dev/null`, { timeout: 5000 });
|
|
3753
3782
|
if (existsSync12(destPath)) {
|
|
3754
3783
|
return destPath;
|
|
3755
3784
|
}
|
|
@@ -4770,7 +4799,7 @@ __export(exports_claude, {
|
|
|
4770
4799
|
buildClaudeArgs: () => buildClaudeArgs,
|
|
4771
4800
|
ClaudeRunner: () => ClaudeRunner
|
|
4772
4801
|
});
|
|
4773
|
-
import { execSync as
|
|
4802
|
+
import { execSync as execSync7, spawn as spawn2 } from "node:child_process";
|
|
4774
4803
|
function buildClaudeArgs(options) {
|
|
4775
4804
|
const args = ["--dangerously-skip-permissions", "--no-session-persistence"];
|
|
4776
4805
|
if (options.model) {
|
|
@@ -4788,7 +4817,7 @@ class ClaudeRunner {
|
|
|
4788
4817
|
aborted = false;
|
|
4789
4818
|
async isAvailable() {
|
|
4790
4819
|
try {
|
|
4791
|
-
|
|
4820
|
+
execSync7("claude --version", {
|
|
4792
4821
|
encoding: "utf-8",
|
|
4793
4822
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4794
4823
|
});
|
|
@@ -4799,7 +4828,7 @@ class ClaudeRunner {
|
|
|
4799
4828
|
}
|
|
4800
4829
|
async getVersion() {
|
|
4801
4830
|
try {
|
|
4802
|
-
const output =
|
|
4831
|
+
const output = execSync7("claude --version", {
|
|
4803
4832
|
encoding: "utf-8",
|
|
4804
4833
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4805
4834
|
}).trim();
|
|
@@ -5141,13 +5170,13 @@ function backupIgnoredFiles(projectRoot) {
|
|
|
5141
5170
|
}
|
|
5142
5171
|
};
|
|
5143
5172
|
}
|
|
5144
|
-
async function enforceSandboxIgnore(sandboxName, projectRoot) {
|
|
5173
|
+
async function enforceSandboxIgnore(sandboxName, projectRoot, containerWorkdir) {
|
|
5145
5174
|
const log = getLogger();
|
|
5146
5175
|
const ignorePath = join13(projectRoot, ".sandboxignore");
|
|
5147
5176
|
const rules = parseIgnoreFile(ignorePath);
|
|
5148
5177
|
if (rules.length === 0)
|
|
5149
5178
|
return;
|
|
5150
|
-
const script = buildCleanupScript(rules, projectRoot);
|
|
5179
|
+
const script = buildCleanupScript(rules, containerWorkdir ?? projectRoot);
|
|
5151
5180
|
if (!script)
|
|
5152
5181
|
return;
|
|
5153
5182
|
log.debug("Enforcing .sandboxignore", {
|
|
@@ -5187,11 +5216,13 @@ import { spawn as spawn3 } from "node:child_process";
|
|
|
5187
5216
|
|
|
5188
5217
|
class SandboxedClaudeRunner {
|
|
5189
5218
|
sandboxName;
|
|
5219
|
+
containerWorkdir;
|
|
5190
5220
|
name = "claude-sandboxed";
|
|
5191
5221
|
process = null;
|
|
5192
5222
|
aborted = false;
|
|
5193
|
-
constructor(sandboxName) {
|
|
5223
|
+
constructor(sandboxName, containerWorkdir) {
|
|
5194
5224
|
this.sandboxName = sandboxName;
|
|
5225
|
+
this.containerWorkdir = containerWorkdir;
|
|
5195
5226
|
}
|
|
5196
5227
|
async isAvailable() {
|
|
5197
5228
|
const { ClaudeRunner: ClaudeRunner2 } = await Promise.resolve().then(() => (init_claude(), exports_claude));
|
|
@@ -5217,13 +5248,14 @@ class SandboxedClaudeRunner {
|
|
|
5217
5248
|
const claudeArgs = ["-p", options.prompt, ...buildClaudeArgs(options)];
|
|
5218
5249
|
options.onStatusChange?.("Syncing sandbox...");
|
|
5219
5250
|
const backup = backupIgnoredFiles(options.cwd);
|
|
5220
|
-
await enforceSandboxIgnore(this.sandboxName, options.cwd);
|
|
5251
|
+
await enforceSandboxIgnore(this.sandboxName, options.cwd, this.containerWorkdir);
|
|
5221
5252
|
options.onStatusChange?.("Thinking...");
|
|
5253
|
+
const workdir = this.containerWorkdir ?? options.cwd;
|
|
5222
5254
|
const dockerArgs = [
|
|
5223
5255
|
"sandbox",
|
|
5224
5256
|
"exec",
|
|
5225
5257
|
"-w",
|
|
5226
|
-
|
|
5258
|
+
workdir,
|
|
5227
5259
|
this.sandboxName,
|
|
5228
5260
|
"claude",
|
|
5229
5261
|
...claudeArgs
|
|
@@ -5388,7 +5420,7 @@ var init_claude_sandbox = __esm(() => {
|
|
|
5388
5420
|
});
|
|
5389
5421
|
|
|
5390
5422
|
// src/ai/codex.ts
|
|
5391
|
-
import { execSync as
|
|
5423
|
+
import { execSync as execSync8, spawn as spawn4 } from "node:child_process";
|
|
5392
5424
|
function buildCodexArgs(model) {
|
|
5393
5425
|
const args = ["exec", "--full-auto", "--skip-git-repo-check", "--json"];
|
|
5394
5426
|
if (model) {
|
|
@@ -5404,7 +5436,7 @@ class CodexRunner {
|
|
|
5404
5436
|
aborted = false;
|
|
5405
5437
|
async isAvailable() {
|
|
5406
5438
|
try {
|
|
5407
|
-
|
|
5439
|
+
execSync8("codex --version", {
|
|
5408
5440
|
encoding: "utf-8",
|
|
5409
5441
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5410
5442
|
});
|
|
@@ -5415,7 +5447,7 @@ class CodexRunner {
|
|
|
5415
5447
|
}
|
|
5416
5448
|
async getVersion() {
|
|
5417
5449
|
try {
|
|
5418
|
-
const output =
|
|
5450
|
+
const output = execSync8("codex --version", {
|
|
5419
5451
|
encoding: "utf-8",
|
|
5420
5452
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5421
5453
|
}).trim();
|
|
@@ -5567,12 +5599,14 @@ import { spawn as spawn5 } from "node:child_process";
|
|
|
5567
5599
|
|
|
5568
5600
|
class SandboxedCodexRunner {
|
|
5569
5601
|
sandboxName;
|
|
5602
|
+
containerWorkdir;
|
|
5570
5603
|
name = "codex-sandboxed";
|
|
5571
5604
|
process = null;
|
|
5572
5605
|
aborted = false;
|
|
5573
5606
|
codexInstalled = false;
|
|
5574
|
-
constructor(sandboxName) {
|
|
5607
|
+
constructor(sandboxName, containerWorkdir) {
|
|
5575
5608
|
this.sandboxName = sandboxName;
|
|
5609
|
+
this.containerWorkdir = containerWorkdir;
|
|
5576
5610
|
}
|
|
5577
5611
|
async isAvailable() {
|
|
5578
5612
|
const delegate = new CodexRunner;
|
|
@@ -5596,19 +5630,20 @@ class SandboxedCodexRunner {
|
|
|
5596
5630
|
const codexArgs = buildCodexArgs(options.model);
|
|
5597
5631
|
options.onStatusChange?.("Syncing sandbox...");
|
|
5598
5632
|
const backup = backupIgnoredFiles(options.cwd);
|
|
5599
|
-
await enforceSandboxIgnore(this.sandboxName, options.cwd);
|
|
5633
|
+
await enforceSandboxIgnore(this.sandboxName, options.cwd, this.containerWorkdir);
|
|
5600
5634
|
if (!this.codexInstalled) {
|
|
5601
5635
|
options.onStatusChange?.("Checking codex...");
|
|
5602
5636
|
await this.ensureCodexInstalled(this.sandboxName);
|
|
5603
5637
|
this.codexInstalled = true;
|
|
5604
5638
|
}
|
|
5605
5639
|
options.onStatusChange?.("Thinking...");
|
|
5640
|
+
const workdir = this.containerWorkdir ?? options.cwd;
|
|
5606
5641
|
const dockerArgs = [
|
|
5607
5642
|
"sandbox",
|
|
5608
5643
|
"exec",
|
|
5609
5644
|
"-i",
|
|
5610
5645
|
"-w",
|
|
5611
|
-
|
|
5646
|
+
workdir,
|
|
5612
5647
|
this.sandboxName,
|
|
5613
5648
|
"codex",
|
|
5614
5649
|
...codexArgs
|
|
@@ -5789,12 +5824,12 @@ async function createRunnerAsync(provider, sandboxed) {
|
|
|
5789
5824
|
throw new Error(`Unknown AI provider: ${provider}`);
|
|
5790
5825
|
}
|
|
5791
5826
|
}
|
|
5792
|
-
function createUserManagedSandboxRunner(provider, sandboxName) {
|
|
5827
|
+
function createUserManagedSandboxRunner(provider, sandboxName, containerWorkdir) {
|
|
5793
5828
|
switch (provider) {
|
|
5794
5829
|
case "claude":
|
|
5795
|
-
return new SandboxedClaudeRunner(sandboxName);
|
|
5830
|
+
return new SandboxedClaudeRunner(sandboxName, containerWorkdir);
|
|
5796
5831
|
case "codex":
|
|
5797
|
-
return new SandboxedCodexRunner(sandboxName);
|
|
5832
|
+
return new SandboxedCodexRunner(sandboxName, containerWorkdir);
|
|
5798
5833
|
default:
|
|
5799
5834
|
throw new Error(`Unknown AI provider: ${provider}`);
|
|
5800
5835
|
}
|
|
@@ -5905,7 +5940,7 @@ ${red2("✗")} ${dim2("Force exit.")}\r
|
|
|
5905
5940
|
exitCode: 1
|
|
5906
5941
|
};
|
|
5907
5942
|
}
|
|
5908
|
-
runner = createUserManagedSandboxRunner(resolvedProvider, options.sandboxName);
|
|
5943
|
+
runner = createUserManagedSandboxRunner(resolvedProvider, options.sandboxName, options.containerWorkdir);
|
|
5909
5944
|
} else {
|
|
5910
5945
|
runner = await createRunnerAsync(resolvedProvider, false);
|
|
5911
5946
|
}
|
|
@@ -6240,7 +6275,8 @@ async function issueCreate(projectRoot, parsed) {
|
|
|
6240
6275
|
silent: true,
|
|
6241
6276
|
activity: "generating issue",
|
|
6242
6277
|
sandboxed: config.sandbox.enabled,
|
|
6243
|
-
sandboxName: getModelSandboxName(config.sandbox, config.ai.model, config.ai.provider)
|
|
6278
|
+
sandboxName: getModelSandboxName(config.sandbox, config.ai.model, config.ai.provider),
|
|
6279
|
+
containerWorkdir: config.sandbox.containerWorkdir
|
|
6244
6280
|
});
|
|
6245
6281
|
if (!aiResult.success && !aiResult.interrupted) {
|
|
6246
6282
|
process.stderr.write(`${red2("✗")} Failed to generate issue: ${aiResult.error}
|
|
@@ -7443,7 +7479,7 @@ var init_sprint = __esm(() => {
|
|
|
7443
7479
|
});
|
|
7444
7480
|
|
|
7445
7481
|
// src/core/prompt-builder.ts
|
|
7446
|
-
import { execSync as
|
|
7482
|
+
import { execSync as execSync9 } from "node:child_process";
|
|
7447
7483
|
import { existsSync as existsSync15, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "node:fs";
|
|
7448
7484
|
import { join as join14 } from "node:path";
|
|
7449
7485
|
function buildExecutionPrompt(ctx) {
|
|
@@ -7592,7 +7628,7 @@ ${parts.join(`
|
|
|
7592
7628
|
function buildRepoContext(projectRoot) {
|
|
7593
7629
|
const parts = [];
|
|
7594
7630
|
try {
|
|
7595
|
-
const tree =
|
|
7631
|
+
const tree = execSync9("find . -maxdepth 2 -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/.locus/*' -not -path '*/dist/*' -not -path '*/build/*' | head -80", { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
7596
7632
|
if (tree) {
|
|
7597
7633
|
parts.push(`<file-tree>
|
|
7598
7634
|
\`\`\`
|
|
@@ -7602,7 +7638,7 @@ ${tree}
|
|
|
7602
7638
|
}
|
|
7603
7639
|
} catch {}
|
|
7604
7640
|
try {
|
|
7605
|
-
const gitLog =
|
|
7641
|
+
const gitLog = execSync9("git log --oneline -10", {
|
|
7606
7642
|
cwd: projectRoot,
|
|
7607
7643
|
encoding: "utf-8",
|
|
7608
7644
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -7616,7 +7652,7 @@ ${gitLog}
|
|
|
7616
7652
|
}
|
|
7617
7653
|
} catch {}
|
|
7618
7654
|
try {
|
|
7619
|
-
const branch =
|
|
7655
|
+
const branch = execSync9("git rev-parse --abbrev-ref HEAD", {
|
|
7620
7656
|
cwd: projectRoot,
|
|
7621
7657
|
encoding: "utf-8",
|
|
7622
7658
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -7872,7 +7908,7 @@ var init_diff_renderer = __esm(() => {
|
|
|
7872
7908
|
});
|
|
7873
7909
|
|
|
7874
7910
|
// src/repl/commands.ts
|
|
7875
|
-
import { execSync as
|
|
7911
|
+
import { execSync as execSync10 } from "node:child_process";
|
|
7876
7912
|
function getSlashCommands() {
|
|
7877
7913
|
return [
|
|
7878
7914
|
{
|
|
@@ -8070,7 +8106,7 @@ function cmdModel(args, ctx) {
|
|
|
8070
8106
|
}
|
|
8071
8107
|
function cmdDiff(_args, ctx) {
|
|
8072
8108
|
try {
|
|
8073
|
-
const diff =
|
|
8109
|
+
const diff = execSync10("git diff", {
|
|
8074
8110
|
cwd: ctx.projectRoot,
|
|
8075
8111
|
encoding: "utf-8",
|
|
8076
8112
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -8106,7 +8142,7 @@ function cmdDiff(_args, ctx) {
|
|
|
8106
8142
|
}
|
|
8107
8143
|
function cmdUndo(_args, ctx) {
|
|
8108
8144
|
try {
|
|
8109
|
-
const status =
|
|
8145
|
+
const status = execSync10("git status --porcelain", {
|
|
8110
8146
|
cwd: ctx.projectRoot,
|
|
8111
8147
|
encoding: "utf-8",
|
|
8112
8148
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -8116,7 +8152,7 @@ function cmdUndo(_args, ctx) {
|
|
|
8116
8152
|
`);
|
|
8117
8153
|
return;
|
|
8118
8154
|
}
|
|
8119
|
-
|
|
8155
|
+
execSync10("git checkout .", {
|
|
8120
8156
|
cwd: ctx.projectRoot,
|
|
8121
8157
|
encoding: "utf-8",
|
|
8122
8158
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -8494,7 +8530,7 @@ var init_session_manager = __esm(() => {
|
|
|
8494
8530
|
});
|
|
8495
8531
|
|
|
8496
8532
|
// src/repl/voice.ts
|
|
8497
|
-
import { execSync as
|
|
8533
|
+
import { execSync as execSync11, spawn as spawn6 } from "node:child_process";
|
|
8498
8534
|
import { existsSync as existsSync18, mkdirSync as mkdirSync13, unlinkSync as unlinkSync4 } from "node:fs";
|
|
8499
8535
|
import { cpus, homedir as homedir4, platform, tmpdir as tmpdir4 } from "node:os";
|
|
8500
8536
|
import { join as join18 } from "node:path";
|
|
@@ -8504,7 +8540,7 @@ function getWhisperModelPath() {
|
|
|
8504
8540
|
function commandExists(cmd) {
|
|
8505
8541
|
try {
|
|
8506
8542
|
const which = platform() === "win32" ? "where" : "which";
|
|
8507
|
-
|
|
8543
|
+
execSync11(`${which} ${cmd}`, { stdio: "pipe" });
|
|
8508
8544
|
return true;
|
|
8509
8545
|
} catch {
|
|
8510
8546
|
return false;
|
|
@@ -8630,22 +8666,22 @@ function installSox(pm) {
|
|
|
8630
8666
|
try {
|
|
8631
8667
|
switch (pm) {
|
|
8632
8668
|
case "brew":
|
|
8633
|
-
|
|
8669
|
+
execSync11("brew install sox", { stdio: "inherit", timeout: 300000 });
|
|
8634
8670
|
break;
|
|
8635
8671
|
case "apt":
|
|
8636
|
-
|
|
8672
|
+
execSync11("sudo apt-get install -y sox", {
|
|
8637
8673
|
stdio: "inherit",
|
|
8638
8674
|
timeout: 300000
|
|
8639
8675
|
});
|
|
8640
8676
|
break;
|
|
8641
8677
|
case "dnf":
|
|
8642
|
-
|
|
8678
|
+
execSync11("sudo dnf install -y sox", {
|
|
8643
8679
|
stdio: "inherit",
|
|
8644
8680
|
timeout: 300000
|
|
8645
8681
|
});
|
|
8646
8682
|
break;
|
|
8647
8683
|
case "pacman":
|
|
8648
|
-
|
|
8684
|
+
execSync11("sudo pacman -S --noconfirm sox", {
|
|
8649
8685
|
stdio: "inherit",
|
|
8650
8686
|
timeout: 300000
|
|
8651
8687
|
});
|
|
@@ -8659,7 +8695,7 @@ function installSox(pm) {
|
|
|
8659
8695
|
function installWhisperCpp(pm) {
|
|
8660
8696
|
if (pm === "brew") {
|
|
8661
8697
|
try {
|
|
8662
|
-
|
|
8698
|
+
execSync11("brew install whisper-cpp", {
|
|
8663
8699
|
stdio: "inherit",
|
|
8664
8700
|
timeout: 300000
|
|
8665
8701
|
});
|
|
@@ -8681,19 +8717,19 @@ function ensureBuildDeps(pm) {
|
|
|
8681
8717
|
try {
|
|
8682
8718
|
switch (pm) {
|
|
8683
8719
|
case "apt":
|
|
8684
|
-
|
|
8720
|
+
execSync11("sudo apt-get install -y cmake g++ make git", {
|
|
8685
8721
|
stdio: "inherit",
|
|
8686
8722
|
timeout: 300000
|
|
8687
8723
|
});
|
|
8688
8724
|
break;
|
|
8689
8725
|
case "dnf":
|
|
8690
|
-
|
|
8726
|
+
execSync11("sudo dnf install -y cmake gcc-c++ make git", {
|
|
8691
8727
|
stdio: "inherit",
|
|
8692
8728
|
timeout: 300000
|
|
8693
8729
|
});
|
|
8694
8730
|
break;
|
|
8695
8731
|
case "pacman":
|
|
8696
|
-
|
|
8732
|
+
execSync11("sudo pacman -S --noconfirm cmake gcc make git", {
|
|
8697
8733
|
stdio: "inherit",
|
|
8698
8734
|
timeout: 300000
|
|
8699
8735
|
});
|
|
@@ -8719,17 +8755,17 @@ function buildWhisperFromSource(pm) {
|
|
|
8719
8755
|
mkdirSync13(LOCUS_BIN_DIR, { recursive: true });
|
|
8720
8756
|
out.write(` ${dim2("Cloning whisper.cpp...")}
|
|
8721
8757
|
`);
|
|
8722
|
-
|
|
8758
|
+
execSync11(`git clone --depth 1 https://github.com/ggerganov/whisper.cpp.git "${join18(buildDir, "whisper.cpp")}"`, { stdio: ["pipe", "pipe", "pipe"], timeout: 120000 });
|
|
8723
8759
|
const srcDir = join18(buildDir, "whisper.cpp");
|
|
8724
8760
|
const numCpus = cpus().length || 2;
|
|
8725
8761
|
out.write(` ${dim2("Building whisper.cpp (this may take a few minutes)...")}
|
|
8726
8762
|
`);
|
|
8727
|
-
|
|
8763
|
+
execSync11("cmake -B build -DCMAKE_BUILD_TYPE=Release", {
|
|
8728
8764
|
cwd: srcDir,
|
|
8729
8765
|
stdio: ["pipe", "pipe", "pipe"],
|
|
8730
8766
|
timeout: 120000
|
|
8731
8767
|
});
|
|
8732
|
-
|
|
8768
|
+
execSync11(`cmake --build build --config Release -j${numCpus}`, {
|
|
8733
8769
|
cwd: srcDir,
|
|
8734
8770
|
stdio: ["pipe", "pipe", "pipe"],
|
|
8735
8771
|
timeout: 600000
|
|
@@ -8741,7 +8777,7 @@ function buildWhisperFromSource(pm) {
|
|
|
8741
8777
|
];
|
|
8742
8778
|
for (const candidate of binaryCandidates) {
|
|
8743
8779
|
if (existsSync18(candidate)) {
|
|
8744
|
-
|
|
8780
|
+
execSync11(`cp "${candidate}" "${destPath}" && chmod +x "${destPath}"`, {
|
|
8745
8781
|
stdio: "pipe"
|
|
8746
8782
|
});
|
|
8747
8783
|
return true;
|
|
@@ -8756,7 +8792,7 @@ function buildWhisperFromSource(pm) {
|
|
|
8756
8792
|
return false;
|
|
8757
8793
|
} finally {
|
|
8758
8794
|
try {
|
|
8759
|
-
|
|
8795
|
+
execSync11(`rm -rf "${buildDir}"`, { stdio: "pipe" });
|
|
8760
8796
|
} catch {}
|
|
8761
8797
|
}
|
|
8762
8798
|
}
|
|
@@ -8827,12 +8863,12 @@ function downloadModel() {
|
|
|
8827
8863
|
`);
|
|
8828
8864
|
try {
|
|
8829
8865
|
if (commandExists("curl")) {
|
|
8830
|
-
|
|
8866
|
+
execSync11(`curl -L -o "${modelPath}" "${url}"`, {
|
|
8831
8867
|
stdio: ["pipe", "pipe", "pipe"],
|
|
8832
8868
|
timeout: 300000
|
|
8833
8869
|
});
|
|
8834
8870
|
} else if (commandExists("wget")) {
|
|
8835
|
-
|
|
8871
|
+
execSync11(`wget -O "${modelPath}" "${url}"`, {
|
|
8836
8872
|
stdio: ["pipe", "pipe", "pipe"],
|
|
8837
8873
|
timeout: 300000
|
|
8838
8874
|
});
|
|
@@ -9030,7 +9066,7 @@ var init_voice = __esm(() => {
|
|
|
9030
9066
|
});
|
|
9031
9067
|
|
|
9032
9068
|
// src/repl/repl.ts
|
|
9033
|
-
import { execSync as
|
|
9069
|
+
import { execSync as execSync12 } from "node:child_process";
|
|
9034
9070
|
async function startRepl(options) {
|
|
9035
9071
|
const { projectRoot, config } = options;
|
|
9036
9072
|
const sessionManager = new SessionManager(projectRoot);
|
|
@@ -9048,7 +9084,7 @@ async function startRepl(options) {
|
|
|
9048
9084
|
} else {
|
|
9049
9085
|
let branch = "main";
|
|
9050
9086
|
try {
|
|
9051
|
-
branch =
|
|
9087
|
+
branch = execSync12("git rev-parse --abbrev-ref HEAD", {
|
|
9052
9088
|
cwd: projectRoot,
|
|
9053
9089
|
encoding: "utf-8",
|
|
9054
9090
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9100,7 +9136,7 @@ async function runInteractiveRepl(session, sessionManager, options) {
|
|
|
9100
9136
|
const provider = inferProviderFromModel(config.ai.model) || config.ai.provider;
|
|
9101
9137
|
const sandboxName = getProviderSandboxName(config.sandbox, provider);
|
|
9102
9138
|
if (sandboxName) {
|
|
9103
|
-
sandboxRunner = createUserManagedSandboxRunner(provider, sandboxName);
|
|
9139
|
+
sandboxRunner = createUserManagedSandboxRunner(provider, sandboxName, config.sandbox.containerWorkdir);
|
|
9104
9140
|
process.stderr.write(`${dim2("Using")} ${dim2(provider)} ${dim2("sandbox")} ${dim2(sandboxName)}
|
|
9105
9141
|
`);
|
|
9106
9142
|
} else {
|
|
@@ -9151,7 +9187,7 @@ async function runInteractiveRepl(session, sessionManager, options) {
|
|
|
9151
9187
|
if (providerChanged && config.sandbox.enabled) {
|
|
9152
9188
|
const sandboxName = getProviderSandboxName(config.sandbox, inferredProvider);
|
|
9153
9189
|
if (sandboxName) {
|
|
9154
|
-
sandboxRunner = createUserManagedSandboxRunner(inferredProvider, sandboxName);
|
|
9190
|
+
sandboxRunner = createUserManagedSandboxRunner(inferredProvider, sandboxName, config.sandbox.containerWorkdir);
|
|
9155
9191
|
process.stderr.write(`${dim2("Switched sandbox agent to")} ${dim2(inferredProvider)} ${dim2(`(${sandboxName})`)}
|
|
9156
9192
|
`);
|
|
9157
9193
|
} else {
|
|
@@ -9279,6 +9315,7 @@ async function executeAITurn(prompt, session, options, verbose = false, runner)
|
|
|
9279
9315
|
verbose,
|
|
9280
9316
|
sandboxed: config.sandbox.enabled,
|
|
9281
9317
|
sandboxName,
|
|
9318
|
+
containerWorkdir: config.sandbox.containerWorkdir,
|
|
9282
9319
|
runner
|
|
9283
9320
|
});
|
|
9284
9321
|
if (aiResult.interrupted) {
|
|
@@ -9483,7 +9520,7 @@ async function handleJsonStream(projectRoot, config, args, sessionId) {
|
|
|
9483
9520
|
try {
|
|
9484
9521
|
const fullPrompt = buildReplPrompt(prompt, projectRoot, config);
|
|
9485
9522
|
const sandboxName = getProviderSandboxName(config.sandbox, config.ai.provider);
|
|
9486
|
-
const runner = config.sandbox.enabled ? sandboxName ? createUserManagedSandboxRunner(config.ai.provider, sandboxName) : null : await createRunnerAsync(config.ai.provider, false);
|
|
9523
|
+
const runner = config.sandbox.enabled ? sandboxName ? createUserManagedSandboxRunner(config.ai.provider, sandboxName, config.sandbox.containerWorkdir) : null : await createRunnerAsync(config.ai.provider, false);
|
|
9487
9524
|
if (!runner) {
|
|
9488
9525
|
const mismatch = checkProviderSandboxMismatch(config.sandbox, config.ai.model, config.ai.provider);
|
|
9489
9526
|
stream.emitError(mismatch ?? `No sandbox configured for "${config.ai.provider}". Run "locus sandbox" to create one.`, false);
|
|
@@ -9536,11 +9573,11 @@ var init_exec = __esm(() => {
|
|
|
9536
9573
|
});
|
|
9537
9574
|
|
|
9538
9575
|
// src/core/submodule.ts
|
|
9539
|
-
import { execSync as
|
|
9576
|
+
import { execSync as execSync13 } from "node:child_process";
|
|
9540
9577
|
import { existsSync as existsSync19 } from "node:fs";
|
|
9541
9578
|
import { join as join19 } from "node:path";
|
|
9542
9579
|
function git2(args, cwd) {
|
|
9543
|
-
return
|
|
9580
|
+
return execSync13(`git ${args}`, {
|
|
9544
9581
|
cwd,
|
|
9545
9582
|
encoding: "utf-8",
|
|
9546
9583
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9608,7 +9645,7 @@ function commitDirtySubmodules(cwd, issueNumber, issueTitle) {
|
|
|
9608
9645
|
const message = `chore: complete #${issueNumber} - ${issueTitle}
|
|
9609
9646
|
|
|
9610
9647
|
Co-Authored-By: LocusAgent <agent@locusai.team>`;
|
|
9611
|
-
|
|
9648
|
+
execSync13("git commit -F -", {
|
|
9612
9649
|
input: message,
|
|
9613
9650
|
cwd: sub.absolutePath,
|
|
9614
9651
|
encoding: "utf-8",
|
|
@@ -9695,7 +9732,7 @@ var init_submodule = __esm(() => {
|
|
|
9695
9732
|
});
|
|
9696
9733
|
|
|
9697
9734
|
// src/core/agent.ts
|
|
9698
|
-
import { execSync as
|
|
9735
|
+
import { execSync as execSync14 } from "node:child_process";
|
|
9699
9736
|
async function executeIssue(projectRoot, options) {
|
|
9700
9737
|
const log = getLogger();
|
|
9701
9738
|
const timer = createTimer();
|
|
@@ -9724,7 +9761,7 @@ ${cyan2("●")} ${bold2(`#${issueNumber}`)} ${issue.title}
|
|
|
9724
9761
|
}
|
|
9725
9762
|
let issueComments = [];
|
|
9726
9763
|
try {
|
|
9727
|
-
const commentsRaw =
|
|
9764
|
+
const commentsRaw = execSync14(`gh issue view ${issueNumber} --json comments --jq '.comments[].body'`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
9728
9765
|
if (commentsRaw) {
|
|
9729
9766
|
issueComments = commentsRaw.split(`
|
|
9730
9767
|
`).filter(Boolean);
|
|
@@ -9761,7 +9798,8 @@ ${yellow2("⚠")} ${bold2("Dry run")} — would execute with:
|
|
|
9761
9798
|
cwd: options.worktreePath ?? projectRoot,
|
|
9762
9799
|
activity: `issue #${issueNumber}`,
|
|
9763
9800
|
sandboxed: options.sandboxed,
|
|
9764
|
-
sandboxName: options.sandboxName
|
|
9801
|
+
sandboxName: options.sandboxName,
|
|
9802
|
+
containerWorkdir: options.containerWorkdir
|
|
9765
9803
|
});
|
|
9766
9804
|
const output = aiResult.output;
|
|
9767
9805
|
if (aiResult.interrupted) {
|
|
@@ -9867,7 +9905,8 @@ ${c.body}`),
|
|
|
9867
9905
|
cwd: projectRoot,
|
|
9868
9906
|
activity: `iterating on PR #${prNumber}`,
|
|
9869
9907
|
sandboxed: config.sandbox.enabled,
|
|
9870
|
-
sandboxName: getModelSandboxName(config.sandbox, config.ai.model, config.ai.provider)
|
|
9908
|
+
sandboxName: getModelSandboxName(config.sandbox, config.ai.model, config.ai.provider),
|
|
9909
|
+
containerWorkdir: config.sandbox.containerWorkdir
|
|
9871
9910
|
});
|
|
9872
9911
|
if (aiResult.interrupted) {
|
|
9873
9912
|
process.stderr.write(`
|
|
@@ -9888,12 +9927,12 @@ ${aiResult.success ? green("✓") : red2("✗")} Iteration ${aiResult.success ?
|
|
|
9888
9927
|
}
|
|
9889
9928
|
async function createIssuePR(projectRoot, config, issue) {
|
|
9890
9929
|
try {
|
|
9891
|
-
const currentBranch =
|
|
9930
|
+
const currentBranch = execSync14("git rev-parse --abbrev-ref HEAD", {
|
|
9892
9931
|
cwd: projectRoot,
|
|
9893
9932
|
encoding: "utf-8",
|
|
9894
9933
|
stdio: ["pipe", "pipe", "pipe"]
|
|
9895
9934
|
}).trim();
|
|
9896
|
-
const diff =
|
|
9935
|
+
const diff = execSync14(`git diff origin/${config.agent.baseBranch}..HEAD --stat`, {
|
|
9897
9936
|
cwd: projectRoot,
|
|
9898
9937
|
encoding: "utf-8",
|
|
9899
9938
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9903,7 +9942,7 @@ async function createIssuePR(projectRoot, config, issue) {
|
|
|
9903
9942
|
return;
|
|
9904
9943
|
}
|
|
9905
9944
|
pushSubmoduleBranches(projectRoot);
|
|
9906
|
-
|
|
9945
|
+
execSync14(`git push -u origin ${currentBranch}`, {
|
|
9907
9946
|
cwd: projectRoot,
|
|
9908
9947
|
encoding: "utf-8",
|
|
9909
9948
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9958,9 +9997,9 @@ var init_agent = __esm(() => {
|
|
|
9958
9997
|
});
|
|
9959
9998
|
|
|
9960
9999
|
// src/core/conflict.ts
|
|
9961
|
-
import { execSync as
|
|
10000
|
+
import { execSync as execSync15 } from "node:child_process";
|
|
9962
10001
|
function git3(args, cwd) {
|
|
9963
|
-
return
|
|
10002
|
+
return execSync15(`git ${args}`, {
|
|
9964
10003
|
cwd,
|
|
9965
10004
|
encoding: "utf-8",
|
|
9966
10005
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10261,11 +10300,11 @@ var init_shutdown = __esm(() => {
|
|
|
10261
10300
|
});
|
|
10262
10301
|
|
|
10263
10302
|
// src/core/worktree.ts
|
|
10264
|
-
import { execSync as
|
|
10303
|
+
import { execSync as execSync16 } from "node:child_process";
|
|
10265
10304
|
import { existsSync as existsSync21, readdirSync as readdirSync7, realpathSync, statSync as statSync4 } from "node:fs";
|
|
10266
10305
|
import { join as join21 } from "node:path";
|
|
10267
10306
|
function git4(args, cwd) {
|
|
10268
|
-
return
|
|
10307
|
+
return execSync16(`git ${args}`, {
|
|
10269
10308
|
cwd,
|
|
10270
10309
|
encoding: "utf-8",
|
|
10271
10310
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10290,7 +10329,7 @@ function generateBranchName(issueNumber) {
|
|
|
10290
10329
|
}
|
|
10291
10330
|
function getWorktreeBranch(worktreePath) {
|
|
10292
10331
|
try {
|
|
10293
|
-
return
|
|
10332
|
+
return execSync16("git branch --show-current", {
|
|
10294
10333
|
cwd: worktreePath,
|
|
10295
10334
|
encoding: "utf-8",
|
|
10296
10335
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10417,12 +10456,12 @@ var exports_run = {};
|
|
|
10417
10456
|
__export(exports_run, {
|
|
10418
10457
|
runCommand: () => runCommand
|
|
10419
10458
|
});
|
|
10420
|
-
import { execSync as
|
|
10459
|
+
import { execSync as execSync17 } from "node:child_process";
|
|
10421
10460
|
function resolveExecutionContext(config, modelOverride) {
|
|
10422
10461
|
const model = modelOverride ?? config.ai.model;
|
|
10423
10462
|
const provider = inferProviderFromModel(model) ?? config.ai.provider;
|
|
10424
10463
|
const sandboxName = getModelSandboxName(config.sandbox, model, provider);
|
|
10425
|
-
return { provider, model, sandboxName };
|
|
10464
|
+
return { provider, model, sandboxName, containerWorkdir: config.sandbox.containerWorkdir };
|
|
10426
10465
|
}
|
|
10427
10466
|
function printRunHelp() {
|
|
10428
10467
|
process.stderr.write(`
|
|
@@ -10577,7 +10616,7 @@ ${yellow2("⚠")} A sprint run is already in progress.
|
|
|
10577
10616
|
}
|
|
10578
10617
|
if (!flags.dryRun) {
|
|
10579
10618
|
try {
|
|
10580
|
-
|
|
10619
|
+
execSync17(`git checkout -B ${branchName}`, {
|
|
10581
10620
|
cwd: projectRoot,
|
|
10582
10621
|
encoding: "utf-8",
|
|
10583
10622
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10627,7 +10666,7 @@ ${red2("✗")} Auto-rebase failed. Resolve manually.
|
|
|
10627
10666
|
let sprintContext;
|
|
10628
10667
|
if (i > 0 && !flags.dryRun) {
|
|
10629
10668
|
try {
|
|
10630
|
-
sprintContext =
|
|
10669
|
+
sprintContext = execSync17(`git diff origin/${config.agent.baseBranch}..HEAD`, {
|
|
10631
10670
|
cwd: projectRoot,
|
|
10632
10671
|
encoding: "utf-8",
|
|
10633
10672
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10648,7 +10687,8 @@ ${progressBar(i, state.tasks.length, { label: "Sprint Progress" })}
|
|
|
10648
10687
|
sprintContext,
|
|
10649
10688
|
skipPR: true,
|
|
10650
10689
|
sandboxed,
|
|
10651
|
-
sandboxName: execution.sandboxName
|
|
10690
|
+
sandboxName: execution.sandboxName,
|
|
10691
|
+
containerWorkdir: execution.containerWorkdir
|
|
10652
10692
|
});
|
|
10653
10693
|
if (result.success) {
|
|
10654
10694
|
if (!flags.dryRun) {
|
|
@@ -10692,7 +10732,7 @@ ${bold2("Summary:")}
|
|
|
10692
10732
|
const prNumber = await createSprintPR(projectRoot, config, sprintName, branchName, completedTasks);
|
|
10693
10733
|
if (prNumber !== undefined) {
|
|
10694
10734
|
try {
|
|
10695
|
-
|
|
10735
|
+
execSync17(`git checkout ${config.agent.baseBranch}`, {
|
|
10696
10736
|
cwd: projectRoot,
|
|
10697
10737
|
encoding: "utf-8",
|
|
10698
10738
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10725,7 +10765,8 @@ ${bold2("Running sprint issue")} ${cyan2(`#${issueNumber}`)} ${dim2("(sequential
|
|
|
10725
10765
|
model: execution.model,
|
|
10726
10766
|
dryRun: flags.dryRun,
|
|
10727
10767
|
sandboxed,
|
|
10728
|
-
sandboxName: execution.sandboxName
|
|
10768
|
+
sandboxName: execution.sandboxName,
|
|
10769
|
+
containerWorkdir: execution.containerWorkdir
|
|
10729
10770
|
});
|
|
10730
10771
|
return;
|
|
10731
10772
|
}
|
|
@@ -10737,7 +10778,7 @@ ${bold2("Running issue")} ${cyan2(`#${issueNumber}`)} ${dim2(`(branch: ${branchN
|
|
|
10737
10778
|
`);
|
|
10738
10779
|
if (!flags.dryRun) {
|
|
10739
10780
|
try {
|
|
10740
|
-
|
|
10781
|
+
execSync17(`git checkout -B ${branchName} ${config.agent.baseBranch}`, {
|
|
10741
10782
|
cwd: projectRoot,
|
|
10742
10783
|
encoding: "utf-8",
|
|
10743
10784
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10762,7 +10803,7 @@ ${bold2("Running issue")} ${cyan2(`#${issueNumber}`)} ${dim2(`(branch: ${branchN
|
|
|
10762
10803
|
if (!flags.dryRun) {
|
|
10763
10804
|
if (result.success) {
|
|
10764
10805
|
try {
|
|
10765
|
-
|
|
10806
|
+
execSync17(`git checkout ${config.agent.baseBranch}`, {
|
|
10766
10807
|
cwd: projectRoot,
|
|
10767
10808
|
encoding: "utf-8",
|
|
10768
10809
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10833,7 +10874,8 @@ ${bold2("Running")} ${cyan2(`${issueNumbers.length} issues`)} ${dim2(`(max ${max
|
|
|
10833
10874
|
model: execution.model,
|
|
10834
10875
|
dryRun: flags.dryRun,
|
|
10835
10876
|
sandboxed,
|
|
10836
|
-
sandboxName: execution.sandboxName
|
|
10877
|
+
sandboxName: execution.sandboxName,
|
|
10878
|
+
containerWorkdir: execution.containerWorkdir
|
|
10837
10879
|
});
|
|
10838
10880
|
if (result.success) {
|
|
10839
10881
|
markTaskDone(state, issueNumber, result.prNumber);
|
|
@@ -10899,13 +10941,13 @@ ${bold2("Resuming")} ${state.type} run ${dim2(state.runId)}
|
|
|
10899
10941
|
`);
|
|
10900
10942
|
if (state.type === "sprint" && state.branch) {
|
|
10901
10943
|
try {
|
|
10902
|
-
const currentBranch =
|
|
10944
|
+
const currentBranch = execSync17("git rev-parse --abbrev-ref HEAD", {
|
|
10903
10945
|
cwd: projectRoot,
|
|
10904
10946
|
encoding: "utf-8",
|
|
10905
10947
|
stdio: ["pipe", "pipe", "pipe"]
|
|
10906
10948
|
}).trim();
|
|
10907
10949
|
if (currentBranch !== state.branch) {
|
|
10908
|
-
|
|
10950
|
+
execSync17(`git checkout ${state.branch}`, {
|
|
10909
10951
|
cwd: projectRoot,
|
|
10910
10952
|
encoding: "utf-8",
|
|
10911
10953
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10933,7 +10975,8 @@ ${bold2("Resuming")} ${state.type} run ${dim2(state.runId)}
|
|
|
10933
10975
|
model: execution.model,
|
|
10934
10976
|
skipPR: isSprintRun,
|
|
10935
10977
|
sandboxed,
|
|
10936
|
-
sandboxName: execution.sandboxName
|
|
10978
|
+
sandboxName: execution.sandboxName,
|
|
10979
|
+
containerWorkdir: execution.containerWorkdir
|
|
10937
10980
|
});
|
|
10938
10981
|
if (result.success) {
|
|
10939
10982
|
if (isSprintRun) {
|
|
@@ -10972,7 +11015,7 @@ ${bold2("Resume complete:")} ${green(`✓ ${finalStats.done}`)} ${finalStats.fai
|
|
|
10972
11015
|
const prNumber = await createSprintPR(projectRoot, config, state.sprint, state.branch, completedTasks);
|
|
10973
11016
|
if (prNumber !== undefined) {
|
|
10974
11017
|
try {
|
|
10975
|
-
|
|
11018
|
+
execSync17(`git checkout ${config.agent.baseBranch}`, {
|
|
10976
11019
|
cwd: projectRoot,
|
|
10977
11020
|
encoding: "utf-8",
|
|
10978
11021
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -11008,14 +11051,14 @@ function ensureTaskCommit(projectRoot, issueNumber, issueTitle) {
|
|
|
11008
11051
|
process.stderr.write(` ${dim2(`Committed submodule changes: ${committedSubmodules.join(", ")}`)}
|
|
11009
11052
|
`);
|
|
11010
11053
|
}
|
|
11011
|
-
const status =
|
|
11054
|
+
const status = execSync17("git status --porcelain", {
|
|
11012
11055
|
cwd: projectRoot,
|
|
11013
11056
|
encoding: "utf-8",
|
|
11014
11057
|
stdio: ["pipe", "pipe", "pipe"]
|
|
11015
11058
|
}).trim();
|
|
11016
11059
|
if (!status)
|
|
11017
11060
|
return;
|
|
11018
|
-
|
|
11061
|
+
execSync17("git add -A", {
|
|
11019
11062
|
cwd: projectRoot,
|
|
11020
11063
|
encoding: "utf-8",
|
|
11021
11064
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -11023,7 +11066,7 @@ function ensureTaskCommit(projectRoot, issueNumber, issueTitle) {
|
|
|
11023
11066
|
const message = `chore: complete #${issueNumber} - ${issueTitle}
|
|
11024
11067
|
|
|
11025
11068
|
Co-Authored-By: LocusAgent <agent@locusai.team>`;
|
|
11026
|
-
|
|
11069
|
+
execSync17(`git commit -F -`, {
|
|
11027
11070
|
input: message,
|
|
11028
11071
|
cwd: projectRoot,
|
|
11029
11072
|
encoding: "utf-8",
|
|
@@ -11037,7 +11080,7 @@ async function createSprintPR(projectRoot, config, sprintName, branchName, tasks
|
|
|
11037
11080
|
if (!config.agent.autoPR)
|
|
11038
11081
|
return;
|
|
11039
11082
|
try {
|
|
11040
|
-
const diff =
|
|
11083
|
+
const diff = execSync17(`git diff origin/${config.agent.baseBranch}..HEAD --stat`, {
|
|
11041
11084
|
cwd: projectRoot,
|
|
11042
11085
|
encoding: "utf-8",
|
|
11043
11086
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -11048,7 +11091,7 @@ async function createSprintPR(projectRoot, config, sprintName, branchName, tasks
|
|
|
11048
11091
|
return;
|
|
11049
11092
|
}
|
|
11050
11093
|
pushSubmoduleBranches(projectRoot);
|
|
11051
|
-
|
|
11094
|
+
execSync17(`git push -u origin ${branchName}`, {
|
|
11052
11095
|
cwd: projectRoot,
|
|
11053
11096
|
encoding: "utf-8",
|
|
11054
11097
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -11467,7 +11510,8 @@ ${bold2("Planning:")} ${cyan2(displayDirective)}
|
|
|
11467
11510
|
cwd: projectRoot,
|
|
11468
11511
|
activity: "planning",
|
|
11469
11512
|
sandboxed: config.sandbox.enabled,
|
|
11470
|
-
sandboxName: getModelSandboxName(config.sandbox, flags.model ?? config.ai.model, config.ai.provider)
|
|
11513
|
+
sandboxName: getModelSandboxName(config.sandbox, flags.model ?? config.ai.model, config.ai.provider),
|
|
11514
|
+
containerWorkdir: config.sandbox.containerWorkdir
|
|
11471
11515
|
});
|
|
11472
11516
|
if (aiResult.interrupted) {
|
|
11473
11517
|
process.stderr.write(`
|
|
@@ -11590,7 +11634,8 @@ Start with foundational/setup tasks, then core features, then integration/testin
|
|
|
11590
11634
|
activity: "issue ordering",
|
|
11591
11635
|
silent: true,
|
|
11592
11636
|
sandboxed: config.sandbox.enabled,
|
|
11593
|
-
sandboxName: getModelSandboxName(config.sandbox, flags.model ?? config.ai.model, config.ai.provider)
|
|
11637
|
+
sandboxName: getModelSandboxName(config.sandbox, flags.model ?? config.ai.model, config.ai.provider),
|
|
11638
|
+
containerWorkdir: config.sandbox.containerWorkdir
|
|
11594
11639
|
});
|
|
11595
11640
|
if (aiResult.interrupted) {
|
|
11596
11641
|
process.stderr.write(`
|
|
@@ -11850,7 +11895,7 @@ var exports_review = {};
|
|
|
11850
11895
|
__export(exports_review, {
|
|
11851
11896
|
reviewCommand: () => reviewCommand
|
|
11852
11897
|
});
|
|
11853
|
-
import { execFileSync as execFileSync2, execSync as
|
|
11898
|
+
import { execFileSync as execFileSync2, execSync as execSync18 } from "node:child_process";
|
|
11854
11899
|
import { existsSync as existsSync23, readFileSync as readFileSync14 } from "node:fs";
|
|
11855
11900
|
import { join as join23 } from "node:path";
|
|
11856
11901
|
function printHelp3() {
|
|
@@ -11936,7 +11981,7 @@ ${bold2("Review complete:")} ${green(`✓ ${reviewed}`)}${failed > 0 ? ` ${red2(
|
|
|
11936
11981
|
async function reviewSinglePR(projectRoot, config, prNumber, focus, flags) {
|
|
11937
11982
|
let prInfo;
|
|
11938
11983
|
try {
|
|
11939
|
-
const result =
|
|
11984
|
+
const result = execSync18(`gh pr view ${prNumber} --json number,title,body,state,headRefName,baseRefName,labels,url,createdAt`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
11940
11985
|
const raw = JSON.parse(result);
|
|
11941
11986
|
prInfo = {
|
|
11942
11987
|
number: raw.number,
|
|
@@ -11981,7 +12026,8 @@ async function reviewPR(projectRoot, config, pr, focus, flags) {
|
|
|
11981
12026
|
cwd: projectRoot,
|
|
11982
12027
|
activity: `PR #${pr.number}`,
|
|
11983
12028
|
sandboxed: config.sandbox.enabled,
|
|
11984
|
-
sandboxName: getModelSandboxName(config.sandbox, flags.model ?? config.ai.model, config.ai.provider)
|
|
12029
|
+
sandboxName: getModelSandboxName(config.sandbox, flags.model ?? config.ai.model, config.ai.provider),
|
|
12030
|
+
containerWorkdir: config.sandbox.containerWorkdir
|
|
11985
12031
|
});
|
|
11986
12032
|
if (aiResult.interrupted) {
|
|
11987
12033
|
process.stderr.write(` ${yellow2("⚡")} Review interrupted.
|
|
@@ -12083,7 +12129,7 @@ var exports_iterate = {};
|
|
|
12083
12129
|
__export(exports_iterate, {
|
|
12084
12130
|
iterateCommand: () => iterateCommand
|
|
12085
12131
|
});
|
|
12086
|
-
import { execSync as
|
|
12132
|
+
import { execSync as execSync19 } from "node:child_process";
|
|
12087
12133
|
function printHelp4() {
|
|
12088
12134
|
process.stderr.write(`
|
|
12089
12135
|
${bold2("locus iterate")} — Re-execute tasks with PR feedback
|
|
@@ -12301,12 +12347,12 @@ ${bold2("Summary:")} ${green(`✓ ${succeeded}`)}${failed > 0 ? ` ${red2(`✗ ${
|
|
|
12301
12347
|
}
|
|
12302
12348
|
function findPRForIssue(projectRoot, issueNumber) {
|
|
12303
12349
|
try {
|
|
12304
|
-
const result =
|
|
12350
|
+
const result = execSync19(`gh pr list --search "Closes #${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
12305
12351
|
const parsed = JSON.parse(result);
|
|
12306
12352
|
if (parsed.length > 0) {
|
|
12307
12353
|
return parsed[0].number;
|
|
12308
12354
|
}
|
|
12309
|
-
const branchResult =
|
|
12355
|
+
const branchResult = execSync19(`gh pr list --head "locus/issue-${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
12310
12356
|
const branchParsed = JSON.parse(branchResult);
|
|
12311
12357
|
if (branchParsed.length > 0) {
|
|
12312
12358
|
return branchParsed[0].number;
|
|
@@ -12563,7 +12609,8 @@ ${bold2("Discussion:")} ${cyan2(topic)}
|
|
|
12563
12609
|
cwd: projectRoot,
|
|
12564
12610
|
activity: "discussion",
|
|
12565
12611
|
sandboxed: config.sandbox.enabled,
|
|
12566
|
-
sandboxName: getModelSandboxName(config.sandbox, flags.model ?? config.ai.model, config.ai.provider)
|
|
12612
|
+
sandboxName: getModelSandboxName(config.sandbox, flags.model ?? config.ai.model, config.ai.provider),
|
|
12613
|
+
containerWorkdir: config.sandbox.containerWorkdir
|
|
12567
12614
|
});
|
|
12568
12615
|
if (aiResult.interrupted) {
|
|
12569
12616
|
process.stderr.write(`
|
|
@@ -12936,7 +12983,7 @@ __export(exports_sandbox2, {
|
|
|
12936
12983
|
parseSandboxLogsArgs: () => parseSandboxLogsArgs,
|
|
12937
12984
|
parseSandboxInstallArgs: () => parseSandboxInstallArgs
|
|
12938
12985
|
});
|
|
12939
|
-
import { execSync as
|
|
12986
|
+
import { execSync as execSync20, spawn as spawn7 } from "node:child_process";
|
|
12940
12987
|
import { createHash } from "node:crypto";
|
|
12941
12988
|
import { existsSync as existsSync26, readFileSync as readFileSync17 } from "node:fs";
|
|
12942
12989
|
import { basename as basename4, join as join26 } from "node:path";
|
|
@@ -13070,11 +13117,20 @@ async function handleCreate(projectRoot) {
|
|
|
13070
13117
|
}
|
|
13071
13118
|
process.stderr.write(`${green("✓")} ${provider} sandbox created: ${bold2(name)}
|
|
13072
13119
|
`);
|
|
13120
|
+
if (!config.sandbox.containerWorkdir) {
|
|
13121
|
+
const containerWorkdir = detectContainerWorkdir(name, projectRoot);
|
|
13122
|
+
if (containerWorkdir) {
|
|
13123
|
+
config.sandbox.containerWorkdir = containerWorkdir;
|
|
13124
|
+
process.stderr.write(` ${dim2(`Container workdir: ${containerWorkdir}`)}
|
|
13125
|
+
`);
|
|
13126
|
+
}
|
|
13127
|
+
}
|
|
13073
13128
|
readySandboxes[provider] = name;
|
|
13074
13129
|
config.sandbox.enabled = true;
|
|
13075
13130
|
config.sandbox.providers = readySandboxes;
|
|
13076
13131
|
saveConfig(projectRoot, config);
|
|
13077
|
-
|
|
13132
|
+
const workdir = config.sandbox.containerWorkdir ?? projectRoot;
|
|
13133
|
+
await runSandboxSetup(name, projectRoot, workdir);
|
|
13078
13134
|
process.stderr.write(`
|
|
13079
13135
|
${green("✓")} Sandbox mode enabled for ${bold2(provider)}.
|
|
13080
13136
|
`);
|
|
@@ -13099,19 +13155,20 @@ async function handleAgentLogin(projectRoot, agent) {
|
|
|
13099
13155
|
if (agent === "codex") {
|
|
13100
13156
|
await ensureCodexInSandbox(sandboxName);
|
|
13101
13157
|
}
|
|
13158
|
+
const workdir = config.sandbox.containerWorkdir ?? projectRoot;
|
|
13102
13159
|
process.stderr.write(`Connecting to ${agent} sandbox ${dim2(sandboxName)}...
|
|
13103
13160
|
`);
|
|
13104
13161
|
process.stderr.write(`${dim2("Login and then exit when ready.")}
|
|
13105
13162
|
|
|
13106
13163
|
`);
|
|
13107
|
-
const child = spawn7("docker", ["sandbox", "exec", "-it", "-w",
|
|
13164
|
+
const child = spawn7("docker", ["sandbox", "exec", "-it", "-w", workdir, sandboxName, agent], {
|
|
13108
13165
|
stdio: "inherit"
|
|
13109
13166
|
});
|
|
13110
13167
|
await new Promise((resolve2) => {
|
|
13111
13168
|
child.on("close", async (code) => {
|
|
13112
13169
|
const backup = backupIgnoredFiles(projectRoot);
|
|
13113
13170
|
try {
|
|
13114
|
-
await enforceSandboxIgnore(sandboxName, projectRoot);
|
|
13171
|
+
await enforceSandboxIgnore(sandboxName, projectRoot, config.sandbox.containerWorkdir);
|
|
13115
13172
|
} finally {
|
|
13116
13173
|
backup.restore();
|
|
13117
13174
|
}
|
|
@@ -13148,7 +13205,7 @@ function handleRemove(projectRoot) {
|
|
|
13148
13205
|
process.stderr.write(`Removing sandbox ${bold2(sandboxName)}...
|
|
13149
13206
|
`);
|
|
13150
13207
|
try {
|
|
13151
|
-
|
|
13208
|
+
execSync20(`docker sandbox rm ${sandboxName}`, {
|
|
13152
13209
|
encoding: "utf-8",
|
|
13153
13210
|
stdio: ["pipe", "pipe", "pipe"],
|
|
13154
13211
|
timeout: 15000
|
|
@@ -13157,6 +13214,7 @@ function handleRemove(projectRoot) {
|
|
|
13157
13214
|
}
|
|
13158
13215
|
config.sandbox.providers = {};
|
|
13159
13216
|
config.sandbox.enabled = false;
|
|
13217
|
+
delete config.sandbox.containerWorkdir;
|
|
13160
13218
|
saveConfig(projectRoot, config);
|
|
13161
13219
|
process.stderr.write(`${green("✓")} Provider sandboxes removed. Sandbox mode disabled.
|
|
13162
13220
|
`);
|
|
@@ -13169,6 +13227,10 @@ ${bold2("Sandbox Status")}
|
|
|
13169
13227
|
`);
|
|
13170
13228
|
process.stderr.write(` ${dim2("Enabled:")} ${config.sandbox.enabled ? green("yes") : red2("no")}
|
|
13171
13229
|
`);
|
|
13230
|
+
if (config.sandbox.containerWorkdir) {
|
|
13231
|
+
process.stderr.write(` ${dim2("Container workdir:")} ${config.sandbox.containerWorkdir}
|
|
13232
|
+
`);
|
|
13233
|
+
}
|
|
13172
13234
|
for (const provider of PROVIDERS) {
|
|
13173
13235
|
const name = config.sandbox.providers[provider];
|
|
13174
13236
|
process.stderr.write(` ${dim2(`${provider}:`).padEnd(15)}${name ? bold2(name) : dim2("(not configured)")}
|
|
@@ -13305,10 +13367,12 @@ async function handleShell(projectRoot, args) {
|
|
|
13305
13367
|
`);
|
|
13306
13368
|
return;
|
|
13307
13369
|
}
|
|
13370
|
+
const config = loadConfig(projectRoot);
|
|
13308
13371
|
const sandboxName = getActiveProviderSandbox(projectRoot, provider);
|
|
13309
13372
|
if (!sandboxName) {
|
|
13310
13373
|
return;
|
|
13311
13374
|
}
|
|
13375
|
+
const workdir = config.sandbox.containerWorkdir ?? projectRoot;
|
|
13312
13376
|
process.stderr.write(`Opening shell in ${provider} sandbox ${dim2(sandboxName)}...
|
|
13313
13377
|
`);
|
|
13314
13378
|
await runInteractiveCommand("docker", [
|
|
@@ -13316,7 +13380,7 @@ async function handleShell(projectRoot, args) {
|
|
|
13316
13380
|
"exec",
|
|
13317
13381
|
"-it",
|
|
13318
13382
|
"-w",
|
|
13319
|
-
|
|
13383
|
+
workdir,
|
|
13320
13384
|
sandboxName,
|
|
13321
13385
|
"sh"
|
|
13322
13386
|
]);
|
|
@@ -13429,7 +13493,8 @@ function getInstallCommand(pm) {
|
|
|
13429
13493
|
return ["npm", "install"];
|
|
13430
13494
|
}
|
|
13431
13495
|
}
|
|
13432
|
-
async function runSandboxSetup(sandboxName, projectRoot) {
|
|
13496
|
+
async function runSandboxSetup(sandboxName, projectRoot, containerWorkdir) {
|
|
13497
|
+
const workdir = containerWorkdir ?? projectRoot;
|
|
13433
13498
|
const ecosystem = detectProjectEcosystem(projectRoot);
|
|
13434
13499
|
const isJS = isJavaScriptEcosystem(ecosystem);
|
|
13435
13500
|
if (isJS) {
|
|
@@ -13445,7 +13510,7 @@ Installing dependencies (${bold2(installCmd.join(" "))}) in sandbox ${dim2(sandb
|
|
|
13445
13510
|
"sandbox",
|
|
13446
13511
|
"exec",
|
|
13447
13512
|
"-w",
|
|
13448
|
-
|
|
13513
|
+
workdir,
|
|
13449
13514
|
sandboxName,
|
|
13450
13515
|
...installCmd
|
|
13451
13516
|
]);
|
|
@@ -13462,6 +13527,7 @@ ${dim2(`Detected ${ecosystem} project — skipping JS package install.`)}
|
|
|
13462
13527
|
`);
|
|
13463
13528
|
}
|
|
13464
13529
|
const setupScript = join26(projectRoot, ".locus", "sandbox-setup.sh");
|
|
13530
|
+
const containerSetupScript = containerWorkdir ? join26(containerWorkdir, ".locus", "sandbox-setup.sh") : setupScript;
|
|
13465
13531
|
if (existsSync26(setupScript)) {
|
|
13466
13532
|
process.stderr.write(`Running ${bold2(".locus/sandbox-setup.sh")} in sandbox ${dim2(sandboxName)}...
|
|
13467
13533
|
`);
|
|
@@ -13469,10 +13535,10 @@ ${dim2(`Detected ${ecosystem} project — skipping JS package install.`)}
|
|
|
13469
13535
|
"sandbox",
|
|
13470
13536
|
"exec",
|
|
13471
13537
|
"-w",
|
|
13472
|
-
|
|
13538
|
+
workdir,
|
|
13473
13539
|
sandboxName,
|
|
13474
13540
|
"sh",
|
|
13475
|
-
|
|
13541
|
+
containerSetupScript
|
|
13476
13542
|
]);
|
|
13477
13543
|
if (!hookOk) {
|
|
13478
13544
|
process.stderr.write(`${yellow2("⚠")} Setup hook failed in sandbox ${dim2(sandboxName)}.
|
|
@@ -13503,7 +13569,7 @@ async function handleSetup(projectRoot) {
|
|
|
13503
13569
|
`);
|
|
13504
13570
|
continue;
|
|
13505
13571
|
}
|
|
13506
|
-
await runSandboxSetup(sandboxName, projectRoot);
|
|
13572
|
+
await runSandboxSetup(sandboxName, projectRoot, config.sandbox.containerWorkdir);
|
|
13507
13573
|
}
|
|
13508
13574
|
}
|
|
13509
13575
|
function buildProviderSandboxNames(projectRoot) {
|
|
@@ -13548,9 +13614,9 @@ function runInteractiveCommand(command, args) {
|
|
|
13548
13614
|
child.on("error", () => resolve2(false));
|
|
13549
13615
|
});
|
|
13550
13616
|
}
|
|
13551
|
-
async function createProviderSandbox(provider, sandboxName, projectRoot) {
|
|
13617
|
+
async function createProviderSandbox(provider, sandboxName, projectRoot, containerWorkdir) {
|
|
13552
13618
|
try {
|
|
13553
|
-
|
|
13619
|
+
execSync20(`docker sandbox create --name ${sandboxName} claude ${projectRoot}`, {
|
|
13554
13620
|
stdio: ["pipe", "pipe", "pipe"],
|
|
13555
13621
|
timeout: 120000
|
|
13556
13622
|
});
|
|
@@ -13563,7 +13629,7 @@ async function createProviderSandbox(provider, sandboxName, projectRoot) {
|
|
|
13563
13629
|
}
|
|
13564
13630
|
const backup = backupIgnoredFiles(projectRoot);
|
|
13565
13631
|
try {
|
|
13566
|
-
await enforceSandboxIgnore(sandboxName, projectRoot);
|
|
13632
|
+
await enforceSandboxIgnore(sandboxName, projectRoot, containerWorkdir);
|
|
13567
13633
|
} finally {
|
|
13568
13634
|
backup.restore();
|
|
13569
13635
|
}
|
|
@@ -13571,7 +13637,7 @@ async function createProviderSandbox(provider, sandboxName, projectRoot) {
|
|
|
13571
13637
|
}
|
|
13572
13638
|
async function ensurePackageManagerInSandbox(sandboxName, pm) {
|
|
13573
13639
|
try {
|
|
13574
|
-
|
|
13640
|
+
execSync20(`docker sandbox exec ${sandboxName} which ${pm}`, {
|
|
13575
13641
|
stdio: ["pipe", "pipe", "pipe"],
|
|
13576
13642
|
timeout: 5000
|
|
13577
13643
|
});
|
|
@@ -13580,7 +13646,7 @@ async function ensurePackageManagerInSandbox(sandboxName, pm) {
|
|
|
13580
13646
|
process.stderr.write(`Installing ${bold2(pm)} in sandbox...
|
|
13581
13647
|
`);
|
|
13582
13648
|
try {
|
|
13583
|
-
|
|
13649
|
+
execSync20(`docker sandbox exec ${sandboxName} npm install -g ${npmPkg}`, {
|
|
13584
13650
|
stdio: "inherit",
|
|
13585
13651
|
timeout: 120000
|
|
13586
13652
|
});
|
|
@@ -13592,7 +13658,7 @@ async function ensurePackageManagerInSandbox(sandboxName, pm) {
|
|
|
13592
13658
|
}
|
|
13593
13659
|
async function ensureCodexInSandbox(sandboxName) {
|
|
13594
13660
|
try {
|
|
13595
|
-
|
|
13661
|
+
execSync20(`docker sandbox exec ${sandboxName} which codex`, {
|
|
13596
13662
|
stdio: ["pipe", "pipe", "pipe"],
|
|
13597
13663
|
timeout: 5000
|
|
13598
13664
|
});
|
|
@@ -13600,7 +13666,7 @@ async function ensureCodexInSandbox(sandboxName) {
|
|
|
13600
13666
|
process.stderr.write(`Installing codex in sandbox...
|
|
13601
13667
|
`);
|
|
13602
13668
|
try {
|
|
13603
|
-
|
|
13669
|
+
execSync20(`docker sandbox exec ${sandboxName} npm install -g @openai/codex`, { stdio: "inherit", timeout: 120000 });
|
|
13604
13670
|
} catch {
|
|
13605
13671
|
process.stderr.write(`${red2("✗")} Failed to install codex in sandbox.
|
|
13606
13672
|
`);
|
|
@@ -13609,7 +13675,7 @@ async function ensureCodexInSandbox(sandboxName) {
|
|
|
13609
13675
|
}
|
|
13610
13676
|
function isSandboxAlive(name) {
|
|
13611
13677
|
try {
|
|
13612
|
-
const output =
|
|
13678
|
+
const output = execSync20("docker sandbox ls", {
|
|
13613
13679
|
encoding: "utf-8",
|
|
13614
13680
|
stdio: ["pipe", "pipe", "pipe"],
|
|
13615
13681
|
timeout: 5000
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@locusai/cli",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.2",
|
|
4
4
|
"description": "GitHub-native AI engineering assistant",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"license": "MIT",
|
|
37
37
|
"dependencies": {},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@locusai/sdk": "^0.22.
|
|
39
|
+
"@locusai/sdk": "^0.22.2",
|
|
40
40
|
"@types/bun": "latest",
|
|
41
41
|
"typescript": "^5.8.3"
|
|
42
42
|
},
|