@locusai/cli 0.21.5 → 0.21.7
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 +367 -207
- package/package.json +1 -1
package/bin/locus.js
CHANGED
|
@@ -4557,8 +4557,18 @@ var init_claude = __esm(() => {
|
|
|
4557
4557
|
|
|
4558
4558
|
// src/core/sandbox-ignore.ts
|
|
4559
4559
|
import { exec } from "node:child_process";
|
|
4560
|
-
import {
|
|
4561
|
-
|
|
4560
|
+
import {
|
|
4561
|
+
cpSync,
|
|
4562
|
+
existsSync as existsSync13,
|
|
4563
|
+
mkdirSync as mkdirSync9,
|
|
4564
|
+
mkdtempSync,
|
|
4565
|
+
readdirSync as readdirSync3,
|
|
4566
|
+
readFileSync as readFileSync8,
|
|
4567
|
+
rmSync,
|
|
4568
|
+
statSync as statSync3
|
|
4569
|
+
} from "node:fs";
|
|
4570
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
4571
|
+
import { dirname as dirname3, join as join12, relative } from "node:path";
|
|
4562
4572
|
import { promisify } from "node:util";
|
|
4563
4573
|
function parseIgnoreFile(filePath) {
|
|
4564
4574
|
if (!existsSync13(filePath))
|
|
@@ -4606,6 +4616,124 @@ function buildCleanupScript(rules, workspacePath) {
|
|
|
4606
4616
|
}
|
|
4607
4617
|
return `${commands.join(" 2>/dev/null ; ")} 2>/dev/null`;
|
|
4608
4618
|
}
|
|
4619
|
+
function patternToRegex(pattern) {
|
|
4620
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
4621
|
+
return new RegExp(`^${escaped}$`);
|
|
4622
|
+
}
|
|
4623
|
+
function findIgnoredPaths(projectRoot, rules) {
|
|
4624
|
+
const positive = rules.filter((r) => !r.negated);
|
|
4625
|
+
const negated = rules.filter((r) => r.negated);
|
|
4626
|
+
if (positive.length === 0)
|
|
4627
|
+
return [];
|
|
4628
|
+
const positiveMatchers = positive.map((r) => ({
|
|
4629
|
+
regex: patternToRegex(r.pattern),
|
|
4630
|
+
isDirectory: r.isDirectory
|
|
4631
|
+
}));
|
|
4632
|
+
const negatedRegexes = negated.map((r) => patternToRegex(r.pattern));
|
|
4633
|
+
const matches = [];
|
|
4634
|
+
function walk(dir) {
|
|
4635
|
+
let entries;
|
|
4636
|
+
try {
|
|
4637
|
+
entries = readdirSync3(dir);
|
|
4638
|
+
} catch {
|
|
4639
|
+
return;
|
|
4640
|
+
}
|
|
4641
|
+
for (const name of entries) {
|
|
4642
|
+
if (SKIP_DIRS.has(name))
|
|
4643
|
+
continue;
|
|
4644
|
+
const fullPath = join12(dir, name);
|
|
4645
|
+
let stat = null;
|
|
4646
|
+
try {
|
|
4647
|
+
stat = statSync3(fullPath);
|
|
4648
|
+
} catch {
|
|
4649
|
+
continue;
|
|
4650
|
+
}
|
|
4651
|
+
const isDir = stat.isDirectory();
|
|
4652
|
+
let matched = false;
|
|
4653
|
+
for (const m of positiveMatchers) {
|
|
4654
|
+
if (m.isDirectory && !isDir)
|
|
4655
|
+
continue;
|
|
4656
|
+
if (m.regex.test(name)) {
|
|
4657
|
+
matched = true;
|
|
4658
|
+
break;
|
|
4659
|
+
}
|
|
4660
|
+
}
|
|
4661
|
+
if (matched) {
|
|
4662
|
+
const isNegated = negatedRegexes.some((nr) => nr.test(name));
|
|
4663
|
+
if (!isNegated) {
|
|
4664
|
+
matches.push(fullPath);
|
|
4665
|
+
continue;
|
|
4666
|
+
}
|
|
4667
|
+
}
|
|
4668
|
+
if (isDir) {
|
|
4669
|
+
walk(fullPath);
|
|
4670
|
+
}
|
|
4671
|
+
}
|
|
4672
|
+
}
|
|
4673
|
+
walk(projectRoot);
|
|
4674
|
+
return matches;
|
|
4675
|
+
}
|
|
4676
|
+
function backupIgnoredFiles(projectRoot) {
|
|
4677
|
+
const log = getLogger();
|
|
4678
|
+
const ignorePath = join12(projectRoot, ".sandboxignore");
|
|
4679
|
+
const rules = parseIgnoreFile(ignorePath);
|
|
4680
|
+
if (rules.length === 0)
|
|
4681
|
+
return NOOP_BACKUP;
|
|
4682
|
+
const paths = findIgnoredPaths(projectRoot, rules);
|
|
4683
|
+
if (paths.length === 0)
|
|
4684
|
+
return NOOP_BACKUP;
|
|
4685
|
+
let backupDir;
|
|
4686
|
+
try {
|
|
4687
|
+
backupDir = mkdtempSync(join12(tmpdir3(), "locus-sandbox-backup-"));
|
|
4688
|
+
} catch (err) {
|
|
4689
|
+
log.debug("Failed to create sandbox backup dir", {
|
|
4690
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4691
|
+
});
|
|
4692
|
+
return NOOP_BACKUP;
|
|
4693
|
+
}
|
|
4694
|
+
const backed = [];
|
|
4695
|
+
for (const src of paths) {
|
|
4696
|
+
const rel = relative(projectRoot, src);
|
|
4697
|
+
const dest = join12(backupDir, rel);
|
|
4698
|
+
try {
|
|
4699
|
+
mkdirSync9(dirname3(dest), { recursive: true });
|
|
4700
|
+
cpSync(src, dest, { recursive: true, preserveTimestamps: true });
|
|
4701
|
+
backed.push({ src, dest });
|
|
4702
|
+
} catch (err) {
|
|
4703
|
+
log.debug("Failed to back up ignored file", {
|
|
4704
|
+
src,
|
|
4705
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4706
|
+
});
|
|
4707
|
+
}
|
|
4708
|
+
}
|
|
4709
|
+
if (backed.length === 0) {
|
|
4710
|
+
rmSync(backupDir, { recursive: true, force: true });
|
|
4711
|
+
return NOOP_BACKUP;
|
|
4712
|
+
}
|
|
4713
|
+
log.debug("Backed up sandbox-ignored files", {
|
|
4714
|
+
count: backed.length,
|
|
4715
|
+
backupDir
|
|
4716
|
+
});
|
|
4717
|
+
return {
|
|
4718
|
+
restore() {
|
|
4719
|
+
for (const { src, dest } of backed) {
|
|
4720
|
+
try {
|
|
4721
|
+
mkdirSync9(dirname3(src), { recursive: true });
|
|
4722
|
+
cpSync(dest, src, { recursive: true, preserveTimestamps: true });
|
|
4723
|
+
} catch (err) {
|
|
4724
|
+
log.debug("Failed to restore ignored file (potential data loss)", {
|
|
4725
|
+
src,
|
|
4726
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4727
|
+
});
|
|
4728
|
+
}
|
|
4729
|
+
}
|
|
4730
|
+
try {
|
|
4731
|
+
rmSync(backupDir, { recursive: true, force: true });
|
|
4732
|
+
} catch {}
|
|
4733
|
+
log.debug("Restored sandbox-ignored files", { count: backed.length });
|
|
4734
|
+
}
|
|
4735
|
+
};
|
|
4736
|
+
}
|
|
4609
4737
|
async function enforceSandboxIgnore(sandboxName, projectRoot) {
|
|
4610
4738
|
const log = getLogger();
|
|
4611
4739
|
const ignorePath = join12(projectRoot, ".sandboxignore");
|
|
@@ -4629,10 +4757,22 @@ async function enforceSandboxIgnore(sandboxName, projectRoot) {
|
|
|
4629
4757
|
});
|
|
4630
4758
|
}
|
|
4631
4759
|
}
|
|
4632
|
-
var execAsync;
|
|
4760
|
+
var execAsync, SKIP_DIRS, NOOP_BACKUP;
|
|
4633
4761
|
var init_sandbox_ignore = __esm(() => {
|
|
4634
4762
|
init_logger();
|
|
4635
4763
|
execAsync = promisify(exec);
|
|
4764
|
+
SKIP_DIRS = new Set([
|
|
4765
|
+
"node_modules",
|
|
4766
|
+
".git",
|
|
4767
|
+
".locus",
|
|
4768
|
+
"dist",
|
|
4769
|
+
"build",
|
|
4770
|
+
".next",
|
|
4771
|
+
".cache"
|
|
4772
|
+
]);
|
|
4773
|
+
NOOP_BACKUP = {
|
|
4774
|
+
restore() {}
|
|
4775
|
+
};
|
|
4636
4776
|
});
|
|
4637
4777
|
|
|
4638
4778
|
// src/ai/claude-sandbox.ts
|
|
@@ -4669,6 +4809,7 @@ class SandboxedClaudeRunner {
|
|
|
4669
4809
|
}
|
|
4670
4810
|
const claudeArgs = ["-p", options.prompt, ...buildClaudeArgs(options)];
|
|
4671
4811
|
options.onStatusChange?.("Syncing sandbox...");
|
|
4812
|
+
const backup = backupIgnoredFiles(options.cwd);
|
|
4672
4813
|
await enforceSandboxIgnore(this.sandboxName, options.cwd);
|
|
4673
4814
|
options.onStatusChange?.("Thinking...");
|
|
4674
4815
|
const dockerArgs = [
|
|
@@ -4685,95 +4826,99 @@ class SandboxedClaudeRunner {
|
|
|
4685
4826
|
args: dockerArgs.join(" "),
|
|
4686
4827
|
cwd: options.cwd
|
|
4687
4828
|
});
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
`
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
seenToolIds.
|
|
4712
|
-
|
|
4829
|
+
try {
|
|
4830
|
+
return await new Promise((resolve2) => {
|
|
4831
|
+
let output = "";
|
|
4832
|
+
let errorOutput = "";
|
|
4833
|
+
this.process = spawn3("docker", dockerArgs, {
|
|
4834
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
4835
|
+
env: process.env
|
|
4836
|
+
});
|
|
4837
|
+
if (options.verbose) {
|
|
4838
|
+
let lineBuffer = "";
|
|
4839
|
+
const seenToolIds = new Set;
|
|
4840
|
+
this.process.stdout?.on("data", (chunk) => {
|
|
4841
|
+
lineBuffer += chunk.toString();
|
|
4842
|
+
const lines = lineBuffer.split(`
|
|
4843
|
+
`);
|
|
4844
|
+
lineBuffer = lines.pop() ?? "";
|
|
4845
|
+
for (const line of lines) {
|
|
4846
|
+
if (!line.trim())
|
|
4847
|
+
continue;
|
|
4848
|
+
try {
|
|
4849
|
+
const event = JSON.parse(line);
|
|
4850
|
+
if (event.type === "assistant" && event.message?.content) {
|
|
4851
|
+
for (const item of event.message.content) {
|
|
4852
|
+
if (item.type === "tool_use" && item.id && !seenToolIds.has(item.id)) {
|
|
4853
|
+
seenToolIds.add(item.id);
|
|
4854
|
+
options.onToolActivity?.(formatToolCall2(item.name ?? "", item.input ?? {}));
|
|
4855
|
+
}
|
|
4713
4856
|
}
|
|
4857
|
+
} else if (event.type === "result") {
|
|
4858
|
+
const text = event.result ?? "";
|
|
4859
|
+
output = text;
|
|
4860
|
+
options.onOutput?.(text);
|
|
4714
4861
|
}
|
|
4715
|
-
}
|
|
4716
|
-
const
|
|
4717
|
-
output = text;
|
|
4718
|
-
options.onOutput?.(text);
|
|
4719
|
-
}
|
|
4720
|
-
} catch {
|
|
4721
|
-
const newLine = `${line}
|
|
4862
|
+
} catch {
|
|
4863
|
+
const newLine = `${line}
|
|
4722
4864
|
`;
|
|
4723
|
-
|
|
4724
|
-
|
|
4865
|
+
output += newLine;
|
|
4866
|
+
options.onOutput?.(newLine);
|
|
4867
|
+
}
|
|
4725
4868
|
}
|
|
4726
|
-
}
|
|
4727
|
-
}
|
|
4728
|
-
|
|
4729
|
-
|
|
4869
|
+
});
|
|
4870
|
+
} else {
|
|
4871
|
+
this.process.stdout?.on("data", (chunk) => {
|
|
4872
|
+
const text = chunk.toString();
|
|
4873
|
+
output += text;
|
|
4874
|
+
options.onOutput?.(text);
|
|
4875
|
+
});
|
|
4876
|
+
}
|
|
4877
|
+
this.process.stderr?.on("data", (chunk) => {
|
|
4730
4878
|
const text = chunk.toString();
|
|
4731
|
-
|
|
4732
|
-
|
|
4879
|
+
errorOutput += text;
|
|
4880
|
+
log.debug("sandboxed claude stderr", { text: text.slice(0, 500) });
|
|
4733
4881
|
});
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4882
|
+
this.process.on("close", (code) => {
|
|
4883
|
+
this.process = null;
|
|
4884
|
+
if (this.aborted) {
|
|
4885
|
+
resolve2({
|
|
4886
|
+
success: false,
|
|
4887
|
+
output,
|
|
4888
|
+
error: "Aborted by user",
|
|
4889
|
+
exitCode: code ?? 143
|
|
4890
|
+
});
|
|
4891
|
+
return;
|
|
4892
|
+
}
|
|
4893
|
+
if (code === 0) {
|
|
4894
|
+
resolve2({ success: true, output, exitCode: 0 });
|
|
4895
|
+
} else {
|
|
4896
|
+
resolve2({
|
|
4897
|
+
success: false,
|
|
4898
|
+
output,
|
|
4899
|
+
error: errorOutput || `sandboxed claude exited with code ${code}`,
|
|
4900
|
+
exitCode: code ?? 1
|
|
4901
|
+
});
|
|
4902
|
+
}
|
|
4903
|
+
});
|
|
4904
|
+
this.process.on("error", (err) => {
|
|
4905
|
+
this.process = null;
|
|
4743
4906
|
resolve2({
|
|
4744
4907
|
success: false,
|
|
4745
4908
|
output,
|
|
4746
|
-
error:
|
|
4747
|
-
exitCode:
|
|
4909
|
+
error: `Failed to spawn docker sandbox: ${err.message}`,
|
|
4910
|
+
exitCode: 1
|
|
4748
4911
|
});
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
} else {
|
|
4754
|
-
resolve2({
|
|
4755
|
-
success: false,
|
|
4756
|
-
output,
|
|
4757
|
-
error: errorOutput || `sandboxed claude exited with code ${code}`,
|
|
4758
|
-
exitCode: code ?? 1
|
|
4912
|
+
});
|
|
4913
|
+
if (options.signal) {
|
|
4914
|
+
options.signal.addEventListener("abort", () => {
|
|
4915
|
+
this.abort();
|
|
4759
4916
|
});
|
|
4760
4917
|
}
|
|
4761
4918
|
});
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
success: false,
|
|
4766
|
-
output,
|
|
4767
|
-
error: `Failed to spawn docker sandbox: ${err.message}`,
|
|
4768
|
-
exitCode: 1
|
|
4769
|
-
});
|
|
4770
|
-
});
|
|
4771
|
-
if (options.signal) {
|
|
4772
|
-
options.signal.addEventListener("abort", () => {
|
|
4773
|
-
this.abort();
|
|
4774
|
-
});
|
|
4775
|
-
}
|
|
4776
|
-
});
|
|
4919
|
+
} finally {
|
|
4920
|
+
backup.restore();
|
|
4921
|
+
}
|
|
4777
4922
|
}
|
|
4778
4923
|
abort() {
|
|
4779
4924
|
this.aborted = true;
|
|
@@ -5043,6 +5188,7 @@ class SandboxedCodexRunner {
|
|
|
5043
5188
|
}
|
|
5044
5189
|
const codexArgs = buildCodexArgs(options.model);
|
|
5045
5190
|
options.onStatusChange?.("Syncing sandbox...");
|
|
5191
|
+
const backup = backupIgnoredFiles(options.cwd);
|
|
5046
5192
|
await enforceSandboxIgnore(this.sandboxName, options.cwd);
|
|
5047
5193
|
if (!this.codexInstalled) {
|
|
5048
5194
|
options.onStatusChange?.("Checking codex...");
|
|
@@ -5065,111 +5211,115 @@ class SandboxedCodexRunner {
|
|
|
5065
5211
|
args: dockerArgs.join(" "),
|
|
5066
5212
|
cwd: options.cwd
|
|
5067
5213
|
});
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5214
|
+
try {
|
|
5215
|
+
return await new Promise((resolve2) => {
|
|
5216
|
+
let rawOutput = "";
|
|
5217
|
+
let errorOutput = "";
|
|
5218
|
+
this.process = spawn5("docker", dockerArgs, {
|
|
5219
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
5220
|
+
env: process.env
|
|
5221
|
+
});
|
|
5222
|
+
let agentMessages = [];
|
|
5223
|
+
const flushAgentMessages = () => {
|
|
5224
|
+
if (agentMessages.length > 0) {
|
|
5225
|
+
options.onOutput?.(agentMessages.join(`
|
|
5079
5226
|
|
|
5080
5227
|
`));
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5228
|
+
agentMessages = [];
|
|
5229
|
+
}
|
|
5230
|
+
};
|
|
5231
|
+
let lineBuffer = "";
|
|
5232
|
+
this.process.stdout?.on("data", (chunk) => {
|
|
5233
|
+
lineBuffer += chunk.toString();
|
|
5234
|
+
const lines = lineBuffer.split(`
|
|
5088
5235
|
`);
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5236
|
+
lineBuffer = lines.pop() ?? "";
|
|
5237
|
+
for (const line of lines) {
|
|
5238
|
+
if (!line.trim())
|
|
5239
|
+
continue;
|
|
5240
|
+
rawOutput += `${line}
|
|
5094
5241
|
`;
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5242
|
+
log.debug("sandboxed codex stdout line", { line });
|
|
5243
|
+
try {
|
|
5244
|
+
const event = JSON.parse(line);
|
|
5245
|
+
const { type, item } = event;
|
|
5246
|
+
if (type === "item.started" && item?.type === "command_execution") {
|
|
5247
|
+
const cmd = (item.command ?? "").split(`
|
|
5101
5248
|
`)[0].slice(0, 80);
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5249
|
+
options.onToolActivity?.(`running: ${cmd}`);
|
|
5250
|
+
} else if (type === "item.completed" && item?.type === "command_execution") {
|
|
5251
|
+
const code = item.exit_code;
|
|
5252
|
+
options.onToolActivity?.(code === 0 ? "done" : `exit ${code}`);
|
|
5253
|
+
} else if (type === "item.completed" && item?.type === "reasoning") {
|
|
5254
|
+
const text = (item.text ?? "").trim().replace(/\*\*([^*]+)\*\*/g, "$1").replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "$1");
|
|
5255
|
+
if (text)
|
|
5256
|
+
options.onToolActivity?.(text);
|
|
5257
|
+
} else if (type === "item.completed" && item?.type === "agent_message") {
|
|
5258
|
+
const text = item.text ?? "";
|
|
5259
|
+
if (text) {
|
|
5260
|
+
agentMessages.push(text);
|
|
5261
|
+
options.onToolActivity?.(text.split(`
|
|
5115
5262
|
`)[0].slice(0, 80));
|
|
5263
|
+
}
|
|
5264
|
+
} else if (type === "turn.completed") {
|
|
5265
|
+
flushAgentMessages();
|
|
5116
5266
|
}
|
|
5117
|
-
}
|
|
5118
|
-
|
|
5119
|
-
}
|
|
5120
|
-
} catch {
|
|
5121
|
-
const newLine = `${line}
|
|
5267
|
+
} catch {
|
|
5268
|
+
const newLine = `${line}
|
|
5122
5269
|
`;
|
|
5123
|
-
|
|
5124
|
-
|
|
5270
|
+
rawOutput += newLine;
|
|
5271
|
+
options.onOutput?.(newLine);
|
|
5272
|
+
}
|
|
5125
5273
|
}
|
|
5126
|
-
}
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5274
|
+
});
|
|
5275
|
+
this.process.stderr?.on("data", (chunk) => {
|
|
5276
|
+
const text = chunk.toString();
|
|
5277
|
+
errorOutput += text;
|
|
5278
|
+
log.debug("sandboxed codex stderr", { text: text.slice(0, 500) });
|
|
5279
|
+
});
|
|
5280
|
+
this.process.on("close", (code) => {
|
|
5281
|
+
this.process = null;
|
|
5282
|
+
flushAgentMessages();
|
|
5283
|
+
if (this.aborted) {
|
|
5284
|
+
resolve2({
|
|
5285
|
+
success: false,
|
|
5286
|
+
output: rawOutput,
|
|
5287
|
+
error: "Aborted by user",
|
|
5288
|
+
exitCode: code ?? 143
|
|
5289
|
+
});
|
|
5290
|
+
return;
|
|
5291
|
+
}
|
|
5292
|
+
if (code === 0) {
|
|
5293
|
+
resolve2({ success: true, output: rawOutput, exitCode: 0 });
|
|
5294
|
+
} else {
|
|
5295
|
+
resolve2({
|
|
5296
|
+
success: false,
|
|
5297
|
+
output: rawOutput,
|
|
5298
|
+
error: errorOutput || `sandboxed codex exited with code ${code}`,
|
|
5299
|
+
exitCode: code ?? 1
|
|
5300
|
+
});
|
|
5301
|
+
}
|
|
5302
|
+
});
|
|
5303
|
+
this.process.on("error", (err) => {
|
|
5304
|
+
this.process = null;
|
|
5137
5305
|
resolve2({
|
|
5138
5306
|
success: false,
|
|
5139
5307
|
output: rawOutput,
|
|
5140
|
-
error:
|
|
5141
|
-
exitCode:
|
|
5308
|
+
error: `Failed to spawn docker sandbox: ${err.message}`,
|
|
5309
|
+
exitCode: 1
|
|
5142
5310
|
});
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
} else {
|
|
5148
|
-
resolve2({
|
|
5149
|
-
success: false,
|
|
5150
|
-
output: rawOutput,
|
|
5151
|
-
error: errorOutput || `sandboxed codex exited with code ${code}`,
|
|
5152
|
-
exitCode: code ?? 1
|
|
5311
|
+
});
|
|
5312
|
+
if (options.signal) {
|
|
5313
|
+
options.signal.addEventListener("abort", () => {
|
|
5314
|
+
this.abort();
|
|
5153
5315
|
});
|
|
5154
5316
|
}
|
|
5317
|
+
this.process.stdin?.write(options.prompt);
|
|
5318
|
+
this.process.stdin?.end();
|
|
5155
5319
|
});
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
success: false,
|
|
5160
|
-
output: rawOutput,
|
|
5161
|
-
error: `Failed to spawn docker sandbox: ${err.message}`,
|
|
5162
|
-
exitCode: 1
|
|
5163
|
-
});
|
|
5164
|
-
});
|
|
5165
|
-
if (options.signal) {
|
|
5166
|
-
options.signal.addEventListener("abort", () => {
|
|
5167
|
-
this.abort();
|
|
5168
|
-
});
|
|
5169
|
-
}
|
|
5170
|
-
this.process.stdin?.write(options.prompt);
|
|
5171
|
-
this.process.stdin?.end();
|
|
5172
|
-
});
|
|
5320
|
+
} finally {
|
|
5321
|
+
backup.restore();
|
|
5322
|
+
}
|
|
5173
5323
|
}
|
|
5174
5324
|
abort() {
|
|
5175
5325
|
this.aborted = true;
|
|
@@ -6887,7 +7037,7 @@ var init_sprint = __esm(() => {
|
|
|
6887
7037
|
|
|
6888
7038
|
// src/core/prompt-builder.ts
|
|
6889
7039
|
import { execSync as execSync7 } from "node:child_process";
|
|
6890
|
-
import { existsSync as existsSync14, readdirSync as
|
|
7040
|
+
import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "node:fs";
|
|
6891
7041
|
import { join as join13 } from "node:path";
|
|
6892
7042
|
function buildExecutionPrompt(ctx) {
|
|
6893
7043
|
const sections = [];
|
|
@@ -6965,7 +7115,7 @@ ${learnings}
|
|
|
6965
7115
|
const discussionsDir = join13(projectRoot, ".locus", "discussions");
|
|
6966
7116
|
if (existsSync14(discussionsDir)) {
|
|
6967
7117
|
try {
|
|
6968
|
-
const files =
|
|
7118
|
+
const files = readdirSync4(discussionsDir).filter((f) => f.endsWith(".md")).slice(0, 3);
|
|
6969
7119
|
for (const file of files) {
|
|
6970
7120
|
const content = readFileSafe(join13(discussionsDir, file));
|
|
6971
7121
|
if (content) {
|
|
@@ -7583,8 +7733,8 @@ var init_commands = __esm(() => {
|
|
|
7583
7733
|
});
|
|
7584
7734
|
|
|
7585
7735
|
// src/repl/completions.ts
|
|
7586
|
-
import { readdirSync as
|
|
7587
|
-
import { basename as basename2, dirname as
|
|
7736
|
+
import { readdirSync as readdirSync5 } from "node:fs";
|
|
7737
|
+
import { basename as basename2, dirname as dirname4, join as join14 } from "node:path";
|
|
7588
7738
|
|
|
7589
7739
|
class SlashCommandCompletion {
|
|
7590
7740
|
commands;
|
|
@@ -7639,9 +7789,9 @@ class FilePathCompletion {
|
|
|
7639
7789
|
}
|
|
7640
7790
|
findMatches(partial) {
|
|
7641
7791
|
try {
|
|
7642
|
-
const dir = partial.includes("/") ? join14(this.projectRoot,
|
|
7792
|
+
const dir = partial.includes("/") ? join14(this.projectRoot, dirname4(partial)) : this.projectRoot;
|
|
7643
7793
|
const prefix = basename2(partial);
|
|
7644
|
-
const entries =
|
|
7794
|
+
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
7645
7795
|
return entries.filter((e) => {
|
|
7646
7796
|
if (e.name.startsWith("."))
|
|
7647
7797
|
return false;
|
|
@@ -7650,7 +7800,7 @@ class FilePathCompletion {
|
|
|
7650
7800
|
return e.name.startsWith(prefix);
|
|
7651
7801
|
}).map((e) => {
|
|
7652
7802
|
const name = e.isDirectory() ? `${e.name}/` : e.name;
|
|
7653
|
-
return partial.includes("/") ? `${
|
|
7803
|
+
return partial.includes("/") ? `${dirname4(partial)}/${name}` : name;
|
|
7654
7804
|
}).slice(0, 20);
|
|
7655
7805
|
} catch {
|
|
7656
7806
|
return [];
|
|
@@ -7675,8 +7825,8 @@ class CombinedCompletion {
|
|
|
7675
7825
|
var init_completions = () => {};
|
|
7676
7826
|
|
|
7677
7827
|
// src/repl/input-history.ts
|
|
7678
|
-
import { existsSync as existsSync15, mkdirSync as
|
|
7679
|
-
import { dirname as
|
|
7828
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "node:fs";
|
|
7829
|
+
import { dirname as dirname5, join as join15 } from "node:path";
|
|
7680
7830
|
|
|
7681
7831
|
class InputHistory {
|
|
7682
7832
|
entries = [];
|
|
@@ -7730,9 +7880,9 @@ class InputHistory {
|
|
|
7730
7880
|
}
|
|
7731
7881
|
save() {
|
|
7732
7882
|
try {
|
|
7733
|
-
const dir =
|
|
7883
|
+
const dir = dirname5(this.filePath);
|
|
7734
7884
|
if (!existsSync15(dir)) {
|
|
7735
|
-
|
|
7885
|
+
mkdirSync10(dir, { recursive: true });
|
|
7736
7886
|
}
|
|
7737
7887
|
const content = this.entries.map((e) => this.escape(e)).join(`
|
|
7738
7888
|
`);
|
|
@@ -7763,8 +7913,8 @@ var init_model_config = __esm(() => {
|
|
|
7763
7913
|
// src/repl/session-manager.ts
|
|
7764
7914
|
import {
|
|
7765
7915
|
existsSync as existsSync16,
|
|
7766
|
-
mkdirSync as
|
|
7767
|
-
readdirSync as
|
|
7916
|
+
mkdirSync as mkdirSync11,
|
|
7917
|
+
readdirSync as readdirSync6,
|
|
7768
7918
|
readFileSync as readFileSync11,
|
|
7769
7919
|
unlinkSync as unlinkSync3,
|
|
7770
7920
|
writeFileSync as writeFileSync7
|
|
@@ -7776,7 +7926,7 @@ class SessionManager {
|
|
|
7776
7926
|
constructor(projectRoot) {
|
|
7777
7927
|
this.sessionsDir = join16(projectRoot, ".locus", "sessions");
|
|
7778
7928
|
if (!existsSync16(this.sessionsDir)) {
|
|
7779
|
-
|
|
7929
|
+
mkdirSync11(this.sessionsDir, { recursive: true });
|
|
7780
7930
|
}
|
|
7781
7931
|
}
|
|
7782
7932
|
create(options) {
|
|
@@ -7901,7 +8051,7 @@ class SessionManager {
|
|
|
7901
8051
|
}
|
|
7902
8052
|
listSessionFiles() {
|
|
7903
8053
|
try {
|
|
7904
|
-
return
|
|
8054
|
+
return readdirSync6(this.sessionsDir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).map((f) => join16(this.sessionsDir, f));
|
|
7905
8055
|
} catch {
|
|
7906
8056
|
return [];
|
|
7907
8057
|
}
|
|
@@ -8945,12 +9095,12 @@ var init_conflict = __esm(() => {
|
|
|
8945
9095
|
// src/core/run-state.ts
|
|
8946
9096
|
import {
|
|
8947
9097
|
existsSync as existsSync18,
|
|
8948
|
-
mkdirSync as
|
|
9098
|
+
mkdirSync as mkdirSync12,
|
|
8949
9099
|
readFileSync as readFileSync12,
|
|
8950
9100
|
unlinkSync as unlinkSync4,
|
|
8951
9101
|
writeFileSync as writeFileSync8
|
|
8952
9102
|
} from "node:fs";
|
|
8953
|
-
import { dirname as
|
|
9103
|
+
import { dirname as dirname6, join as join18 } from "node:path";
|
|
8954
9104
|
function getRunStatePath(projectRoot) {
|
|
8955
9105
|
return join18(projectRoot, ".locus", "run-state.json");
|
|
8956
9106
|
}
|
|
@@ -8967,9 +9117,9 @@ function loadRunState(projectRoot) {
|
|
|
8967
9117
|
}
|
|
8968
9118
|
function saveRunState(projectRoot, state) {
|
|
8969
9119
|
const path = getRunStatePath(projectRoot);
|
|
8970
|
-
const dir =
|
|
9120
|
+
const dir = dirname6(path);
|
|
8971
9121
|
if (!existsSync18(dir)) {
|
|
8972
|
-
|
|
9122
|
+
mkdirSync12(dir, { recursive: true });
|
|
8973
9123
|
}
|
|
8974
9124
|
writeFileSync8(path, `${JSON.stringify(state, null, 2)}
|
|
8975
9125
|
`, "utf-8");
|
|
@@ -9117,7 +9267,7 @@ var init_shutdown = __esm(() => {
|
|
|
9117
9267
|
|
|
9118
9268
|
// src/core/worktree.ts
|
|
9119
9269
|
import { execSync as execSync13 } from "node:child_process";
|
|
9120
|
-
import { existsSync as existsSync19, readdirSync as
|
|
9270
|
+
import { existsSync as existsSync19, readdirSync as readdirSync7, realpathSync, statSync as statSync4 } from "node:fs";
|
|
9121
9271
|
import { join as join19 } from "node:path";
|
|
9122
9272
|
function git4(args, cwd) {
|
|
9123
9273
|
return execSync13(`git ${args}`, {
|
|
@@ -9207,7 +9357,7 @@ function listWorktrees(projectRoot) {
|
|
|
9207
9357
|
if (!existsSync19(worktreeDir)) {
|
|
9208
9358
|
return [];
|
|
9209
9359
|
}
|
|
9210
|
-
const entries =
|
|
9360
|
+
const entries = readdirSync7(worktreeDir).filter((entry) => entry.startsWith("issue-"));
|
|
9211
9361
|
const gitWorktreeList = gitSafe3("worktree list --porcelain", projectRoot);
|
|
9212
9362
|
const activeWorktrees = new Set;
|
|
9213
9363
|
if (gitWorktreeList) {
|
|
@@ -10062,8 +10212,8 @@ __export(exports_plan, {
|
|
|
10062
10212
|
});
|
|
10063
10213
|
import {
|
|
10064
10214
|
existsSync as existsSync20,
|
|
10065
|
-
mkdirSync as
|
|
10066
|
-
readdirSync as
|
|
10215
|
+
mkdirSync as mkdirSync13,
|
|
10216
|
+
readdirSync as readdirSync8,
|
|
10067
10217
|
readFileSync as readFileSync13,
|
|
10068
10218
|
writeFileSync as writeFileSync9
|
|
10069
10219
|
} from "node:fs";
|
|
@@ -10103,7 +10253,7 @@ function getPlansDir(projectRoot) {
|
|
|
10103
10253
|
function ensurePlansDir(projectRoot) {
|
|
10104
10254
|
const dir = getPlansDir(projectRoot);
|
|
10105
10255
|
if (!existsSync20(dir)) {
|
|
10106
|
-
|
|
10256
|
+
mkdirSync13(dir, { recursive: true });
|
|
10107
10257
|
}
|
|
10108
10258
|
return dir;
|
|
10109
10259
|
}
|
|
@@ -10114,7 +10264,7 @@ function loadPlanFile(projectRoot, id) {
|
|
|
10114
10264
|
const dir = getPlansDir(projectRoot);
|
|
10115
10265
|
if (!existsSync20(dir))
|
|
10116
10266
|
return null;
|
|
10117
|
-
const files =
|
|
10267
|
+
const files = readdirSync8(dir).filter((f) => f.endsWith(".json"));
|
|
10118
10268
|
const match = files.find((f) => f.startsWith(id));
|
|
10119
10269
|
if (!match)
|
|
10120
10270
|
return null;
|
|
@@ -10170,7 +10320,7 @@ function handleListPlans(projectRoot) {
|
|
|
10170
10320
|
`);
|
|
10171
10321
|
return;
|
|
10172
10322
|
}
|
|
10173
|
-
const files =
|
|
10323
|
+
const files = readdirSync8(dir).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
10174
10324
|
if (files.length === 0) {
|
|
10175
10325
|
process.stderr.write(`${dim("No saved plans yet.")}
|
|
10176
10326
|
`);
|
|
@@ -11198,8 +11348,8 @@ __export(exports_discuss, {
|
|
|
11198
11348
|
});
|
|
11199
11349
|
import {
|
|
11200
11350
|
existsSync as existsSync22,
|
|
11201
|
-
mkdirSync as
|
|
11202
|
-
readdirSync as
|
|
11351
|
+
mkdirSync as mkdirSync14,
|
|
11352
|
+
readdirSync as readdirSync9,
|
|
11203
11353
|
readFileSync as readFileSync15,
|
|
11204
11354
|
unlinkSync as unlinkSync5,
|
|
11205
11355
|
writeFileSync as writeFileSync10
|
|
@@ -11231,7 +11381,7 @@ function getDiscussionsDir(projectRoot) {
|
|
|
11231
11381
|
function ensureDiscussionsDir(projectRoot) {
|
|
11232
11382
|
const dir = getDiscussionsDir(projectRoot);
|
|
11233
11383
|
if (!existsSync22(dir)) {
|
|
11234
|
-
|
|
11384
|
+
mkdirSync14(dir, { recursive: true });
|
|
11235
11385
|
}
|
|
11236
11386
|
return dir;
|
|
11237
11387
|
}
|
|
@@ -11270,7 +11420,7 @@ function listDiscussions(projectRoot) {
|
|
|
11270
11420
|
`);
|
|
11271
11421
|
return;
|
|
11272
11422
|
}
|
|
11273
|
-
const files =
|
|
11423
|
+
const files = readdirSync9(dir).filter((f) => f.endsWith(".md")).sort().reverse();
|
|
11274
11424
|
if (files.length === 0) {
|
|
11275
11425
|
process.stderr.write(`${dim("No discussions yet.")}
|
|
11276
11426
|
`);
|
|
@@ -11305,7 +11455,7 @@ function showDiscussion(projectRoot, id) {
|
|
|
11305
11455
|
`);
|
|
11306
11456
|
return;
|
|
11307
11457
|
}
|
|
11308
|
-
const files =
|
|
11458
|
+
const files = readdirSync9(dir).filter((f) => f.endsWith(".md"));
|
|
11309
11459
|
const match = files.find((f) => f.startsWith(id));
|
|
11310
11460
|
if (!match) {
|
|
11311
11461
|
process.stderr.write(`${red("✗")} Discussion "${id}" not found.
|
|
@@ -11328,7 +11478,7 @@ function deleteDiscussion(projectRoot, id) {
|
|
|
11328
11478
|
`);
|
|
11329
11479
|
return;
|
|
11330
11480
|
}
|
|
11331
|
-
const files =
|
|
11481
|
+
const files = readdirSync9(dir).filter((f) => f.endsWith(".md"));
|
|
11332
11482
|
const match = files.find((f) => f.startsWith(id));
|
|
11333
11483
|
if (!match) {
|
|
11334
11484
|
process.stderr.write(`${red("✗")} Discussion "${id}" not found.
|
|
@@ -11353,7 +11503,7 @@ async function convertDiscussionToPlan(projectRoot, id) {
|
|
|
11353
11503
|
`);
|
|
11354
11504
|
return;
|
|
11355
11505
|
}
|
|
11356
|
-
const files =
|
|
11506
|
+
const files = readdirSync9(dir).filter((f) => f.endsWith(".md"));
|
|
11357
11507
|
const match = files.find((f) => f.startsWith(id));
|
|
11358
11508
|
if (!match) {
|
|
11359
11509
|
process.stderr.write(`${red("✗")} Discussion "${id}" not found.
|
|
@@ -11577,7 +11727,7 @@ __export(exports_artifacts, {
|
|
|
11577
11727
|
formatDate: () => formatDate2,
|
|
11578
11728
|
artifactsCommand: () => artifactsCommand
|
|
11579
11729
|
});
|
|
11580
|
-
import { existsSync as existsSync23, readdirSync as
|
|
11730
|
+
import { existsSync as existsSync23, readdirSync as readdirSync10, readFileSync as readFileSync16, statSync as statSync5 } from "node:fs";
|
|
11581
11731
|
import { join as join23 } from "node:path";
|
|
11582
11732
|
function printHelp5() {
|
|
11583
11733
|
process.stderr.write(`
|
|
@@ -11604,9 +11754,9 @@ function listArtifacts(projectRoot) {
|
|
|
11604
11754
|
const dir = getArtifactsDir(projectRoot);
|
|
11605
11755
|
if (!existsSync23(dir))
|
|
11606
11756
|
return [];
|
|
11607
|
-
return
|
|
11757
|
+
return readdirSync10(dir).filter((f) => f.endsWith(".md")).map((fileName) => {
|
|
11608
11758
|
const filePath = join23(dir, fileName);
|
|
11609
|
-
const stat =
|
|
11759
|
+
const stat = statSync5(filePath);
|
|
11610
11760
|
return {
|
|
11611
11761
|
name: fileName.replace(/\.md$/, ""),
|
|
11612
11762
|
fileName,
|
|
@@ -11621,7 +11771,7 @@ function readArtifact(projectRoot, name) {
|
|
|
11621
11771
|
const filePath = join23(dir, fileName);
|
|
11622
11772
|
if (!existsSync23(filePath))
|
|
11623
11773
|
return null;
|
|
11624
|
-
const stat =
|
|
11774
|
+
const stat = statSync5(filePath);
|
|
11625
11775
|
return {
|
|
11626
11776
|
content: readFileSync16(filePath, "utf-8"),
|
|
11627
11777
|
info: {
|
|
@@ -11961,7 +12111,12 @@ async function handleAgentLogin(projectRoot, agent) {
|
|
|
11961
12111
|
});
|
|
11962
12112
|
await new Promise((resolve2) => {
|
|
11963
12113
|
child.on("close", async (code) => {
|
|
11964
|
-
|
|
12114
|
+
const backup = backupIgnoredFiles(projectRoot);
|
|
12115
|
+
try {
|
|
12116
|
+
await enforceSandboxIgnore(sandboxName, projectRoot);
|
|
12117
|
+
} finally {
|
|
12118
|
+
backup.restore();
|
|
12119
|
+
}
|
|
11965
12120
|
if (code === 0) {
|
|
11966
12121
|
process.stderr.write(`
|
|
11967
12122
|
${green("✓")} ${agent} session ended. Auth should now be persisted in the sandbox.
|
|
@@ -12408,7 +12563,12 @@ async function createProviderSandbox(provider, sandboxName, projectRoot) {
|
|
|
12408
12563
|
if (provider === "codex") {
|
|
12409
12564
|
await ensureCodexInSandbox(sandboxName);
|
|
12410
12565
|
}
|
|
12411
|
-
|
|
12566
|
+
const backup = backupIgnoredFiles(projectRoot);
|
|
12567
|
+
try {
|
|
12568
|
+
await enforceSandboxIgnore(sandboxName, projectRoot);
|
|
12569
|
+
} finally {
|
|
12570
|
+
backup.restore();
|
|
12571
|
+
}
|
|
12412
12572
|
return true;
|
|
12413
12573
|
}
|
|
12414
12574
|
async function ensurePackageManagerInSandbox(sandboxName, pm) {
|