@locusai/cli 0.22.1 → 0.22.3
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 -131
- 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,30 @@ ${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
|
+
const containerPath = execSync2(`docker sandbox exec ${sandboxName} pwd`, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
|
|
1041
|
+
if (!containerPath) {
|
|
1042
|
+
log.debug("Container pwd returned empty result");
|
|
1043
|
+
return null;
|
|
1044
|
+
}
|
|
1045
|
+
if (containerPath === hostProjectRoot) {
|
|
1046
|
+
log.debug("Container workdir matches host path", { hostProjectRoot });
|
|
1047
|
+
return null;
|
|
1048
|
+
}
|
|
1049
|
+
log.debug("Detected container workdir differs from host", {
|
|
1050
|
+
hostProjectRoot,
|
|
1051
|
+
containerWorkdir: containerPath
|
|
1052
|
+
});
|
|
1053
|
+
return containerPath;
|
|
1054
|
+
} catch (err) {
|
|
1055
|
+
log.debug("Container workdir detection via pwd failed", {
|
|
1056
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
return null;
|
|
1060
|
+
}
|
|
1036
1061
|
function waitForEnter() {
|
|
1037
1062
|
return new Promise((resolve) => {
|
|
1038
1063
|
const rl = createInterface({
|
|
@@ -1062,7 +1087,7 @@ __export(exports_upgrade, {
|
|
|
1062
1087
|
fetchLatestVersion: () => fetchLatestVersion,
|
|
1063
1088
|
compareSemver: () => compareSemver
|
|
1064
1089
|
});
|
|
1065
|
-
import { execSync as
|
|
1090
|
+
import { execSync as execSync3 } from "node:child_process";
|
|
1066
1091
|
function compareSemver(a, b) {
|
|
1067
1092
|
const partsA = a.replace(/^v/, "").split(".").map(Number);
|
|
1068
1093
|
const partsB = b.replace(/^v/, "").split(".").map(Number);
|
|
@@ -1078,7 +1103,7 @@ function compareSemver(a, b) {
|
|
|
1078
1103
|
}
|
|
1079
1104
|
function fetchLatestVersion() {
|
|
1080
1105
|
try {
|
|
1081
|
-
const result =
|
|
1106
|
+
const result = execSync3(`npm view ${PACKAGE_NAME} version`, {
|
|
1082
1107
|
encoding: "utf-8",
|
|
1083
1108
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1084
1109
|
timeout: 15000
|
|
@@ -1090,14 +1115,14 @@ function fetchLatestVersion() {
|
|
|
1090
1115
|
}
|
|
1091
1116
|
function installVersion(version) {
|
|
1092
1117
|
try {
|
|
1093
|
-
|
|
1118
|
+
execSync3("npm cache clean --force", {
|
|
1094
1119
|
encoding: "utf-8",
|
|
1095
1120
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1096
1121
|
timeout: 30000
|
|
1097
1122
|
});
|
|
1098
1123
|
} catch {}
|
|
1099
1124
|
try {
|
|
1100
|
-
|
|
1125
|
+
execSync3(`npm install -g ${PACKAGE_NAME}@${version}`, {
|
|
1101
1126
|
encoding: "utf-8",
|
|
1102
1127
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1103
1128
|
timeout: 120000
|
|
@@ -1109,7 +1134,7 @@ function installVersion(version) {
|
|
|
1109
1134
|
}
|
|
1110
1135
|
function verifyInstalled(expectedVersion) {
|
|
1111
1136
|
try {
|
|
1112
|
-
const installed =
|
|
1137
|
+
const installed = execSync3(`npm list -g ${PACKAGE_NAME} --depth=0 --json`, {
|
|
1113
1138
|
encoding: "utf-8",
|
|
1114
1139
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1115
1140
|
timeout: 1e4
|
|
@@ -1473,7 +1498,7 @@ var init_ecosystem = __esm(() => {
|
|
|
1473
1498
|
// src/core/github.ts
|
|
1474
1499
|
import {
|
|
1475
1500
|
execFileSync,
|
|
1476
|
-
execSync as
|
|
1501
|
+
execSync as execSync4
|
|
1477
1502
|
} from "node:child_process";
|
|
1478
1503
|
function gh(args, options = {}) {
|
|
1479
1504
|
const log = getLogger();
|
|
@@ -1482,7 +1507,7 @@ function gh(args, options = {}) {
|
|
|
1482
1507
|
log.debug(`gh ${args}`, { cwd });
|
|
1483
1508
|
const startTime = Date.now();
|
|
1484
1509
|
try {
|
|
1485
|
-
const result =
|
|
1510
|
+
const result = execSync4(`gh ${args}`, {
|
|
1486
1511
|
cwd,
|
|
1487
1512
|
encoding: "utf-8",
|
|
1488
1513
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -2231,7 +2256,7 @@ var exports_create = {};
|
|
|
2231
2256
|
__export(exports_create, {
|
|
2232
2257
|
createCommand: () => createCommand
|
|
2233
2258
|
});
|
|
2234
|
-
import { execSync as
|
|
2259
|
+
import { execSync as execSync5 } from "node:child_process";
|
|
2235
2260
|
import { existsSync as existsSync7, mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
2236
2261
|
import { join as join7 } from "node:path";
|
|
2237
2262
|
function validateName(name) {
|
|
@@ -2250,7 +2275,7 @@ function capitalize(str) {
|
|
|
2250
2275
|
}
|
|
2251
2276
|
function checkNpmExists(fullName) {
|
|
2252
2277
|
try {
|
|
2253
|
-
|
|
2278
|
+
execSync5(`npm view ${fullName} version 2>/dev/null`, {
|
|
2254
2279
|
stdio: "pipe",
|
|
2255
2280
|
timeout: 1e4
|
|
2256
2281
|
});
|
|
@@ -2390,7 +2415,7 @@ function printHelp(): void {
|
|
|
2390
2415
|
}
|
|
2391
2416
|
`;
|
|
2392
2417
|
}
|
|
2393
|
-
function generateReadme(name,
|
|
2418
|
+
function generateReadme(name, description) {
|
|
2394
2419
|
return `# @locusai/locus-${name}
|
|
2395
2420
|
|
|
2396
2421
|
${description}
|
|
@@ -2545,7 +2570,7 @@ ${bold2("Creating package:")} ${cyan2(fullNpmName)}
|
|
|
2545
2570
|
writeFileSync5(join7(packagesDir, "src", "index.ts"), generateIndexTs(name), "utf-8");
|
|
2546
2571
|
process.stderr.write(`${green("✓")} Generated src/index.ts
|
|
2547
2572
|
`);
|
|
2548
|
-
writeFileSync5(join7(packagesDir, "README.md"), generateReadme(name,
|
|
2573
|
+
writeFileSync5(join7(packagesDir, "README.md"), generateReadme(name, description), "utf-8");
|
|
2549
2574
|
process.stderr.write(`${green("✓")} Generated README.md
|
|
2550
2575
|
`);
|
|
2551
2576
|
process.stderr.write(`
|
|
@@ -3690,7 +3715,7 @@ var init_stream_renderer = __esm(() => {
|
|
|
3690
3715
|
});
|
|
3691
3716
|
|
|
3692
3717
|
// src/repl/clipboard.ts
|
|
3693
|
-
import { execSync as
|
|
3718
|
+
import { execSync as execSync6 } from "node:child_process";
|
|
3694
3719
|
import { existsSync as existsSync12, mkdirSync as mkdirSync8 } from "node:fs";
|
|
3695
3720
|
import { tmpdir } from "node:os";
|
|
3696
3721
|
import { join as join11 } from "node:path";
|
|
@@ -3729,7 +3754,7 @@ function readMacOSClipboardImage() {
|
|
|
3729
3754
|
`return "ok"`
|
|
3730
3755
|
].join(`
|
|
3731
3756
|
`);
|
|
3732
|
-
const result =
|
|
3757
|
+
const result = execSync6("osascript", {
|
|
3733
3758
|
input: script,
|
|
3734
3759
|
encoding: "utf-8",
|
|
3735
3760
|
timeout: 5000,
|
|
@@ -3743,13 +3768,13 @@ function readMacOSClipboardImage() {
|
|
|
3743
3768
|
}
|
|
3744
3769
|
function readLinuxClipboardImage() {
|
|
3745
3770
|
try {
|
|
3746
|
-
const targets =
|
|
3771
|
+
const targets = execSync6("xclip -selection clipboard -t TARGETS -o 2>/dev/null", { encoding: "utf-8", timeout: 3000 });
|
|
3747
3772
|
if (!targets.includes("image/png")) {
|
|
3748
3773
|
return null;
|
|
3749
3774
|
}
|
|
3750
3775
|
ensureStableDir();
|
|
3751
3776
|
const destPath = join11(STABLE_DIR, `clipboard-${Date.now()}.png`);
|
|
3752
|
-
|
|
3777
|
+
execSync6(`xclip -selection clipboard -t image/png -o > "${destPath}" 2>/dev/null`, { timeout: 5000 });
|
|
3753
3778
|
if (existsSync12(destPath)) {
|
|
3754
3779
|
return destPath;
|
|
3755
3780
|
}
|
|
@@ -4770,7 +4795,7 @@ __export(exports_claude, {
|
|
|
4770
4795
|
buildClaudeArgs: () => buildClaudeArgs,
|
|
4771
4796
|
ClaudeRunner: () => ClaudeRunner
|
|
4772
4797
|
});
|
|
4773
|
-
import { execSync as
|
|
4798
|
+
import { execSync as execSync7, spawn as spawn2 } from "node:child_process";
|
|
4774
4799
|
function buildClaudeArgs(options) {
|
|
4775
4800
|
const args = ["--dangerously-skip-permissions", "--no-session-persistence"];
|
|
4776
4801
|
if (options.model) {
|
|
@@ -4788,7 +4813,7 @@ class ClaudeRunner {
|
|
|
4788
4813
|
aborted = false;
|
|
4789
4814
|
async isAvailable() {
|
|
4790
4815
|
try {
|
|
4791
|
-
|
|
4816
|
+
execSync7("claude --version", {
|
|
4792
4817
|
encoding: "utf-8",
|
|
4793
4818
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4794
4819
|
});
|
|
@@ -4799,7 +4824,7 @@ class ClaudeRunner {
|
|
|
4799
4824
|
}
|
|
4800
4825
|
async getVersion() {
|
|
4801
4826
|
try {
|
|
4802
|
-
const output =
|
|
4827
|
+
const output = execSync7("claude --version", {
|
|
4803
4828
|
encoding: "utf-8",
|
|
4804
4829
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4805
4830
|
}).trim();
|
|
@@ -5141,13 +5166,13 @@ function backupIgnoredFiles(projectRoot) {
|
|
|
5141
5166
|
}
|
|
5142
5167
|
};
|
|
5143
5168
|
}
|
|
5144
|
-
async function enforceSandboxIgnore(sandboxName, projectRoot) {
|
|
5169
|
+
async function enforceSandboxIgnore(sandboxName, projectRoot, containerWorkdir) {
|
|
5145
5170
|
const log = getLogger();
|
|
5146
5171
|
const ignorePath = join13(projectRoot, ".sandboxignore");
|
|
5147
5172
|
const rules = parseIgnoreFile(ignorePath);
|
|
5148
5173
|
if (rules.length === 0)
|
|
5149
5174
|
return;
|
|
5150
|
-
const script = buildCleanupScript(rules, projectRoot);
|
|
5175
|
+
const script = buildCleanupScript(rules, containerWorkdir ?? projectRoot);
|
|
5151
5176
|
if (!script)
|
|
5152
5177
|
return;
|
|
5153
5178
|
log.debug("Enforcing .sandboxignore", {
|
|
@@ -5187,11 +5212,13 @@ import { spawn as spawn3 } from "node:child_process";
|
|
|
5187
5212
|
|
|
5188
5213
|
class SandboxedClaudeRunner {
|
|
5189
5214
|
sandboxName;
|
|
5215
|
+
containerWorkdir;
|
|
5190
5216
|
name = "claude-sandboxed";
|
|
5191
5217
|
process = null;
|
|
5192
5218
|
aborted = false;
|
|
5193
|
-
constructor(sandboxName) {
|
|
5219
|
+
constructor(sandboxName, containerWorkdir) {
|
|
5194
5220
|
this.sandboxName = sandboxName;
|
|
5221
|
+
this.containerWorkdir = containerWorkdir;
|
|
5195
5222
|
}
|
|
5196
5223
|
async isAvailable() {
|
|
5197
5224
|
const { ClaudeRunner: ClaudeRunner2 } = await Promise.resolve().then(() => (init_claude(), exports_claude));
|
|
@@ -5217,13 +5244,14 @@ class SandboxedClaudeRunner {
|
|
|
5217
5244
|
const claudeArgs = ["-p", options.prompt, ...buildClaudeArgs(options)];
|
|
5218
5245
|
options.onStatusChange?.("Syncing sandbox...");
|
|
5219
5246
|
const backup = backupIgnoredFiles(options.cwd);
|
|
5220
|
-
await enforceSandboxIgnore(this.sandboxName, options.cwd);
|
|
5247
|
+
await enforceSandboxIgnore(this.sandboxName, options.cwd, this.containerWorkdir);
|
|
5221
5248
|
options.onStatusChange?.("Thinking...");
|
|
5249
|
+
const workdir = this.containerWorkdir ?? options.cwd;
|
|
5222
5250
|
const dockerArgs = [
|
|
5223
5251
|
"sandbox",
|
|
5224
5252
|
"exec",
|
|
5225
5253
|
"-w",
|
|
5226
|
-
|
|
5254
|
+
workdir,
|
|
5227
5255
|
this.sandboxName,
|
|
5228
5256
|
"claude",
|
|
5229
5257
|
...claudeArgs
|
|
@@ -5388,7 +5416,7 @@ var init_claude_sandbox = __esm(() => {
|
|
|
5388
5416
|
});
|
|
5389
5417
|
|
|
5390
5418
|
// src/ai/codex.ts
|
|
5391
|
-
import { execSync as
|
|
5419
|
+
import { execSync as execSync8, spawn as spawn4 } from "node:child_process";
|
|
5392
5420
|
function buildCodexArgs(model) {
|
|
5393
5421
|
const args = ["exec", "--full-auto", "--skip-git-repo-check", "--json"];
|
|
5394
5422
|
if (model) {
|
|
@@ -5404,7 +5432,7 @@ class CodexRunner {
|
|
|
5404
5432
|
aborted = false;
|
|
5405
5433
|
async isAvailable() {
|
|
5406
5434
|
try {
|
|
5407
|
-
|
|
5435
|
+
execSync8("codex --version", {
|
|
5408
5436
|
encoding: "utf-8",
|
|
5409
5437
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5410
5438
|
});
|
|
@@ -5415,7 +5443,7 @@ class CodexRunner {
|
|
|
5415
5443
|
}
|
|
5416
5444
|
async getVersion() {
|
|
5417
5445
|
try {
|
|
5418
|
-
const output =
|
|
5446
|
+
const output = execSync8("codex --version", {
|
|
5419
5447
|
encoding: "utf-8",
|
|
5420
5448
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5421
5449
|
}).trim();
|
|
@@ -5567,12 +5595,14 @@ import { spawn as spawn5 } from "node:child_process";
|
|
|
5567
5595
|
|
|
5568
5596
|
class SandboxedCodexRunner {
|
|
5569
5597
|
sandboxName;
|
|
5598
|
+
containerWorkdir;
|
|
5570
5599
|
name = "codex-sandboxed";
|
|
5571
5600
|
process = null;
|
|
5572
5601
|
aborted = false;
|
|
5573
5602
|
codexInstalled = false;
|
|
5574
|
-
constructor(sandboxName) {
|
|
5603
|
+
constructor(sandboxName, containerWorkdir) {
|
|
5575
5604
|
this.sandboxName = sandboxName;
|
|
5605
|
+
this.containerWorkdir = containerWorkdir;
|
|
5576
5606
|
}
|
|
5577
5607
|
async isAvailable() {
|
|
5578
5608
|
const delegate = new CodexRunner;
|
|
@@ -5596,19 +5626,20 @@ class SandboxedCodexRunner {
|
|
|
5596
5626
|
const codexArgs = buildCodexArgs(options.model);
|
|
5597
5627
|
options.onStatusChange?.("Syncing sandbox...");
|
|
5598
5628
|
const backup = backupIgnoredFiles(options.cwd);
|
|
5599
|
-
await enforceSandboxIgnore(this.sandboxName, options.cwd);
|
|
5629
|
+
await enforceSandboxIgnore(this.sandboxName, options.cwd, this.containerWorkdir);
|
|
5600
5630
|
if (!this.codexInstalled) {
|
|
5601
5631
|
options.onStatusChange?.("Checking codex...");
|
|
5602
5632
|
await this.ensureCodexInstalled(this.sandboxName);
|
|
5603
5633
|
this.codexInstalled = true;
|
|
5604
5634
|
}
|
|
5605
5635
|
options.onStatusChange?.("Thinking...");
|
|
5636
|
+
const workdir = this.containerWorkdir ?? options.cwd;
|
|
5606
5637
|
const dockerArgs = [
|
|
5607
5638
|
"sandbox",
|
|
5608
5639
|
"exec",
|
|
5609
5640
|
"-i",
|
|
5610
5641
|
"-w",
|
|
5611
|
-
|
|
5642
|
+
workdir,
|
|
5612
5643
|
this.sandboxName,
|
|
5613
5644
|
"codex",
|
|
5614
5645
|
...codexArgs
|
|
@@ -5789,12 +5820,12 @@ async function createRunnerAsync(provider, sandboxed) {
|
|
|
5789
5820
|
throw new Error(`Unknown AI provider: ${provider}`);
|
|
5790
5821
|
}
|
|
5791
5822
|
}
|
|
5792
|
-
function createUserManagedSandboxRunner(provider, sandboxName) {
|
|
5823
|
+
function createUserManagedSandboxRunner(provider, sandboxName, containerWorkdir) {
|
|
5793
5824
|
switch (provider) {
|
|
5794
5825
|
case "claude":
|
|
5795
|
-
return new SandboxedClaudeRunner(sandboxName);
|
|
5826
|
+
return new SandboxedClaudeRunner(sandboxName, containerWorkdir);
|
|
5796
5827
|
case "codex":
|
|
5797
|
-
return new SandboxedCodexRunner(sandboxName);
|
|
5828
|
+
return new SandboxedCodexRunner(sandboxName, containerWorkdir);
|
|
5798
5829
|
default:
|
|
5799
5830
|
throw new Error(`Unknown AI provider: ${provider}`);
|
|
5800
5831
|
}
|
|
@@ -5905,7 +5936,7 @@ ${red2("✗")} ${dim2("Force exit.")}\r
|
|
|
5905
5936
|
exitCode: 1
|
|
5906
5937
|
};
|
|
5907
5938
|
}
|
|
5908
|
-
runner = createUserManagedSandboxRunner(resolvedProvider, options.sandboxName);
|
|
5939
|
+
runner = createUserManagedSandboxRunner(resolvedProvider, options.sandboxName, options.containerWorkdir);
|
|
5909
5940
|
} else {
|
|
5910
5941
|
runner = await createRunnerAsync(resolvedProvider, false);
|
|
5911
5942
|
}
|
|
@@ -6240,7 +6271,8 @@ async function issueCreate(projectRoot, parsed) {
|
|
|
6240
6271
|
silent: true,
|
|
6241
6272
|
activity: "generating issue",
|
|
6242
6273
|
sandboxed: config.sandbox.enabled,
|
|
6243
|
-
sandboxName: getModelSandboxName(config.sandbox, config.ai.model, config.ai.provider)
|
|
6274
|
+
sandboxName: getModelSandboxName(config.sandbox, config.ai.model, config.ai.provider),
|
|
6275
|
+
containerWorkdir: config.sandbox.containerWorkdir
|
|
6244
6276
|
});
|
|
6245
6277
|
if (!aiResult.success && !aiResult.interrupted) {
|
|
6246
6278
|
process.stderr.write(`${red2("✗")} Failed to generate issue: ${aiResult.error}
|
|
@@ -7443,7 +7475,7 @@ var init_sprint = __esm(() => {
|
|
|
7443
7475
|
});
|
|
7444
7476
|
|
|
7445
7477
|
// src/core/prompt-builder.ts
|
|
7446
|
-
import { execSync as
|
|
7478
|
+
import { execSync as execSync9 } from "node:child_process";
|
|
7447
7479
|
import { existsSync as existsSync15, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "node:fs";
|
|
7448
7480
|
import { join as join14 } from "node:path";
|
|
7449
7481
|
function buildExecutionPrompt(ctx) {
|
|
@@ -7592,7 +7624,7 @@ ${parts.join(`
|
|
|
7592
7624
|
function buildRepoContext(projectRoot) {
|
|
7593
7625
|
const parts = [];
|
|
7594
7626
|
try {
|
|
7595
|
-
const tree =
|
|
7627
|
+
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
7628
|
if (tree) {
|
|
7597
7629
|
parts.push(`<file-tree>
|
|
7598
7630
|
\`\`\`
|
|
@@ -7602,7 +7634,7 @@ ${tree}
|
|
|
7602
7634
|
}
|
|
7603
7635
|
} catch {}
|
|
7604
7636
|
try {
|
|
7605
|
-
const gitLog =
|
|
7637
|
+
const gitLog = execSync9("git log --oneline -10", {
|
|
7606
7638
|
cwd: projectRoot,
|
|
7607
7639
|
encoding: "utf-8",
|
|
7608
7640
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -7616,7 +7648,7 @@ ${gitLog}
|
|
|
7616
7648
|
}
|
|
7617
7649
|
} catch {}
|
|
7618
7650
|
try {
|
|
7619
|
-
const branch =
|
|
7651
|
+
const branch = execSync9("git rev-parse --abbrev-ref HEAD", {
|
|
7620
7652
|
cwd: projectRoot,
|
|
7621
7653
|
encoding: "utf-8",
|
|
7622
7654
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -7872,7 +7904,7 @@ var init_diff_renderer = __esm(() => {
|
|
|
7872
7904
|
});
|
|
7873
7905
|
|
|
7874
7906
|
// src/repl/commands.ts
|
|
7875
|
-
import { execSync as
|
|
7907
|
+
import { execSync as execSync10 } from "node:child_process";
|
|
7876
7908
|
function getSlashCommands() {
|
|
7877
7909
|
return [
|
|
7878
7910
|
{
|
|
@@ -8070,7 +8102,7 @@ function cmdModel(args, ctx) {
|
|
|
8070
8102
|
}
|
|
8071
8103
|
function cmdDiff(_args, ctx) {
|
|
8072
8104
|
try {
|
|
8073
|
-
const diff =
|
|
8105
|
+
const diff = execSync10("git diff", {
|
|
8074
8106
|
cwd: ctx.projectRoot,
|
|
8075
8107
|
encoding: "utf-8",
|
|
8076
8108
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -8106,7 +8138,7 @@ function cmdDiff(_args, ctx) {
|
|
|
8106
8138
|
}
|
|
8107
8139
|
function cmdUndo(_args, ctx) {
|
|
8108
8140
|
try {
|
|
8109
|
-
const status =
|
|
8141
|
+
const status = execSync10("git status --porcelain", {
|
|
8110
8142
|
cwd: ctx.projectRoot,
|
|
8111
8143
|
encoding: "utf-8",
|
|
8112
8144
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -8116,7 +8148,7 @@ function cmdUndo(_args, ctx) {
|
|
|
8116
8148
|
`);
|
|
8117
8149
|
return;
|
|
8118
8150
|
}
|
|
8119
|
-
|
|
8151
|
+
execSync10("git checkout .", {
|
|
8120
8152
|
cwd: ctx.projectRoot,
|
|
8121
8153
|
encoding: "utf-8",
|
|
8122
8154
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -8494,7 +8526,7 @@ var init_session_manager = __esm(() => {
|
|
|
8494
8526
|
});
|
|
8495
8527
|
|
|
8496
8528
|
// src/repl/voice.ts
|
|
8497
|
-
import { execSync as
|
|
8529
|
+
import { execSync as execSync11, spawn as spawn6 } from "node:child_process";
|
|
8498
8530
|
import { existsSync as existsSync18, mkdirSync as mkdirSync13, unlinkSync as unlinkSync4 } from "node:fs";
|
|
8499
8531
|
import { cpus, homedir as homedir4, platform, tmpdir as tmpdir4 } from "node:os";
|
|
8500
8532
|
import { join as join18 } from "node:path";
|
|
@@ -8504,7 +8536,7 @@ function getWhisperModelPath() {
|
|
|
8504
8536
|
function commandExists(cmd) {
|
|
8505
8537
|
try {
|
|
8506
8538
|
const which = platform() === "win32" ? "where" : "which";
|
|
8507
|
-
|
|
8539
|
+
execSync11(`${which} ${cmd}`, { stdio: "pipe" });
|
|
8508
8540
|
return true;
|
|
8509
8541
|
} catch {
|
|
8510
8542
|
return false;
|
|
@@ -8630,22 +8662,22 @@ function installSox(pm) {
|
|
|
8630
8662
|
try {
|
|
8631
8663
|
switch (pm) {
|
|
8632
8664
|
case "brew":
|
|
8633
|
-
|
|
8665
|
+
execSync11("brew install sox", { stdio: "inherit", timeout: 300000 });
|
|
8634
8666
|
break;
|
|
8635
8667
|
case "apt":
|
|
8636
|
-
|
|
8668
|
+
execSync11("sudo apt-get install -y sox", {
|
|
8637
8669
|
stdio: "inherit",
|
|
8638
8670
|
timeout: 300000
|
|
8639
8671
|
});
|
|
8640
8672
|
break;
|
|
8641
8673
|
case "dnf":
|
|
8642
|
-
|
|
8674
|
+
execSync11("sudo dnf install -y sox", {
|
|
8643
8675
|
stdio: "inherit",
|
|
8644
8676
|
timeout: 300000
|
|
8645
8677
|
});
|
|
8646
8678
|
break;
|
|
8647
8679
|
case "pacman":
|
|
8648
|
-
|
|
8680
|
+
execSync11("sudo pacman -S --noconfirm sox", {
|
|
8649
8681
|
stdio: "inherit",
|
|
8650
8682
|
timeout: 300000
|
|
8651
8683
|
});
|
|
@@ -8659,7 +8691,7 @@ function installSox(pm) {
|
|
|
8659
8691
|
function installWhisperCpp(pm) {
|
|
8660
8692
|
if (pm === "brew") {
|
|
8661
8693
|
try {
|
|
8662
|
-
|
|
8694
|
+
execSync11("brew install whisper-cpp", {
|
|
8663
8695
|
stdio: "inherit",
|
|
8664
8696
|
timeout: 300000
|
|
8665
8697
|
});
|
|
@@ -8681,19 +8713,19 @@ function ensureBuildDeps(pm) {
|
|
|
8681
8713
|
try {
|
|
8682
8714
|
switch (pm) {
|
|
8683
8715
|
case "apt":
|
|
8684
|
-
|
|
8716
|
+
execSync11("sudo apt-get install -y cmake g++ make git", {
|
|
8685
8717
|
stdio: "inherit",
|
|
8686
8718
|
timeout: 300000
|
|
8687
8719
|
});
|
|
8688
8720
|
break;
|
|
8689
8721
|
case "dnf":
|
|
8690
|
-
|
|
8722
|
+
execSync11("sudo dnf install -y cmake gcc-c++ make git", {
|
|
8691
8723
|
stdio: "inherit",
|
|
8692
8724
|
timeout: 300000
|
|
8693
8725
|
});
|
|
8694
8726
|
break;
|
|
8695
8727
|
case "pacman":
|
|
8696
|
-
|
|
8728
|
+
execSync11("sudo pacman -S --noconfirm cmake gcc make git", {
|
|
8697
8729
|
stdio: "inherit",
|
|
8698
8730
|
timeout: 300000
|
|
8699
8731
|
});
|
|
@@ -8719,17 +8751,17 @@ function buildWhisperFromSource(pm) {
|
|
|
8719
8751
|
mkdirSync13(LOCUS_BIN_DIR, { recursive: true });
|
|
8720
8752
|
out.write(` ${dim2("Cloning whisper.cpp...")}
|
|
8721
8753
|
`);
|
|
8722
|
-
|
|
8754
|
+
execSync11(`git clone --depth 1 https://github.com/ggerganov/whisper.cpp.git "${join18(buildDir, "whisper.cpp")}"`, { stdio: ["pipe", "pipe", "pipe"], timeout: 120000 });
|
|
8723
8755
|
const srcDir = join18(buildDir, "whisper.cpp");
|
|
8724
8756
|
const numCpus = cpus().length || 2;
|
|
8725
8757
|
out.write(` ${dim2("Building whisper.cpp (this may take a few minutes)...")}
|
|
8726
8758
|
`);
|
|
8727
|
-
|
|
8759
|
+
execSync11("cmake -B build -DCMAKE_BUILD_TYPE=Release", {
|
|
8728
8760
|
cwd: srcDir,
|
|
8729
8761
|
stdio: ["pipe", "pipe", "pipe"],
|
|
8730
8762
|
timeout: 120000
|
|
8731
8763
|
});
|
|
8732
|
-
|
|
8764
|
+
execSync11(`cmake --build build --config Release -j${numCpus}`, {
|
|
8733
8765
|
cwd: srcDir,
|
|
8734
8766
|
stdio: ["pipe", "pipe", "pipe"],
|
|
8735
8767
|
timeout: 600000
|
|
@@ -8741,7 +8773,7 @@ function buildWhisperFromSource(pm) {
|
|
|
8741
8773
|
];
|
|
8742
8774
|
for (const candidate of binaryCandidates) {
|
|
8743
8775
|
if (existsSync18(candidate)) {
|
|
8744
|
-
|
|
8776
|
+
execSync11(`cp "${candidate}" "${destPath}" && chmod +x "${destPath}"`, {
|
|
8745
8777
|
stdio: "pipe"
|
|
8746
8778
|
});
|
|
8747
8779
|
return true;
|
|
@@ -8756,7 +8788,7 @@ function buildWhisperFromSource(pm) {
|
|
|
8756
8788
|
return false;
|
|
8757
8789
|
} finally {
|
|
8758
8790
|
try {
|
|
8759
|
-
|
|
8791
|
+
execSync11(`rm -rf "${buildDir}"`, { stdio: "pipe" });
|
|
8760
8792
|
} catch {}
|
|
8761
8793
|
}
|
|
8762
8794
|
}
|
|
@@ -8827,12 +8859,12 @@ function downloadModel() {
|
|
|
8827
8859
|
`);
|
|
8828
8860
|
try {
|
|
8829
8861
|
if (commandExists("curl")) {
|
|
8830
|
-
|
|
8862
|
+
execSync11(`curl -L -o "${modelPath}" "${url}"`, {
|
|
8831
8863
|
stdio: ["pipe", "pipe", "pipe"],
|
|
8832
8864
|
timeout: 300000
|
|
8833
8865
|
});
|
|
8834
8866
|
} else if (commandExists("wget")) {
|
|
8835
|
-
|
|
8867
|
+
execSync11(`wget -O "${modelPath}" "${url}"`, {
|
|
8836
8868
|
stdio: ["pipe", "pipe", "pipe"],
|
|
8837
8869
|
timeout: 300000
|
|
8838
8870
|
});
|
|
@@ -9030,7 +9062,7 @@ var init_voice = __esm(() => {
|
|
|
9030
9062
|
});
|
|
9031
9063
|
|
|
9032
9064
|
// src/repl/repl.ts
|
|
9033
|
-
import { execSync as
|
|
9065
|
+
import { execSync as execSync12 } from "node:child_process";
|
|
9034
9066
|
async function startRepl(options) {
|
|
9035
9067
|
const { projectRoot, config } = options;
|
|
9036
9068
|
const sessionManager = new SessionManager(projectRoot);
|
|
@@ -9048,7 +9080,7 @@ async function startRepl(options) {
|
|
|
9048
9080
|
} else {
|
|
9049
9081
|
let branch = "main";
|
|
9050
9082
|
try {
|
|
9051
|
-
branch =
|
|
9083
|
+
branch = execSync12("git rev-parse --abbrev-ref HEAD", {
|
|
9052
9084
|
cwd: projectRoot,
|
|
9053
9085
|
encoding: "utf-8",
|
|
9054
9086
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9100,7 +9132,7 @@ async function runInteractiveRepl(session, sessionManager, options) {
|
|
|
9100
9132
|
const provider = inferProviderFromModel(config.ai.model) || config.ai.provider;
|
|
9101
9133
|
const sandboxName = getProviderSandboxName(config.sandbox, provider);
|
|
9102
9134
|
if (sandboxName) {
|
|
9103
|
-
sandboxRunner = createUserManagedSandboxRunner(provider, sandboxName);
|
|
9135
|
+
sandboxRunner = createUserManagedSandboxRunner(provider, sandboxName, config.sandbox.containerWorkdir);
|
|
9104
9136
|
process.stderr.write(`${dim2("Using")} ${dim2(provider)} ${dim2("sandbox")} ${dim2(sandboxName)}
|
|
9105
9137
|
`);
|
|
9106
9138
|
} else {
|
|
@@ -9151,7 +9183,7 @@ async function runInteractiveRepl(session, sessionManager, options) {
|
|
|
9151
9183
|
if (providerChanged && config.sandbox.enabled) {
|
|
9152
9184
|
const sandboxName = getProviderSandboxName(config.sandbox, inferredProvider);
|
|
9153
9185
|
if (sandboxName) {
|
|
9154
|
-
sandboxRunner = createUserManagedSandboxRunner(inferredProvider, sandboxName);
|
|
9186
|
+
sandboxRunner = createUserManagedSandboxRunner(inferredProvider, sandboxName, config.sandbox.containerWorkdir);
|
|
9155
9187
|
process.stderr.write(`${dim2("Switched sandbox agent to")} ${dim2(inferredProvider)} ${dim2(`(${sandboxName})`)}
|
|
9156
9188
|
`);
|
|
9157
9189
|
} else {
|
|
@@ -9279,6 +9311,7 @@ async function executeAITurn(prompt, session, options, verbose = false, runner)
|
|
|
9279
9311
|
verbose,
|
|
9280
9312
|
sandboxed: config.sandbox.enabled,
|
|
9281
9313
|
sandboxName,
|
|
9314
|
+
containerWorkdir: config.sandbox.containerWorkdir,
|
|
9282
9315
|
runner
|
|
9283
9316
|
});
|
|
9284
9317
|
if (aiResult.interrupted) {
|
|
@@ -9483,7 +9516,7 @@ async function handleJsonStream(projectRoot, config, args, sessionId) {
|
|
|
9483
9516
|
try {
|
|
9484
9517
|
const fullPrompt = buildReplPrompt(prompt, projectRoot, config);
|
|
9485
9518
|
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);
|
|
9519
|
+
const runner = config.sandbox.enabled ? sandboxName ? createUserManagedSandboxRunner(config.ai.provider, sandboxName, config.sandbox.containerWorkdir) : null : await createRunnerAsync(config.ai.provider, false);
|
|
9487
9520
|
if (!runner) {
|
|
9488
9521
|
const mismatch = checkProviderSandboxMismatch(config.sandbox, config.ai.model, config.ai.provider);
|
|
9489
9522
|
stream.emitError(mismatch ?? `No sandbox configured for "${config.ai.provider}". Run "locus sandbox" to create one.`, false);
|
|
@@ -9536,11 +9569,11 @@ var init_exec = __esm(() => {
|
|
|
9536
9569
|
});
|
|
9537
9570
|
|
|
9538
9571
|
// src/core/submodule.ts
|
|
9539
|
-
import { execSync as
|
|
9572
|
+
import { execSync as execSync13 } from "node:child_process";
|
|
9540
9573
|
import { existsSync as existsSync19 } from "node:fs";
|
|
9541
9574
|
import { join as join19 } from "node:path";
|
|
9542
9575
|
function git2(args, cwd) {
|
|
9543
|
-
return
|
|
9576
|
+
return execSync13(`git ${args}`, {
|
|
9544
9577
|
cwd,
|
|
9545
9578
|
encoding: "utf-8",
|
|
9546
9579
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9608,7 +9641,7 @@ function commitDirtySubmodules(cwd, issueNumber, issueTitle) {
|
|
|
9608
9641
|
const message = `chore: complete #${issueNumber} - ${issueTitle}
|
|
9609
9642
|
|
|
9610
9643
|
Co-Authored-By: LocusAgent <agent@locusai.team>`;
|
|
9611
|
-
|
|
9644
|
+
execSync13("git commit -F -", {
|
|
9612
9645
|
input: message,
|
|
9613
9646
|
cwd: sub.absolutePath,
|
|
9614
9647
|
encoding: "utf-8",
|
|
@@ -9695,7 +9728,7 @@ var init_submodule = __esm(() => {
|
|
|
9695
9728
|
});
|
|
9696
9729
|
|
|
9697
9730
|
// src/core/agent.ts
|
|
9698
|
-
import { execSync as
|
|
9731
|
+
import { execSync as execSync14 } from "node:child_process";
|
|
9699
9732
|
async function executeIssue(projectRoot, options) {
|
|
9700
9733
|
const log = getLogger();
|
|
9701
9734
|
const timer = createTimer();
|
|
@@ -9724,7 +9757,7 @@ ${cyan2("●")} ${bold2(`#${issueNumber}`)} ${issue.title}
|
|
|
9724
9757
|
}
|
|
9725
9758
|
let issueComments = [];
|
|
9726
9759
|
try {
|
|
9727
|
-
const commentsRaw =
|
|
9760
|
+
const commentsRaw = execSync14(`gh issue view ${issueNumber} --json comments --jq '.comments[].body'`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
9728
9761
|
if (commentsRaw) {
|
|
9729
9762
|
issueComments = commentsRaw.split(`
|
|
9730
9763
|
`).filter(Boolean);
|
|
@@ -9761,7 +9794,8 @@ ${yellow2("⚠")} ${bold2("Dry run")} — would execute with:
|
|
|
9761
9794
|
cwd: options.worktreePath ?? projectRoot,
|
|
9762
9795
|
activity: `issue #${issueNumber}`,
|
|
9763
9796
|
sandboxed: options.sandboxed,
|
|
9764
|
-
sandboxName: options.sandboxName
|
|
9797
|
+
sandboxName: options.sandboxName,
|
|
9798
|
+
containerWorkdir: options.containerWorkdir
|
|
9765
9799
|
});
|
|
9766
9800
|
const output = aiResult.output;
|
|
9767
9801
|
if (aiResult.interrupted) {
|
|
@@ -9867,7 +9901,8 @@ ${c.body}`),
|
|
|
9867
9901
|
cwd: projectRoot,
|
|
9868
9902
|
activity: `iterating on PR #${prNumber}`,
|
|
9869
9903
|
sandboxed: config.sandbox.enabled,
|
|
9870
|
-
sandboxName: getModelSandboxName(config.sandbox, config.ai.model, config.ai.provider)
|
|
9904
|
+
sandboxName: getModelSandboxName(config.sandbox, config.ai.model, config.ai.provider),
|
|
9905
|
+
containerWorkdir: config.sandbox.containerWorkdir
|
|
9871
9906
|
});
|
|
9872
9907
|
if (aiResult.interrupted) {
|
|
9873
9908
|
process.stderr.write(`
|
|
@@ -9888,12 +9923,12 @@ ${aiResult.success ? green("✓") : red2("✗")} Iteration ${aiResult.success ?
|
|
|
9888
9923
|
}
|
|
9889
9924
|
async function createIssuePR(projectRoot, config, issue) {
|
|
9890
9925
|
try {
|
|
9891
|
-
const currentBranch =
|
|
9926
|
+
const currentBranch = execSync14("git rev-parse --abbrev-ref HEAD", {
|
|
9892
9927
|
cwd: projectRoot,
|
|
9893
9928
|
encoding: "utf-8",
|
|
9894
9929
|
stdio: ["pipe", "pipe", "pipe"]
|
|
9895
9930
|
}).trim();
|
|
9896
|
-
const diff =
|
|
9931
|
+
const diff = execSync14(`git diff origin/${config.agent.baseBranch}..HEAD --stat`, {
|
|
9897
9932
|
cwd: projectRoot,
|
|
9898
9933
|
encoding: "utf-8",
|
|
9899
9934
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9903,7 +9938,7 @@ async function createIssuePR(projectRoot, config, issue) {
|
|
|
9903
9938
|
return;
|
|
9904
9939
|
}
|
|
9905
9940
|
pushSubmoduleBranches(projectRoot);
|
|
9906
|
-
|
|
9941
|
+
execSync14(`git push -u origin ${currentBranch}`, {
|
|
9907
9942
|
cwd: projectRoot,
|
|
9908
9943
|
encoding: "utf-8",
|
|
9909
9944
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -9958,9 +9993,9 @@ var init_agent = __esm(() => {
|
|
|
9958
9993
|
});
|
|
9959
9994
|
|
|
9960
9995
|
// src/core/conflict.ts
|
|
9961
|
-
import { execSync as
|
|
9996
|
+
import { execSync as execSync15 } from "node:child_process";
|
|
9962
9997
|
function git3(args, cwd) {
|
|
9963
|
-
return
|
|
9998
|
+
return execSync15(`git ${args}`, {
|
|
9964
9999
|
cwd,
|
|
9965
10000
|
encoding: "utf-8",
|
|
9966
10001
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10261,11 +10296,11 @@ var init_shutdown = __esm(() => {
|
|
|
10261
10296
|
});
|
|
10262
10297
|
|
|
10263
10298
|
// src/core/worktree.ts
|
|
10264
|
-
import { execSync as
|
|
10299
|
+
import { execSync as execSync16 } from "node:child_process";
|
|
10265
10300
|
import { existsSync as existsSync21, readdirSync as readdirSync7, realpathSync, statSync as statSync4 } from "node:fs";
|
|
10266
10301
|
import { join as join21 } from "node:path";
|
|
10267
10302
|
function git4(args, cwd) {
|
|
10268
|
-
return
|
|
10303
|
+
return execSync16(`git ${args}`, {
|
|
10269
10304
|
cwd,
|
|
10270
10305
|
encoding: "utf-8",
|
|
10271
10306
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10290,7 +10325,7 @@ function generateBranchName(issueNumber) {
|
|
|
10290
10325
|
}
|
|
10291
10326
|
function getWorktreeBranch(worktreePath) {
|
|
10292
10327
|
try {
|
|
10293
|
-
return
|
|
10328
|
+
return execSync16("git branch --show-current", {
|
|
10294
10329
|
cwd: worktreePath,
|
|
10295
10330
|
encoding: "utf-8",
|
|
10296
10331
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10417,12 +10452,12 @@ var exports_run = {};
|
|
|
10417
10452
|
__export(exports_run, {
|
|
10418
10453
|
runCommand: () => runCommand
|
|
10419
10454
|
});
|
|
10420
|
-
import { execSync as
|
|
10455
|
+
import { execSync as execSync17 } from "node:child_process";
|
|
10421
10456
|
function resolveExecutionContext(config, modelOverride) {
|
|
10422
10457
|
const model = modelOverride ?? config.ai.model;
|
|
10423
10458
|
const provider = inferProviderFromModel(model) ?? config.ai.provider;
|
|
10424
10459
|
const sandboxName = getModelSandboxName(config.sandbox, model, provider);
|
|
10425
|
-
return { provider, model, sandboxName };
|
|
10460
|
+
return { provider, model, sandboxName, containerWorkdir: config.sandbox.containerWorkdir };
|
|
10426
10461
|
}
|
|
10427
10462
|
function printRunHelp() {
|
|
10428
10463
|
process.stderr.write(`
|
|
@@ -10577,7 +10612,7 @@ ${yellow2("⚠")} A sprint run is already in progress.
|
|
|
10577
10612
|
}
|
|
10578
10613
|
if (!flags.dryRun) {
|
|
10579
10614
|
try {
|
|
10580
|
-
|
|
10615
|
+
execSync17(`git checkout -B ${branchName}`, {
|
|
10581
10616
|
cwd: projectRoot,
|
|
10582
10617
|
encoding: "utf-8",
|
|
10583
10618
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10627,7 +10662,7 @@ ${red2("✗")} Auto-rebase failed. Resolve manually.
|
|
|
10627
10662
|
let sprintContext;
|
|
10628
10663
|
if (i > 0 && !flags.dryRun) {
|
|
10629
10664
|
try {
|
|
10630
|
-
sprintContext =
|
|
10665
|
+
sprintContext = execSync17(`git diff origin/${config.agent.baseBranch}..HEAD`, {
|
|
10631
10666
|
cwd: projectRoot,
|
|
10632
10667
|
encoding: "utf-8",
|
|
10633
10668
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10648,7 +10683,8 @@ ${progressBar(i, state.tasks.length, { label: "Sprint Progress" })}
|
|
|
10648
10683
|
sprintContext,
|
|
10649
10684
|
skipPR: true,
|
|
10650
10685
|
sandboxed,
|
|
10651
|
-
sandboxName: execution.sandboxName
|
|
10686
|
+
sandboxName: execution.sandboxName,
|
|
10687
|
+
containerWorkdir: execution.containerWorkdir
|
|
10652
10688
|
});
|
|
10653
10689
|
if (result.success) {
|
|
10654
10690
|
if (!flags.dryRun) {
|
|
@@ -10692,7 +10728,7 @@ ${bold2("Summary:")}
|
|
|
10692
10728
|
const prNumber = await createSprintPR(projectRoot, config, sprintName, branchName, completedTasks);
|
|
10693
10729
|
if (prNumber !== undefined) {
|
|
10694
10730
|
try {
|
|
10695
|
-
|
|
10731
|
+
execSync17(`git checkout ${config.agent.baseBranch}`, {
|
|
10696
10732
|
cwd: projectRoot,
|
|
10697
10733
|
encoding: "utf-8",
|
|
10698
10734
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10725,7 +10761,8 @@ ${bold2("Running sprint issue")} ${cyan2(`#${issueNumber}`)} ${dim2("(sequential
|
|
|
10725
10761
|
model: execution.model,
|
|
10726
10762
|
dryRun: flags.dryRun,
|
|
10727
10763
|
sandboxed,
|
|
10728
|
-
sandboxName: execution.sandboxName
|
|
10764
|
+
sandboxName: execution.sandboxName,
|
|
10765
|
+
containerWorkdir: execution.containerWorkdir
|
|
10729
10766
|
});
|
|
10730
10767
|
return;
|
|
10731
10768
|
}
|
|
@@ -10737,7 +10774,7 @@ ${bold2("Running issue")} ${cyan2(`#${issueNumber}`)} ${dim2(`(branch: ${branchN
|
|
|
10737
10774
|
`);
|
|
10738
10775
|
if (!flags.dryRun) {
|
|
10739
10776
|
try {
|
|
10740
|
-
|
|
10777
|
+
execSync17(`git checkout -B ${branchName} ${config.agent.baseBranch}`, {
|
|
10741
10778
|
cwd: projectRoot,
|
|
10742
10779
|
encoding: "utf-8",
|
|
10743
10780
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10762,7 +10799,7 @@ ${bold2("Running issue")} ${cyan2(`#${issueNumber}`)} ${dim2(`(branch: ${branchN
|
|
|
10762
10799
|
if (!flags.dryRun) {
|
|
10763
10800
|
if (result.success) {
|
|
10764
10801
|
try {
|
|
10765
|
-
|
|
10802
|
+
execSync17(`git checkout ${config.agent.baseBranch}`, {
|
|
10766
10803
|
cwd: projectRoot,
|
|
10767
10804
|
encoding: "utf-8",
|
|
10768
10805
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10833,7 +10870,8 @@ ${bold2("Running")} ${cyan2(`${issueNumbers.length} issues`)} ${dim2(`(max ${max
|
|
|
10833
10870
|
model: execution.model,
|
|
10834
10871
|
dryRun: flags.dryRun,
|
|
10835
10872
|
sandboxed,
|
|
10836
|
-
sandboxName: execution.sandboxName
|
|
10873
|
+
sandboxName: execution.sandboxName,
|
|
10874
|
+
containerWorkdir: execution.containerWorkdir
|
|
10837
10875
|
});
|
|
10838
10876
|
if (result.success) {
|
|
10839
10877
|
markTaskDone(state, issueNumber, result.prNumber);
|
|
@@ -10899,13 +10937,13 @@ ${bold2("Resuming")} ${state.type} run ${dim2(state.runId)}
|
|
|
10899
10937
|
`);
|
|
10900
10938
|
if (state.type === "sprint" && state.branch) {
|
|
10901
10939
|
try {
|
|
10902
|
-
const currentBranch =
|
|
10940
|
+
const currentBranch = execSync17("git rev-parse --abbrev-ref HEAD", {
|
|
10903
10941
|
cwd: projectRoot,
|
|
10904
10942
|
encoding: "utf-8",
|
|
10905
10943
|
stdio: ["pipe", "pipe", "pipe"]
|
|
10906
10944
|
}).trim();
|
|
10907
10945
|
if (currentBranch !== state.branch) {
|
|
10908
|
-
|
|
10946
|
+
execSync17(`git checkout ${state.branch}`, {
|
|
10909
10947
|
cwd: projectRoot,
|
|
10910
10948
|
encoding: "utf-8",
|
|
10911
10949
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -10933,7 +10971,8 @@ ${bold2("Resuming")} ${state.type} run ${dim2(state.runId)}
|
|
|
10933
10971
|
model: execution.model,
|
|
10934
10972
|
skipPR: isSprintRun,
|
|
10935
10973
|
sandboxed,
|
|
10936
|
-
sandboxName: execution.sandboxName
|
|
10974
|
+
sandboxName: execution.sandboxName,
|
|
10975
|
+
containerWorkdir: execution.containerWorkdir
|
|
10937
10976
|
});
|
|
10938
10977
|
if (result.success) {
|
|
10939
10978
|
if (isSprintRun) {
|
|
@@ -10972,7 +11011,7 @@ ${bold2("Resume complete:")} ${green(`✓ ${finalStats.done}`)} ${finalStats.fai
|
|
|
10972
11011
|
const prNumber = await createSprintPR(projectRoot, config, state.sprint, state.branch, completedTasks);
|
|
10973
11012
|
if (prNumber !== undefined) {
|
|
10974
11013
|
try {
|
|
10975
|
-
|
|
11014
|
+
execSync17(`git checkout ${config.agent.baseBranch}`, {
|
|
10976
11015
|
cwd: projectRoot,
|
|
10977
11016
|
encoding: "utf-8",
|
|
10978
11017
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -11008,14 +11047,14 @@ function ensureTaskCommit(projectRoot, issueNumber, issueTitle) {
|
|
|
11008
11047
|
process.stderr.write(` ${dim2(`Committed submodule changes: ${committedSubmodules.join(", ")}`)}
|
|
11009
11048
|
`);
|
|
11010
11049
|
}
|
|
11011
|
-
const status =
|
|
11050
|
+
const status = execSync17("git status --porcelain", {
|
|
11012
11051
|
cwd: projectRoot,
|
|
11013
11052
|
encoding: "utf-8",
|
|
11014
11053
|
stdio: ["pipe", "pipe", "pipe"]
|
|
11015
11054
|
}).trim();
|
|
11016
11055
|
if (!status)
|
|
11017
11056
|
return;
|
|
11018
|
-
|
|
11057
|
+
execSync17("git add -A", {
|
|
11019
11058
|
cwd: projectRoot,
|
|
11020
11059
|
encoding: "utf-8",
|
|
11021
11060
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -11023,7 +11062,7 @@ function ensureTaskCommit(projectRoot, issueNumber, issueTitle) {
|
|
|
11023
11062
|
const message = `chore: complete #${issueNumber} - ${issueTitle}
|
|
11024
11063
|
|
|
11025
11064
|
Co-Authored-By: LocusAgent <agent@locusai.team>`;
|
|
11026
|
-
|
|
11065
|
+
execSync17(`git commit -F -`, {
|
|
11027
11066
|
input: message,
|
|
11028
11067
|
cwd: projectRoot,
|
|
11029
11068
|
encoding: "utf-8",
|
|
@@ -11037,7 +11076,7 @@ async function createSprintPR(projectRoot, config, sprintName, branchName, tasks
|
|
|
11037
11076
|
if (!config.agent.autoPR)
|
|
11038
11077
|
return;
|
|
11039
11078
|
try {
|
|
11040
|
-
const diff =
|
|
11079
|
+
const diff = execSync17(`git diff origin/${config.agent.baseBranch}..HEAD --stat`, {
|
|
11041
11080
|
cwd: projectRoot,
|
|
11042
11081
|
encoding: "utf-8",
|
|
11043
11082
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -11048,7 +11087,7 @@ async function createSprintPR(projectRoot, config, sprintName, branchName, tasks
|
|
|
11048
11087
|
return;
|
|
11049
11088
|
}
|
|
11050
11089
|
pushSubmoduleBranches(projectRoot);
|
|
11051
|
-
|
|
11090
|
+
execSync17(`git push -u origin ${branchName}`, {
|
|
11052
11091
|
cwd: projectRoot,
|
|
11053
11092
|
encoding: "utf-8",
|
|
11054
11093
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -11467,7 +11506,8 @@ ${bold2("Planning:")} ${cyan2(displayDirective)}
|
|
|
11467
11506
|
cwd: projectRoot,
|
|
11468
11507
|
activity: "planning",
|
|
11469
11508
|
sandboxed: config.sandbox.enabled,
|
|
11470
|
-
sandboxName: getModelSandboxName(config.sandbox, flags.model ?? config.ai.model, config.ai.provider)
|
|
11509
|
+
sandboxName: getModelSandboxName(config.sandbox, flags.model ?? config.ai.model, config.ai.provider),
|
|
11510
|
+
containerWorkdir: config.sandbox.containerWorkdir
|
|
11471
11511
|
});
|
|
11472
11512
|
if (aiResult.interrupted) {
|
|
11473
11513
|
process.stderr.write(`
|
|
@@ -11590,7 +11630,8 @@ Start with foundational/setup tasks, then core features, then integration/testin
|
|
|
11590
11630
|
activity: "issue ordering",
|
|
11591
11631
|
silent: true,
|
|
11592
11632
|
sandboxed: config.sandbox.enabled,
|
|
11593
|
-
sandboxName: getModelSandboxName(config.sandbox, flags.model ?? config.ai.model, config.ai.provider)
|
|
11633
|
+
sandboxName: getModelSandboxName(config.sandbox, flags.model ?? config.ai.model, config.ai.provider),
|
|
11634
|
+
containerWorkdir: config.sandbox.containerWorkdir
|
|
11594
11635
|
});
|
|
11595
11636
|
if (aiResult.interrupted) {
|
|
11596
11637
|
process.stderr.write(`
|
|
@@ -11850,7 +11891,7 @@ var exports_review = {};
|
|
|
11850
11891
|
__export(exports_review, {
|
|
11851
11892
|
reviewCommand: () => reviewCommand
|
|
11852
11893
|
});
|
|
11853
|
-
import { execFileSync as execFileSync2, execSync as
|
|
11894
|
+
import { execFileSync as execFileSync2, execSync as execSync18 } from "node:child_process";
|
|
11854
11895
|
import { existsSync as existsSync23, readFileSync as readFileSync14 } from "node:fs";
|
|
11855
11896
|
import { join as join23 } from "node:path";
|
|
11856
11897
|
function printHelp3() {
|
|
@@ -11936,7 +11977,7 @@ ${bold2("Review complete:")} ${green(`✓ ${reviewed}`)}${failed > 0 ? ` ${red2(
|
|
|
11936
11977
|
async function reviewSinglePR(projectRoot, config, prNumber, focus, flags) {
|
|
11937
11978
|
let prInfo;
|
|
11938
11979
|
try {
|
|
11939
|
-
const result =
|
|
11980
|
+
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
11981
|
const raw = JSON.parse(result);
|
|
11941
11982
|
prInfo = {
|
|
11942
11983
|
number: raw.number,
|
|
@@ -11981,7 +12022,8 @@ async function reviewPR(projectRoot, config, pr, focus, flags) {
|
|
|
11981
12022
|
cwd: projectRoot,
|
|
11982
12023
|
activity: `PR #${pr.number}`,
|
|
11983
12024
|
sandboxed: config.sandbox.enabled,
|
|
11984
|
-
sandboxName: getModelSandboxName(config.sandbox, flags.model ?? config.ai.model, config.ai.provider)
|
|
12025
|
+
sandboxName: getModelSandboxName(config.sandbox, flags.model ?? config.ai.model, config.ai.provider),
|
|
12026
|
+
containerWorkdir: config.sandbox.containerWorkdir
|
|
11985
12027
|
});
|
|
11986
12028
|
if (aiResult.interrupted) {
|
|
11987
12029
|
process.stderr.write(` ${yellow2("⚡")} Review interrupted.
|
|
@@ -12083,7 +12125,7 @@ var exports_iterate = {};
|
|
|
12083
12125
|
__export(exports_iterate, {
|
|
12084
12126
|
iterateCommand: () => iterateCommand
|
|
12085
12127
|
});
|
|
12086
|
-
import { execSync as
|
|
12128
|
+
import { execSync as execSync19 } from "node:child_process";
|
|
12087
12129
|
function printHelp4() {
|
|
12088
12130
|
process.stderr.write(`
|
|
12089
12131
|
${bold2("locus iterate")} — Re-execute tasks with PR feedback
|
|
@@ -12301,12 +12343,12 @@ ${bold2("Summary:")} ${green(`✓ ${succeeded}`)}${failed > 0 ? ` ${red2(`✗ ${
|
|
|
12301
12343
|
}
|
|
12302
12344
|
function findPRForIssue(projectRoot, issueNumber) {
|
|
12303
12345
|
try {
|
|
12304
|
-
const result =
|
|
12346
|
+
const result = execSync19(`gh pr list --search "Closes #${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
12305
12347
|
const parsed = JSON.parse(result);
|
|
12306
12348
|
if (parsed.length > 0) {
|
|
12307
12349
|
return parsed[0].number;
|
|
12308
12350
|
}
|
|
12309
|
-
const branchResult =
|
|
12351
|
+
const branchResult = execSync19(`gh pr list --head "locus/issue-${issueNumber}" --json number --state open`, { cwd: projectRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
12310
12352
|
const branchParsed = JSON.parse(branchResult);
|
|
12311
12353
|
if (branchParsed.length > 0) {
|
|
12312
12354
|
return branchParsed[0].number;
|
|
@@ -12563,7 +12605,8 @@ ${bold2("Discussion:")} ${cyan2(topic)}
|
|
|
12563
12605
|
cwd: projectRoot,
|
|
12564
12606
|
activity: "discussion",
|
|
12565
12607
|
sandboxed: config.sandbox.enabled,
|
|
12566
|
-
sandboxName: getModelSandboxName(config.sandbox, flags.model ?? config.ai.model, config.ai.provider)
|
|
12608
|
+
sandboxName: getModelSandboxName(config.sandbox, flags.model ?? config.ai.model, config.ai.provider),
|
|
12609
|
+
containerWorkdir: config.sandbox.containerWorkdir
|
|
12567
12610
|
});
|
|
12568
12611
|
if (aiResult.interrupted) {
|
|
12569
12612
|
process.stderr.write(`
|
|
@@ -12936,7 +12979,7 @@ __export(exports_sandbox2, {
|
|
|
12936
12979
|
parseSandboxLogsArgs: () => parseSandboxLogsArgs,
|
|
12937
12980
|
parseSandboxInstallArgs: () => parseSandboxInstallArgs
|
|
12938
12981
|
});
|
|
12939
|
-
import { execSync as
|
|
12982
|
+
import { execSync as execSync20, spawn as spawn7 } from "node:child_process";
|
|
12940
12983
|
import { createHash } from "node:crypto";
|
|
12941
12984
|
import { existsSync as existsSync26, readFileSync as readFileSync17 } from "node:fs";
|
|
12942
12985
|
import { basename as basename4, join as join26 } from "node:path";
|
|
@@ -13070,11 +13113,26 @@ async function handleCreate(projectRoot) {
|
|
|
13070
13113
|
}
|
|
13071
13114
|
process.stderr.write(`${green("✓")} ${provider} sandbox created: ${bold2(name)}
|
|
13072
13115
|
`);
|
|
13116
|
+
if (!config.sandbox.containerWorkdir) {
|
|
13117
|
+
const containerWorkdir = detectContainerWorkdir(name, projectRoot);
|
|
13118
|
+
if (containerWorkdir) {
|
|
13119
|
+
config.sandbox.containerWorkdir = containerWorkdir;
|
|
13120
|
+
process.stderr.write(` ${dim2(`Container workdir: ${containerWorkdir}`)}
|
|
13121
|
+
`);
|
|
13122
|
+
}
|
|
13123
|
+
}
|
|
13124
|
+
const workdir = config.sandbox.containerWorkdir ?? projectRoot;
|
|
13125
|
+
const backup = backupIgnoredFiles(projectRoot);
|
|
13126
|
+
try {
|
|
13127
|
+
await enforceSandboxIgnore(name, projectRoot, config.sandbox.containerWorkdir);
|
|
13128
|
+
} finally {
|
|
13129
|
+
backup.restore();
|
|
13130
|
+
}
|
|
13073
13131
|
readySandboxes[provider] = name;
|
|
13074
13132
|
config.sandbox.enabled = true;
|
|
13075
13133
|
config.sandbox.providers = readySandboxes;
|
|
13076
13134
|
saveConfig(projectRoot, config);
|
|
13077
|
-
await runSandboxSetup(name, projectRoot);
|
|
13135
|
+
await runSandboxSetup(name, projectRoot, workdir);
|
|
13078
13136
|
process.stderr.write(`
|
|
13079
13137
|
${green("✓")} Sandbox mode enabled for ${bold2(provider)}.
|
|
13080
13138
|
`);
|
|
@@ -13099,19 +13157,20 @@ async function handleAgentLogin(projectRoot, agent) {
|
|
|
13099
13157
|
if (agent === "codex") {
|
|
13100
13158
|
await ensureCodexInSandbox(sandboxName);
|
|
13101
13159
|
}
|
|
13160
|
+
const workdir = config.sandbox.containerWorkdir ?? projectRoot;
|
|
13102
13161
|
process.stderr.write(`Connecting to ${agent} sandbox ${dim2(sandboxName)}...
|
|
13103
13162
|
`);
|
|
13104
13163
|
process.stderr.write(`${dim2("Login and then exit when ready.")}
|
|
13105
13164
|
|
|
13106
13165
|
`);
|
|
13107
|
-
const child = spawn7("docker", ["sandbox", "exec", "-it", "-w",
|
|
13166
|
+
const child = spawn7("docker", ["sandbox", "exec", "-it", "-w", workdir, sandboxName, agent], {
|
|
13108
13167
|
stdio: "inherit"
|
|
13109
13168
|
});
|
|
13110
13169
|
await new Promise((resolve2) => {
|
|
13111
13170
|
child.on("close", async (code) => {
|
|
13112
13171
|
const backup = backupIgnoredFiles(projectRoot);
|
|
13113
13172
|
try {
|
|
13114
|
-
await enforceSandboxIgnore(sandboxName, projectRoot);
|
|
13173
|
+
await enforceSandboxIgnore(sandboxName, projectRoot, config.sandbox.containerWorkdir);
|
|
13115
13174
|
} finally {
|
|
13116
13175
|
backup.restore();
|
|
13117
13176
|
}
|
|
@@ -13148,7 +13207,7 @@ function handleRemove(projectRoot) {
|
|
|
13148
13207
|
process.stderr.write(`Removing sandbox ${bold2(sandboxName)}...
|
|
13149
13208
|
`);
|
|
13150
13209
|
try {
|
|
13151
|
-
|
|
13210
|
+
execSync20(`docker sandbox rm ${sandboxName}`, {
|
|
13152
13211
|
encoding: "utf-8",
|
|
13153
13212
|
stdio: ["pipe", "pipe", "pipe"],
|
|
13154
13213
|
timeout: 15000
|
|
@@ -13157,6 +13216,7 @@ function handleRemove(projectRoot) {
|
|
|
13157
13216
|
}
|
|
13158
13217
|
config.sandbox.providers = {};
|
|
13159
13218
|
config.sandbox.enabled = false;
|
|
13219
|
+
delete config.sandbox.containerWorkdir;
|
|
13160
13220
|
saveConfig(projectRoot, config);
|
|
13161
13221
|
process.stderr.write(`${green("✓")} Provider sandboxes removed. Sandbox mode disabled.
|
|
13162
13222
|
`);
|
|
@@ -13169,6 +13229,10 @@ ${bold2("Sandbox Status")}
|
|
|
13169
13229
|
`);
|
|
13170
13230
|
process.stderr.write(` ${dim2("Enabled:")} ${config.sandbox.enabled ? green("yes") : red2("no")}
|
|
13171
13231
|
`);
|
|
13232
|
+
if (config.sandbox.containerWorkdir) {
|
|
13233
|
+
process.stderr.write(` ${dim2("Container workdir:")} ${config.sandbox.containerWorkdir}
|
|
13234
|
+
`);
|
|
13235
|
+
}
|
|
13172
13236
|
for (const provider of PROVIDERS) {
|
|
13173
13237
|
const name = config.sandbox.providers[provider];
|
|
13174
13238
|
process.stderr.write(` ${dim2(`${provider}:`).padEnd(15)}${name ? bold2(name) : dim2("(not configured)")}
|
|
@@ -13305,10 +13369,12 @@ async function handleShell(projectRoot, args) {
|
|
|
13305
13369
|
`);
|
|
13306
13370
|
return;
|
|
13307
13371
|
}
|
|
13372
|
+
const config = loadConfig(projectRoot);
|
|
13308
13373
|
const sandboxName = getActiveProviderSandbox(projectRoot, provider);
|
|
13309
13374
|
if (!sandboxName) {
|
|
13310
13375
|
return;
|
|
13311
13376
|
}
|
|
13377
|
+
const workdir = config.sandbox.containerWorkdir ?? projectRoot;
|
|
13312
13378
|
process.stderr.write(`Opening shell in ${provider} sandbox ${dim2(sandboxName)}...
|
|
13313
13379
|
`);
|
|
13314
13380
|
await runInteractiveCommand("docker", [
|
|
@@ -13316,7 +13382,7 @@ async function handleShell(projectRoot, args) {
|
|
|
13316
13382
|
"exec",
|
|
13317
13383
|
"-it",
|
|
13318
13384
|
"-w",
|
|
13319
|
-
|
|
13385
|
+
workdir,
|
|
13320
13386
|
sandboxName,
|
|
13321
13387
|
"sh"
|
|
13322
13388
|
]);
|
|
@@ -13429,7 +13495,8 @@ function getInstallCommand(pm) {
|
|
|
13429
13495
|
return ["npm", "install"];
|
|
13430
13496
|
}
|
|
13431
13497
|
}
|
|
13432
|
-
async function runSandboxSetup(sandboxName, projectRoot) {
|
|
13498
|
+
async function runSandboxSetup(sandboxName, projectRoot, containerWorkdir) {
|
|
13499
|
+
const workdir = containerWorkdir ?? projectRoot;
|
|
13433
13500
|
const ecosystem = detectProjectEcosystem(projectRoot);
|
|
13434
13501
|
const isJS = isJavaScriptEcosystem(ecosystem);
|
|
13435
13502
|
if (isJS) {
|
|
@@ -13445,7 +13512,7 @@ Installing dependencies (${bold2(installCmd.join(" "))}) in sandbox ${dim2(sandb
|
|
|
13445
13512
|
"sandbox",
|
|
13446
13513
|
"exec",
|
|
13447
13514
|
"-w",
|
|
13448
|
-
|
|
13515
|
+
workdir,
|
|
13449
13516
|
sandboxName,
|
|
13450
13517
|
...installCmd
|
|
13451
13518
|
]);
|
|
@@ -13462,6 +13529,7 @@ ${dim2(`Detected ${ecosystem} project — skipping JS package install.`)}
|
|
|
13462
13529
|
`);
|
|
13463
13530
|
}
|
|
13464
13531
|
const setupScript = join26(projectRoot, ".locus", "sandbox-setup.sh");
|
|
13532
|
+
const containerSetupScript = containerWorkdir ? join26(containerWorkdir, ".locus", "sandbox-setup.sh") : setupScript;
|
|
13465
13533
|
if (existsSync26(setupScript)) {
|
|
13466
13534
|
process.stderr.write(`Running ${bold2(".locus/sandbox-setup.sh")} in sandbox ${dim2(sandboxName)}...
|
|
13467
13535
|
`);
|
|
@@ -13469,10 +13537,10 @@ ${dim2(`Detected ${ecosystem} project — skipping JS package install.`)}
|
|
|
13469
13537
|
"sandbox",
|
|
13470
13538
|
"exec",
|
|
13471
13539
|
"-w",
|
|
13472
|
-
|
|
13540
|
+
workdir,
|
|
13473
13541
|
sandboxName,
|
|
13474
13542
|
"sh",
|
|
13475
|
-
|
|
13543
|
+
containerSetupScript
|
|
13476
13544
|
]);
|
|
13477
13545
|
if (!hookOk) {
|
|
13478
13546
|
process.stderr.write(`${yellow2("⚠")} Setup hook failed in sandbox ${dim2(sandboxName)}.
|
|
@@ -13503,7 +13571,7 @@ async function handleSetup(projectRoot) {
|
|
|
13503
13571
|
`);
|
|
13504
13572
|
continue;
|
|
13505
13573
|
}
|
|
13506
|
-
await runSandboxSetup(sandboxName, projectRoot);
|
|
13574
|
+
await runSandboxSetup(sandboxName, projectRoot, config.sandbox.containerWorkdir);
|
|
13507
13575
|
}
|
|
13508
13576
|
}
|
|
13509
13577
|
function buildProviderSandboxNames(projectRoot) {
|
|
@@ -13550,7 +13618,7 @@ function runInteractiveCommand(command, args) {
|
|
|
13550
13618
|
}
|
|
13551
13619
|
async function createProviderSandbox(provider, sandboxName, projectRoot) {
|
|
13552
13620
|
try {
|
|
13553
|
-
|
|
13621
|
+
execSync20(`docker sandbox create --name ${sandboxName} claude ${projectRoot}`, {
|
|
13554
13622
|
stdio: ["pipe", "pipe", "pipe"],
|
|
13555
13623
|
timeout: 120000
|
|
13556
13624
|
});
|
|
@@ -13561,17 +13629,11 @@ async function createProviderSandbox(provider, sandboxName, projectRoot) {
|
|
|
13561
13629
|
if (provider === "codex") {
|
|
13562
13630
|
await ensureCodexInSandbox(sandboxName);
|
|
13563
13631
|
}
|
|
13564
|
-
const backup = backupIgnoredFiles(projectRoot);
|
|
13565
|
-
try {
|
|
13566
|
-
await enforceSandboxIgnore(sandboxName, projectRoot);
|
|
13567
|
-
} finally {
|
|
13568
|
-
backup.restore();
|
|
13569
|
-
}
|
|
13570
13632
|
return true;
|
|
13571
13633
|
}
|
|
13572
13634
|
async function ensurePackageManagerInSandbox(sandboxName, pm) {
|
|
13573
13635
|
try {
|
|
13574
|
-
|
|
13636
|
+
execSync20(`docker sandbox exec ${sandboxName} which ${pm}`, {
|
|
13575
13637
|
stdio: ["pipe", "pipe", "pipe"],
|
|
13576
13638
|
timeout: 5000
|
|
13577
13639
|
});
|
|
@@ -13580,7 +13642,7 @@ async function ensurePackageManagerInSandbox(sandboxName, pm) {
|
|
|
13580
13642
|
process.stderr.write(`Installing ${bold2(pm)} in sandbox...
|
|
13581
13643
|
`);
|
|
13582
13644
|
try {
|
|
13583
|
-
|
|
13645
|
+
execSync20(`docker sandbox exec ${sandboxName} npm install -g ${npmPkg}`, {
|
|
13584
13646
|
stdio: "inherit",
|
|
13585
13647
|
timeout: 120000
|
|
13586
13648
|
});
|
|
@@ -13592,7 +13654,7 @@ async function ensurePackageManagerInSandbox(sandboxName, pm) {
|
|
|
13592
13654
|
}
|
|
13593
13655
|
async function ensureCodexInSandbox(sandboxName) {
|
|
13594
13656
|
try {
|
|
13595
|
-
|
|
13657
|
+
execSync20(`docker sandbox exec ${sandboxName} which codex`, {
|
|
13596
13658
|
stdio: ["pipe", "pipe", "pipe"],
|
|
13597
13659
|
timeout: 5000
|
|
13598
13660
|
});
|
|
@@ -13600,7 +13662,7 @@ async function ensureCodexInSandbox(sandboxName) {
|
|
|
13600
13662
|
process.stderr.write(`Installing codex in sandbox...
|
|
13601
13663
|
`);
|
|
13602
13664
|
try {
|
|
13603
|
-
|
|
13665
|
+
execSync20(`docker sandbox exec ${sandboxName} npm install -g @openai/codex`, { stdio: "inherit", timeout: 120000 });
|
|
13604
13666
|
} catch {
|
|
13605
13667
|
process.stderr.write(`${red2("✗")} Failed to install codex in sandbox.
|
|
13606
13668
|
`);
|
|
@@ -13609,7 +13671,7 @@ async function ensureCodexInSandbox(sandboxName) {
|
|
|
13609
13671
|
}
|
|
13610
13672
|
function isSandboxAlive(name) {
|
|
13611
13673
|
try {
|
|
13612
|
-
const output =
|
|
13674
|
+
const output = execSync20("docker sandbox ls", {
|
|
13613
13675
|
encoding: "utf-8",
|
|
13614
13676
|
stdio: ["pipe", "pipe", "pipe"],
|
|
13615
13677
|
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.3",
|
|
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.3",
|
|
40
40
|
"@types/bun": "latest",
|
|
41
41
|
"typescript": "^5.8.3"
|
|
42
42
|
},
|