@locusai/cli 0.21.4 → 0.21.6
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 -208
- 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;
|
|
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,20 @@ 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 = { restore() {} };
|
|
4636
4774
|
});
|
|
4637
4775
|
|
|
4638
4776
|
// src/ai/claude-sandbox.ts
|
|
@@ -4669,6 +4807,7 @@ class SandboxedClaudeRunner {
|
|
|
4669
4807
|
}
|
|
4670
4808
|
const claudeArgs = ["-p", options.prompt, ...buildClaudeArgs(options)];
|
|
4671
4809
|
options.onStatusChange?.("Syncing sandbox...");
|
|
4810
|
+
const backup = backupIgnoredFiles(options.cwd);
|
|
4672
4811
|
await enforceSandboxIgnore(this.sandboxName, options.cwd);
|
|
4673
4812
|
options.onStatusChange?.("Thinking...");
|
|
4674
4813
|
const dockerArgs = [
|
|
@@ -4685,95 +4824,99 @@ class SandboxedClaudeRunner {
|
|
|
4685
4824
|
args: dockerArgs.join(" "),
|
|
4686
4825
|
cwd: options.cwd
|
|
4687
4826
|
});
|
|
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
|
-
|
|
4827
|
+
try {
|
|
4828
|
+
return await new Promise((resolve2) => {
|
|
4829
|
+
let output = "";
|
|
4830
|
+
let errorOutput = "";
|
|
4831
|
+
this.process = spawn3("docker", dockerArgs, {
|
|
4832
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
4833
|
+
env: process.env
|
|
4834
|
+
});
|
|
4835
|
+
if (options.verbose) {
|
|
4836
|
+
let lineBuffer = "";
|
|
4837
|
+
const seenToolIds = new Set;
|
|
4838
|
+
this.process.stdout?.on("data", (chunk) => {
|
|
4839
|
+
lineBuffer += chunk.toString();
|
|
4840
|
+
const lines = lineBuffer.split(`
|
|
4841
|
+
`);
|
|
4842
|
+
lineBuffer = lines.pop() ?? "";
|
|
4843
|
+
for (const line of lines) {
|
|
4844
|
+
if (!line.trim())
|
|
4845
|
+
continue;
|
|
4846
|
+
try {
|
|
4847
|
+
const event = JSON.parse(line);
|
|
4848
|
+
if (event.type === "assistant" && event.message?.content) {
|
|
4849
|
+
for (const item of event.message.content) {
|
|
4850
|
+
if (item.type === "tool_use" && item.id && !seenToolIds.has(item.id)) {
|
|
4851
|
+
seenToolIds.add(item.id);
|
|
4852
|
+
options.onToolActivity?.(formatToolCall2(item.name ?? "", item.input ?? {}));
|
|
4853
|
+
}
|
|
4713
4854
|
}
|
|
4855
|
+
} else if (event.type === "result") {
|
|
4856
|
+
const text = event.result ?? "";
|
|
4857
|
+
output = text;
|
|
4858
|
+
options.onOutput?.(text);
|
|
4714
4859
|
}
|
|
4715
|
-
}
|
|
4716
|
-
const
|
|
4717
|
-
output = text;
|
|
4718
|
-
options.onOutput?.(text);
|
|
4719
|
-
}
|
|
4720
|
-
} catch {
|
|
4721
|
-
const newLine = `${line}
|
|
4860
|
+
} catch {
|
|
4861
|
+
const newLine = `${line}
|
|
4722
4862
|
`;
|
|
4723
|
-
|
|
4724
|
-
|
|
4863
|
+
output += newLine;
|
|
4864
|
+
options.onOutput?.(newLine);
|
|
4865
|
+
}
|
|
4725
4866
|
}
|
|
4726
|
-
}
|
|
4727
|
-
}
|
|
4728
|
-
|
|
4729
|
-
|
|
4867
|
+
});
|
|
4868
|
+
} else {
|
|
4869
|
+
this.process.stdout?.on("data", (chunk) => {
|
|
4870
|
+
const text = chunk.toString();
|
|
4871
|
+
output += text;
|
|
4872
|
+
options.onOutput?.(text);
|
|
4873
|
+
});
|
|
4874
|
+
}
|
|
4875
|
+
this.process.stderr?.on("data", (chunk) => {
|
|
4730
4876
|
const text = chunk.toString();
|
|
4731
|
-
|
|
4732
|
-
|
|
4877
|
+
errorOutput += text;
|
|
4878
|
+
log.debug("sandboxed claude stderr", { text: text.slice(0, 500) });
|
|
4733
4879
|
});
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4880
|
+
this.process.on("close", (code) => {
|
|
4881
|
+
this.process = null;
|
|
4882
|
+
if (this.aborted) {
|
|
4883
|
+
resolve2({
|
|
4884
|
+
success: false,
|
|
4885
|
+
output,
|
|
4886
|
+
error: "Aborted by user",
|
|
4887
|
+
exitCode: code ?? 143
|
|
4888
|
+
});
|
|
4889
|
+
return;
|
|
4890
|
+
}
|
|
4891
|
+
if (code === 0) {
|
|
4892
|
+
resolve2({ success: true, output, exitCode: 0 });
|
|
4893
|
+
} else {
|
|
4894
|
+
resolve2({
|
|
4895
|
+
success: false,
|
|
4896
|
+
output,
|
|
4897
|
+
error: errorOutput || `sandboxed claude exited with code ${code}`,
|
|
4898
|
+
exitCode: code ?? 1
|
|
4899
|
+
});
|
|
4900
|
+
}
|
|
4901
|
+
});
|
|
4902
|
+
this.process.on("error", (err) => {
|
|
4903
|
+
this.process = null;
|
|
4743
4904
|
resolve2({
|
|
4744
4905
|
success: false,
|
|
4745
4906
|
output,
|
|
4746
|
-
error:
|
|
4747
|
-
exitCode:
|
|
4907
|
+
error: `Failed to spawn docker sandbox: ${err.message}`,
|
|
4908
|
+
exitCode: 1
|
|
4748
4909
|
});
|
|
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
|
|
4910
|
+
});
|
|
4911
|
+
if (options.signal) {
|
|
4912
|
+
options.signal.addEventListener("abort", () => {
|
|
4913
|
+
this.abort();
|
|
4759
4914
|
});
|
|
4760
4915
|
}
|
|
4761
4916
|
});
|
|
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
|
-
});
|
|
4917
|
+
} finally {
|
|
4918
|
+
backup.restore();
|
|
4919
|
+
}
|
|
4777
4920
|
}
|
|
4778
4921
|
abort() {
|
|
4779
4922
|
this.aborted = true;
|
|
@@ -5043,6 +5186,7 @@ class SandboxedCodexRunner {
|
|
|
5043
5186
|
}
|
|
5044
5187
|
const codexArgs = buildCodexArgs(options.model);
|
|
5045
5188
|
options.onStatusChange?.("Syncing sandbox...");
|
|
5189
|
+
const backup = backupIgnoredFiles(options.cwd);
|
|
5046
5190
|
await enforceSandboxIgnore(this.sandboxName, options.cwd);
|
|
5047
5191
|
if (!this.codexInstalled) {
|
|
5048
5192
|
options.onStatusChange?.("Checking codex...");
|
|
@@ -5065,111 +5209,115 @@ class SandboxedCodexRunner {
|
|
|
5065
5209
|
args: dockerArgs.join(" "),
|
|
5066
5210
|
cwd: options.cwd
|
|
5067
5211
|
});
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5212
|
+
try {
|
|
5213
|
+
return await new Promise((resolve2) => {
|
|
5214
|
+
let rawOutput = "";
|
|
5215
|
+
let errorOutput = "";
|
|
5216
|
+
this.process = spawn5("docker", dockerArgs, {
|
|
5217
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
5218
|
+
env: process.env
|
|
5219
|
+
});
|
|
5220
|
+
let agentMessages = [];
|
|
5221
|
+
const flushAgentMessages = () => {
|
|
5222
|
+
if (agentMessages.length > 0) {
|
|
5223
|
+
options.onOutput?.(agentMessages.join(`
|
|
5079
5224
|
|
|
5080
5225
|
`));
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5226
|
+
agentMessages = [];
|
|
5227
|
+
}
|
|
5228
|
+
};
|
|
5229
|
+
let lineBuffer = "";
|
|
5230
|
+
this.process.stdout?.on("data", (chunk) => {
|
|
5231
|
+
lineBuffer += chunk.toString();
|
|
5232
|
+
const lines = lineBuffer.split(`
|
|
5088
5233
|
`);
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5234
|
+
lineBuffer = lines.pop() ?? "";
|
|
5235
|
+
for (const line of lines) {
|
|
5236
|
+
if (!line.trim())
|
|
5237
|
+
continue;
|
|
5238
|
+
rawOutput += `${line}
|
|
5094
5239
|
`;
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5240
|
+
log.debug("sandboxed codex stdout line", { line });
|
|
5241
|
+
try {
|
|
5242
|
+
const event = JSON.parse(line);
|
|
5243
|
+
const { type, item } = event;
|
|
5244
|
+
if (type === "item.started" && item?.type === "command_execution") {
|
|
5245
|
+
const cmd = (item.command ?? "").split(`
|
|
5101
5246
|
`)[0].slice(0, 80);
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5247
|
+
options.onToolActivity?.(`running: ${cmd}`);
|
|
5248
|
+
} else if (type === "item.completed" && item?.type === "command_execution") {
|
|
5249
|
+
const code = item.exit_code;
|
|
5250
|
+
options.onToolActivity?.(code === 0 ? "done" : `exit ${code}`);
|
|
5251
|
+
} else if (type === "item.completed" && item?.type === "reasoning") {
|
|
5252
|
+
const text = (item.text ?? "").trim().replace(/\*\*([^*]+)\*\*/g, "$1").replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "$1");
|
|
5253
|
+
if (text)
|
|
5254
|
+
options.onToolActivity?.(text);
|
|
5255
|
+
} else if (type === "item.completed" && item?.type === "agent_message") {
|
|
5256
|
+
const text = item.text ?? "";
|
|
5257
|
+
if (text) {
|
|
5258
|
+
agentMessages.push(text);
|
|
5259
|
+
options.onToolActivity?.(text.split(`
|
|
5115
5260
|
`)[0].slice(0, 80));
|
|
5261
|
+
}
|
|
5262
|
+
} else if (type === "turn.completed") {
|
|
5263
|
+
flushAgentMessages();
|
|
5116
5264
|
}
|
|
5117
|
-
}
|
|
5118
|
-
|
|
5119
|
-
}
|
|
5120
|
-
} catch {
|
|
5121
|
-
const newLine = `${line}
|
|
5265
|
+
} catch {
|
|
5266
|
+
const newLine = `${line}
|
|
5122
5267
|
`;
|
|
5123
|
-
|
|
5124
|
-
|
|
5268
|
+
rawOutput += newLine;
|
|
5269
|
+
options.onOutput?.(newLine);
|
|
5270
|
+
}
|
|
5125
5271
|
}
|
|
5126
|
-
}
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5272
|
+
});
|
|
5273
|
+
this.process.stderr?.on("data", (chunk) => {
|
|
5274
|
+
const text = chunk.toString();
|
|
5275
|
+
errorOutput += text;
|
|
5276
|
+
log.debug("sandboxed codex stderr", { text: text.slice(0, 500) });
|
|
5277
|
+
});
|
|
5278
|
+
this.process.on("close", (code) => {
|
|
5279
|
+
this.process = null;
|
|
5280
|
+
flushAgentMessages();
|
|
5281
|
+
if (this.aborted) {
|
|
5282
|
+
resolve2({
|
|
5283
|
+
success: false,
|
|
5284
|
+
output: rawOutput,
|
|
5285
|
+
error: "Aborted by user",
|
|
5286
|
+
exitCode: code ?? 143
|
|
5287
|
+
});
|
|
5288
|
+
return;
|
|
5289
|
+
}
|
|
5290
|
+
if (code === 0) {
|
|
5291
|
+
resolve2({ success: true, output: rawOutput, exitCode: 0 });
|
|
5292
|
+
} else {
|
|
5293
|
+
resolve2({
|
|
5294
|
+
success: false,
|
|
5295
|
+
output: rawOutput,
|
|
5296
|
+
error: errorOutput || `sandboxed codex exited with code ${code}`,
|
|
5297
|
+
exitCode: code ?? 1
|
|
5298
|
+
});
|
|
5299
|
+
}
|
|
5300
|
+
});
|
|
5301
|
+
this.process.on("error", (err) => {
|
|
5302
|
+
this.process = null;
|
|
5137
5303
|
resolve2({
|
|
5138
5304
|
success: false,
|
|
5139
5305
|
output: rawOutput,
|
|
5140
|
-
error:
|
|
5141
|
-
exitCode:
|
|
5306
|
+
error: `Failed to spawn docker sandbox: ${err.message}`,
|
|
5307
|
+
exitCode: 1
|
|
5142
5308
|
});
|
|
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
|
|
5309
|
+
});
|
|
5310
|
+
if (options.signal) {
|
|
5311
|
+
options.signal.addEventListener("abort", () => {
|
|
5312
|
+
this.abort();
|
|
5153
5313
|
});
|
|
5154
5314
|
}
|
|
5315
|
+
this.process.stdin?.write(options.prompt);
|
|
5316
|
+
this.process.stdin?.end();
|
|
5155
5317
|
});
|
|
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
|
-
});
|
|
5318
|
+
} finally {
|
|
5319
|
+
backup.restore();
|
|
5320
|
+
}
|
|
5173
5321
|
}
|
|
5174
5322
|
abort() {
|
|
5175
5323
|
this.aborted = true;
|
|
@@ -6887,7 +7035,7 @@ var init_sprint = __esm(() => {
|
|
|
6887
7035
|
|
|
6888
7036
|
// src/core/prompt-builder.ts
|
|
6889
7037
|
import { execSync as execSync7 } from "node:child_process";
|
|
6890
|
-
import { existsSync as existsSync14, readdirSync as
|
|
7038
|
+
import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "node:fs";
|
|
6891
7039
|
import { join as join13 } from "node:path";
|
|
6892
7040
|
function buildExecutionPrompt(ctx) {
|
|
6893
7041
|
const sections = [];
|
|
@@ -6965,7 +7113,7 @@ ${learnings}
|
|
|
6965
7113
|
const discussionsDir = join13(projectRoot, ".locus", "discussions");
|
|
6966
7114
|
if (existsSync14(discussionsDir)) {
|
|
6967
7115
|
try {
|
|
6968
|
-
const files =
|
|
7116
|
+
const files = readdirSync4(discussionsDir).filter((f) => f.endsWith(".md")).slice(0, 3);
|
|
6969
7117
|
for (const file of files) {
|
|
6970
7118
|
const content = readFileSafe(join13(discussionsDir, file));
|
|
6971
7119
|
if (content) {
|
|
@@ -7583,8 +7731,8 @@ var init_commands = __esm(() => {
|
|
|
7583
7731
|
});
|
|
7584
7732
|
|
|
7585
7733
|
// src/repl/completions.ts
|
|
7586
|
-
import { readdirSync as
|
|
7587
|
-
import { basename as basename2, dirname as
|
|
7734
|
+
import { readdirSync as readdirSync5 } from "node:fs";
|
|
7735
|
+
import { basename as basename2, dirname as dirname4, join as join14 } from "node:path";
|
|
7588
7736
|
|
|
7589
7737
|
class SlashCommandCompletion {
|
|
7590
7738
|
commands;
|
|
@@ -7639,9 +7787,9 @@ class FilePathCompletion {
|
|
|
7639
7787
|
}
|
|
7640
7788
|
findMatches(partial) {
|
|
7641
7789
|
try {
|
|
7642
|
-
const dir = partial.includes("/") ? join14(this.projectRoot,
|
|
7790
|
+
const dir = partial.includes("/") ? join14(this.projectRoot, dirname4(partial)) : this.projectRoot;
|
|
7643
7791
|
const prefix = basename2(partial);
|
|
7644
|
-
const entries =
|
|
7792
|
+
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
7645
7793
|
return entries.filter((e) => {
|
|
7646
7794
|
if (e.name.startsWith("."))
|
|
7647
7795
|
return false;
|
|
@@ -7650,7 +7798,7 @@ class FilePathCompletion {
|
|
|
7650
7798
|
return e.name.startsWith(prefix);
|
|
7651
7799
|
}).map((e) => {
|
|
7652
7800
|
const name = e.isDirectory() ? `${e.name}/` : e.name;
|
|
7653
|
-
return partial.includes("/") ? `${
|
|
7801
|
+
return partial.includes("/") ? `${dirname4(partial)}/${name}` : name;
|
|
7654
7802
|
}).slice(0, 20);
|
|
7655
7803
|
} catch {
|
|
7656
7804
|
return [];
|
|
@@ -7675,8 +7823,8 @@ class CombinedCompletion {
|
|
|
7675
7823
|
var init_completions = () => {};
|
|
7676
7824
|
|
|
7677
7825
|
// src/repl/input-history.ts
|
|
7678
|
-
import { existsSync as existsSync15, mkdirSync as
|
|
7679
|
-
import { dirname as
|
|
7826
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "node:fs";
|
|
7827
|
+
import { dirname as dirname5, join as join15 } from "node:path";
|
|
7680
7828
|
|
|
7681
7829
|
class InputHistory {
|
|
7682
7830
|
entries = [];
|
|
@@ -7730,9 +7878,9 @@ class InputHistory {
|
|
|
7730
7878
|
}
|
|
7731
7879
|
save() {
|
|
7732
7880
|
try {
|
|
7733
|
-
const dir =
|
|
7881
|
+
const dir = dirname5(this.filePath);
|
|
7734
7882
|
if (!existsSync15(dir)) {
|
|
7735
|
-
|
|
7883
|
+
mkdirSync10(dir, { recursive: true });
|
|
7736
7884
|
}
|
|
7737
7885
|
const content = this.entries.map((e) => this.escape(e)).join(`
|
|
7738
7886
|
`);
|
|
@@ -7763,8 +7911,8 @@ var init_model_config = __esm(() => {
|
|
|
7763
7911
|
// src/repl/session-manager.ts
|
|
7764
7912
|
import {
|
|
7765
7913
|
existsSync as existsSync16,
|
|
7766
|
-
mkdirSync as
|
|
7767
|
-
readdirSync as
|
|
7914
|
+
mkdirSync as mkdirSync11,
|
|
7915
|
+
readdirSync as readdirSync6,
|
|
7768
7916
|
readFileSync as readFileSync11,
|
|
7769
7917
|
unlinkSync as unlinkSync3,
|
|
7770
7918
|
writeFileSync as writeFileSync7
|
|
@@ -7776,7 +7924,7 @@ class SessionManager {
|
|
|
7776
7924
|
constructor(projectRoot) {
|
|
7777
7925
|
this.sessionsDir = join16(projectRoot, ".locus", "sessions");
|
|
7778
7926
|
if (!existsSync16(this.sessionsDir)) {
|
|
7779
|
-
|
|
7927
|
+
mkdirSync11(this.sessionsDir, { recursive: true });
|
|
7780
7928
|
}
|
|
7781
7929
|
}
|
|
7782
7930
|
create(options) {
|
|
@@ -7901,7 +8049,7 @@ class SessionManager {
|
|
|
7901
8049
|
}
|
|
7902
8050
|
listSessionFiles() {
|
|
7903
8051
|
try {
|
|
7904
|
-
return
|
|
8052
|
+
return readdirSync6(this.sessionsDir).filter((f) => f.endsWith(".json") && !f.startsWith(".")).map((f) => join16(this.sessionsDir, f));
|
|
7905
8053
|
} catch {
|
|
7906
8054
|
return [];
|
|
7907
8055
|
}
|
|
@@ -8945,12 +9093,12 @@ var init_conflict = __esm(() => {
|
|
|
8945
9093
|
// src/core/run-state.ts
|
|
8946
9094
|
import {
|
|
8947
9095
|
existsSync as existsSync18,
|
|
8948
|
-
mkdirSync as
|
|
9096
|
+
mkdirSync as mkdirSync12,
|
|
8949
9097
|
readFileSync as readFileSync12,
|
|
8950
9098
|
unlinkSync as unlinkSync4,
|
|
8951
9099
|
writeFileSync as writeFileSync8
|
|
8952
9100
|
} from "node:fs";
|
|
8953
|
-
import { dirname as
|
|
9101
|
+
import { dirname as dirname6, join as join18 } from "node:path";
|
|
8954
9102
|
function getRunStatePath(projectRoot) {
|
|
8955
9103
|
return join18(projectRoot, ".locus", "run-state.json");
|
|
8956
9104
|
}
|
|
@@ -8967,9 +9115,9 @@ function loadRunState(projectRoot) {
|
|
|
8967
9115
|
}
|
|
8968
9116
|
function saveRunState(projectRoot, state) {
|
|
8969
9117
|
const path = getRunStatePath(projectRoot);
|
|
8970
|
-
const dir =
|
|
9118
|
+
const dir = dirname6(path);
|
|
8971
9119
|
if (!existsSync18(dir)) {
|
|
8972
|
-
|
|
9120
|
+
mkdirSync12(dir, { recursive: true });
|
|
8973
9121
|
}
|
|
8974
9122
|
writeFileSync8(path, `${JSON.stringify(state, null, 2)}
|
|
8975
9123
|
`, "utf-8");
|
|
@@ -9117,7 +9265,7 @@ var init_shutdown = __esm(() => {
|
|
|
9117
9265
|
|
|
9118
9266
|
// src/core/worktree.ts
|
|
9119
9267
|
import { execSync as execSync13 } from "node:child_process";
|
|
9120
|
-
import { existsSync as existsSync19, readdirSync as
|
|
9268
|
+
import { existsSync as existsSync19, readdirSync as readdirSync7, realpathSync, statSync as statSync4 } from "node:fs";
|
|
9121
9269
|
import { join as join19 } from "node:path";
|
|
9122
9270
|
function git4(args, cwd) {
|
|
9123
9271
|
return execSync13(`git ${args}`, {
|
|
@@ -9207,7 +9355,7 @@ function listWorktrees(projectRoot) {
|
|
|
9207
9355
|
if (!existsSync19(worktreeDir)) {
|
|
9208
9356
|
return [];
|
|
9209
9357
|
}
|
|
9210
|
-
const entries =
|
|
9358
|
+
const entries = readdirSync7(worktreeDir).filter((entry) => entry.startsWith("issue-"));
|
|
9211
9359
|
const gitWorktreeList = gitSafe3("worktree list --porcelain", projectRoot);
|
|
9212
9360
|
const activeWorktrees = new Set;
|
|
9213
9361
|
if (gitWorktreeList) {
|
|
@@ -10062,8 +10210,8 @@ __export(exports_plan, {
|
|
|
10062
10210
|
});
|
|
10063
10211
|
import {
|
|
10064
10212
|
existsSync as existsSync20,
|
|
10065
|
-
mkdirSync as
|
|
10066
|
-
readdirSync as
|
|
10213
|
+
mkdirSync as mkdirSync13,
|
|
10214
|
+
readdirSync as readdirSync8,
|
|
10067
10215
|
readFileSync as readFileSync13,
|
|
10068
10216
|
writeFileSync as writeFileSync9
|
|
10069
10217
|
} from "node:fs";
|
|
@@ -10103,7 +10251,7 @@ function getPlansDir(projectRoot) {
|
|
|
10103
10251
|
function ensurePlansDir(projectRoot) {
|
|
10104
10252
|
const dir = getPlansDir(projectRoot);
|
|
10105
10253
|
if (!existsSync20(dir)) {
|
|
10106
|
-
|
|
10254
|
+
mkdirSync13(dir, { recursive: true });
|
|
10107
10255
|
}
|
|
10108
10256
|
return dir;
|
|
10109
10257
|
}
|
|
@@ -10114,7 +10262,7 @@ function loadPlanFile(projectRoot, id) {
|
|
|
10114
10262
|
const dir = getPlansDir(projectRoot);
|
|
10115
10263
|
if (!existsSync20(dir))
|
|
10116
10264
|
return null;
|
|
10117
|
-
const files =
|
|
10265
|
+
const files = readdirSync8(dir).filter((f) => f.endsWith(".json"));
|
|
10118
10266
|
const match = files.find((f) => f.startsWith(id));
|
|
10119
10267
|
if (!match)
|
|
10120
10268
|
return null;
|
|
@@ -10170,7 +10318,7 @@ function handleListPlans(projectRoot) {
|
|
|
10170
10318
|
`);
|
|
10171
10319
|
return;
|
|
10172
10320
|
}
|
|
10173
|
-
const files =
|
|
10321
|
+
const files = readdirSync8(dir).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
10174
10322
|
if (files.length === 0) {
|
|
10175
10323
|
process.stderr.write(`${dim("No saved plans yet.")}
|
|
10176
10324
|
`);
|
|
@@ -10914,7 +11062,8 @@ Pay special attention to the above areas.`;
|
|
|
10914
11062
|
}
|
|
10915
11063
|
instructions += `
|
|
10916
11064
|
|
|
10917
|
-
End with an overall assessment: APPROVE
|
|
11065
|
+
End with an overall assessment: APPROVE or COMMENT.
|
|
11066
|
+
If there are issues or suggestions, use COMMENT — never request changes.
|
|
10918
11067
|
Be constructive and specific. Praise good patterns too.`;
|
|
10919
11068
|
parts.push(`<review-instructions>
|
|
10920
11069
|
${instructions}
|
|
@@ -11197,8 +11346,8 @@ __export(exports_discuss, {
|
|
|
11197
11346
|
});
|
|
11198
11347
|
import {
|
|
11199
11348
|
existsSync as existsSync22,
|
|
11200
|
-
mkdirSync as
|
|
11201
|
-
readdirSync as
|
|
11349
|
+
mkdirSync as mkdirSync14,
|
|
11350
|
+
readdirSync as readdirSync9,
|
|
11202
11351
|
readFileSync as readFileSync15,
|
|
11203
11352
|
unlinkSync as unlinkSync5,
|
|
11204
11353
|
writeFileSync as writeFileSync10
|
|
@@ -11230,7 +11379,7 @@ function getDiscussionsDir(projectRoot) {
|
|
|
11230
11379
|
function ensureDiscussionsDir(projectRoot) {
|
|
11231
11380
|
const dir = getDiscussionsDir(projectRoot);
|
|
11232
11381
|
if (!existsSync22(dir)) {
|
|
11233
|
-
|
|
11382
|
+
mkdirSync14(dir, { recursive: true });
|
|
11234
11383
|
}
|
|
11235
11384
|
return dir;
|
|
11236
11385
|
}
|
|
@@ -11269,7 +11418,7 @@ function listDiscussions(projectRoot) {
|
|
|
11269
11418
|
`);
|
|
11270
11419
|
return;
|
|
11271
11420
|
}
|
|
11272
|
-
const files =
|
|
11421
|
+
const files = readdirSync9(dir).filter((f) => f.endsWith(".md")).sort().reverse();
|
|
11273
11422
|
if (files.length === 0) {
|
|
11274
11423
|
process.stderr.write(`${dim("No discussions yet.")}
|
|
11275
11424
|
`);
|
|
@@ -11304,7 +11453,7 @@ function showDiscussion(projectRoot, id) {
|
|
|
11304
11453
|
`);
|
|
11305
11454
|
return;
|
|
11306
11455
|
}
|
|
11307
|
-
const files =
|
|
11456
|
+
const files = readdirSync9(dir).filter((f) => f.endsWith(".md"));
|
|
11308
11457
|
const match = files.find((f) => f.startsWith(id));
|
|
11309
11458
|
if (!match) {
|
|
11310
11459
|
process.stderr.write(`${red("✗")} Discussion "${id}" not found.
|
|
@@ -11327,7 +11476,7 @@ function deleteDiscussion(projectRoot, id) {
|
|
|
11327
11476
|
`);
|
|
11328
11477
|
return;
|
|
11329
11478
|
}
|
|
11330
|
-
const files =
|
|
11479
|
+
const files = readdirSync9(dir).filter((f) => f.endsWith(".md"));
|
|
11331
11480
|
const match = files.find((f) => f.startsWith(id));
|
|
11332
11481
|
if (!match) {
|
|
11333
11482
|
process.stderr.write(`${red("✗")} Discussion "${id}" not found.
|
|
@@ -11352,7 +11501,7 @@ async function convertDiscussionToPlan(projectRoot, id) {
|
|
|
11352
11501
|
`);
|
|
11353
11502
|
return;
|
|
11354
11503
|
}
|
|
11355
|
-
const files =
|
|
11504
|
+
const files = readdirSync9(dir).filter((f) => f.endsWith(".md"));
|
|
11356
11505
|
const match = files.find((f) => f.startsWith(id));
|
|
11357
11506
|
if (!match) {
|
|
11358
11507
|
process.stderr.write(`${red("✗")} Discussion "${id}" not found.
|
|
@@ -11576,7 +11725,7 @@ __export(exports_artifacts, {
|
|
|
11576
11725
|
formatDate: () => formatDate2,
|
|
11577
11726
|
artifactsCommand: () => artifactsCommand
|
|
11578
11727
|
});
|
|
11579
|
-
import { existsSync as existsSync23, readdirSync as
|
|
11728
|
+
import { existsSync as existsSync23, readdirSync as readdirSync10, readFileSync as readFileSync16, statSync as statSync5 } from "node:fs";
|
|
11580
11729
|
import { join as join23 } from "node:path";
|
|
11581
11730
|
function printHelp5() {
|
|
11582
11731
|
process.stderr.write(`
|
|
@@ -11603,9 +11752,9 @@ function listArtifacts(projectRoot) {
|
|
|
11603
11752
|
const dir = getArtifactsDir(projectRoot);
|
|
11604
11753
|
if (!existsSync23(dir))
|
|
11605
11754
|
return [];
|
|
11606
|
-
return
|
|
11755
|
+
return readdirSync10(dir).filter((f) => f.endsWith(".md")).map((fileName) => {
|
|
11607
11756
|
const filePath = join23(dir, fileName);
|
|
11608
|
-
const stat =
|
|
11757
|
+
const stat = statSync5(filePath);
|
|
11609
11758
|
return {
|
|
11610
11759
|
name: fileName.replace(/\.md$/, ""),
|
|
11611
11760
|
fileName,
|
|
@@ -11620,7 +11769,7 @@ function readArtifact(projectRoot, name) {
|
|
|
11620
11769
|
const filePath = join23(dir, fileName);
|
|
11621
11770
|
if (!existsSync23(filePath))
|
|
11622
11771
|
return null;
|
|
11623
|
-
const stat =
|
|
11772
|
+
const stat = statSync5(filePath);
|
|
11624
11773
|
return {
|
|
11625
11774
|
content: readFileSync16(filePath, "utf-8"),
|
|
11626
11775
|
info: {
|
|
@@ -11960,7 +12109,12 @@ async function handleAgentLogin(projectRoot, agent) {
|
|
|
11960
12109
|
});
|
|
11961
12110
|
await new Promise((resolve2) => {
|
|
11962
12111
|
child.on("close", async (code) => {
|
|
11963
|
-
|
|
12112
|
+
const backup = backupIgnoredFiles(projectRoot);
|
|
12113
|
+
try {
|
|
12114
|
+
await enforceSandboxIgnore(sandboxName, projectRoot);
|
|
12115
|
+
} finally {
|
|
12116
|
+
backup.restore();
|
|
12117
|
+
}
|
|
11964
12118
|
if (code === 0) {
|
|
11965
12119
|
process.stderr.write(`
|
|
11966
12120
|
${green("✓")} ${agent} session ended. Auth should now be persisted in the sandbox.
|
|
@@ -12407,7 +12561,12 @@ async function createProviderSandbox(provider, sandboxName, projectRoot) {
|
|
|
12407
12561
|
if (provider === "codex") {
|
|
12408
12562
|
await ensureCodexInSandbox(sandboxName);
|
|
12409
12563
|
}
|
|
12410
|
-
|
|
12564
|
+
const backup = backupIgnoredFiles(projectRoot);
|
|
12565
|
+
try {
|
|
12566
|
+
await enforceSandboxIgnore(sandboxName, projectRoot);
|
|
12567
|
+
} finally {
|
|
12568
|
+
backup.restore();
|
|
12569
|
+
}
|
|
12411
12570
|
return true;
|
|
12412
12571
|
}
|
|
12413
12572
|
async function ensurePackageManagerInSandbox(sandboxName, pm) {
|