@ganglion/xacpx 0.17.0-beta.1 → 0.17.0-beta.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/dist/cli.js +518 -51
- package/dist/config/types.d.ts +7 -0
- package/dist/control/control-service.d.ts +29 -2
- package/dist/control/workspace-fs.d.ts +85 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -25585,6 +25585,9 @@ function terminalIdleTimeoutSeconds(config4) {
|
|
|
25585
25585
|
const v = config4.terminal?.idleTimeoutSeconds;
|
|
25586
25586
|
return typeof v === "number" && v > 0 ? v : 900;
|
|
25587
25587
|
}
|
|
25588
|
+
function filesWriteEnabled(config4) {
|
|
25589
|
+
return config4.files?.writeEnabled === true;
|
|
25590
|
+
}
|
|
25588
25591
|
|
|
25589
25592
|
// src/orchestration/orchestration-server.ts
|
|
25590
25593
|
import { chmod as chmod4, rm as rm8 } from "node:fs/promises";
|
|
@@ -33681,11 +33684,11 @@ async function readNativeSessionHistory(opts) {
|
|
|
33681
33684
|
var init_native_session_history = () => {};
|
|
33682
33685
|
|
|
33683
33686
|
// src/control/workspace-fs.ts
|
|
33684
|
-
import { execFile } from "node:child_process";
|
|
33687
|
+
import { execFile, spawn as spawn11 } from "node:child_process";
|
|
33685
33688
|
import { promisify } from "node:util";
|
|
33686
33689
|
import { homedir as homedir12 } from "node:os";
|
|
33687
|
-
import { readdir as readdir4, realpath, stat as stat3, open as open5 } from "node:fs/promises";
|
|
33688
|
-
import { isAbsolute as isAbsolute3, relative, resolve as resolve3, sep } from "node:path";
|
|
33690
|
+
import { readdir as readdir4, realpath, stat as stat3, open as open5, readFile as readFile15, writeFile as writeFile8, mkdir as mkdir9, rename as fsRename, cp, rm as rm10 } from "node:fs/promises";
|
|
33691
|
+
import { basename as basename3, dirname as dirname12, isAbsolute as isAbsolute3, relative, resolve as resolve3, sep } from "node:path";
|
|
33689
33692
|
function expandHome2(p) {
|
|
33690
33693
|
if (p === "~")
|
|
33691
33694
|
return homedir12();
|
|
@@ -33723,8 +33726,93 @@ class WorkspaceFs {
|
|
|
33723
33726
|
const rel = abs === root ? "" : relative(root, abs).split(sep).join("/");
|
|
33724
33727
|
return { root, abs, rel };
|
|
33725
33728
|
}
|
|
33726
|
-
async
|
|
33729
|
+
async resolveParent(workspace3, relPath) {
|
|
33730
|
+
if (relPath && isAbsolute3(relPath))
|
|
33731
|
+
throw new Error("path-must-be-relative");
|
|
33732
|
+
const trimmed = (relPath ?? "").replace(/\/+$/, "");
|
|
33733
|
+
if (!trimmed)
|
|
33734
|
+
throw new Error("bad-target");
|
|
33735
|
+
const name = basename3(trimmed);
|
|
33736
|
+
if (!name || name === "." || name === ".." || name.includes("/") || name.includes("\\")) {
|
|
33737
|
+
throw new Error("bad-target");
|
|
33738
|
+
}
|
|
33739
|
+
const parentRel = dirname12(trimmed);
|
|
33740
|
+
const { root, abs: parentAbs, rel: parentRelNorm } = await this.resolve(workspace3, parentRel === "." ? undefined : parentRel);
|
|
33741
|
+
const targetAbs = resolve3(parentAbs, name);
|
|
33742
|
+
const rel = parentRelNorm ? `${parentRelNorm}/${name}` : name;
|
|
33743
|
+
return { root, parentAbs, name, targetAbs, rel };
|
|
33744
|
+
}
|
|
33745
|
+
async createFile(workspace3, relPath) {
|
|
33746
|
+
const { targetAbs, rel } = await this.resolveParent(workspace3, relPath);
|
|
33747
|
+
try {
|
|
33748
|
+
await writeFile8(targetAbs, "", { flag: "wx" });
|
|
33749
|
+
} catch (e) {
|
|
33750
|
+
if (e.code === "EEXIST")
|
|
33751
|
+
throw new Error("already-exists");
|
|
33752
|
+
throw e;
|
|
33753
|
+
}
|
|
33754
|
+
return { path: rel };
|
|
33755
|
+
}
|
|
33756
|
+
async createDir(workspace3, relPath) {
|
|
33757
|
+
const { targetAbs, rel } = await this.resolveParent(workspace3, relPath);
|
|
33758
|
+
try {
|
|
33759
|
+
await mkdir9(targetAbs);
|
|
33760
|
+
} catch (e) {
|
|
33761
|
+
if (e.code === "EEXIST")
|
|
33762
|
+
throw new Error("already-exists");
|
|
33763
|
+
throw e;
|
|
33764
|
+
}
|
|
33765
|
+
return { path: rel };
|
|
33766
|
+
}
|
|
33767
|
+
async rename(workspace3, relPath, newName) {
|
|
33768
|
+
const src = await this.resolve(workspace3, relPath);
|
|
33769
|
+
if (!src.rel)
|
|
33770
|
+
throw new Error("bad-target");
|
|
33771
|
+
if (newName.includes("/") || newName.includes("\\"))
|
|
33772
|
+
throw new Error("bad-target");
|
|
33773
|
+
const destRelInput = src.rel.includes("/") ? `${src.rel.slice(0, src.rel.lastIndexOf("/"))}/${newName}` : newName;
|
|
33774
|
+
const dest = await this.resolveParent(workspace3, destRelInput);
|
|
33775
|
+
try {
|
|
33776
|
+
await stat3(dest.targetAbs);
|
|
33777
|
+
throw new Error("already-exists");
|
|
33778
|
+
} catch (e) {
|
|
33779
|
+
if (e.message === "already-exists")
|
|
33780
|
+
throw e;
|
|
33781
|
+
}
|
|
33782
|
+
await fsRename(src.abs, dest.targetAbs);
|
|
33783
|
+
return { path: dest.rel };
|
|
33784
|
+
}
|
|
33785
|
+
async duplicate(workspace3, relPath) {
|
|
33786
|
+
const src = await this.resolve(workspace3, relPath);
|
|
33787
|
+
if (!src.rel)
|
|
33788
|
+
throw new Error("bad-target");
|
|
33789
|
+
const parent = await this.resolveParent(workspace3, src.rel);
|
|
33790
|
+
const siblings = new Set(await readdir4(parent.parentAbs));
|
|
33791
|
+
const newBase = copyName(siblings, parent.name);
|
|
33792
|
+
const destAbs = resolve3(parent.parentAbs, newBase);
|
|
33793
|
+
await cp(src.abs, destAbs, { recursive: true });
|
|
33794
|
+
const destRel = parent.rel.includes("/") ? `${parent.rel.slice(0, parent.rel.lastIndexOf("/"))}/${newBase}` : newBase;
|
|
33795
|
+
return { path: destRel };
|
|
33796
|
+
}
|
|
33797
|
+
async remove(workspace3, relPath) {
|
|
33798
|
+
const { abs, rel } = await this.resolve(workspace3, relPath);
|
|
33799
|
+
if (!rel)
|
|
33800
|
+
throw new Error("refuse-delete-root");
|
|
33801
|
+
await rm10(abs, { recursive: true, force: false });
|
|
33802
|
+
return { path: rel };
|
|
33803
|
+
}
|
|
33804
|
+
async readFileBytes(workspace3, relPath) {
|
|
33727
33805
|
const { abs, rel } = await this.resolve(workspace3, relPath);
|
|
33806
|
+
const info = await stat3(abs);
|
|
33807
|
+
if (!info.isFile())
|
|
33808
|
+
throw new Error("not-a-file");
|
|
33809
|
+
if (info.size > DOWNLOAD_MAX)
|
|
33810
|
+
throw new Error("file-too-large");
|
|
33811
|
+
const buf = await readFile15(abs);
|
|
33812
|
+
return { path: rel, base64: buf.toString("base64"), size: info.size, mimeType: mimeForName(basename3(abs)) };
|
|
33813
|
+
}
|
|
33814
|
+
async listDirectory(workspace3, relPath) {
|
|
33815
|
+
const { root, abs, rel } = await this.resolve(workspace3, relPath);
|
|
33728
33816
|
const dirents = await readdir4(abs, { withFileTypes: true });
|
|
33729
33817
|
const entries = [];
|
|
33730
33818
|
for (const d of dirents.slice(0, MAX_ENTRIES)) {
|
|
@@ -33741,7 +33829,49 @@ class WorkspaceFs {
|
|
|
33741
33829
|
}
|
|
33742
33830
|
}
|
|
33743
33831
|
entries.sort((a, b) => a.type !== b.type ? a.type === "dir" ? -1 : 1 : a.name.localeCompare(b.name));
|
|
33744
|
-
|
|
33832
|
+
const relOf = (name, isDir) => (rel ? `${rel}/${name}` : name) + (isDir ? "/" : "");
|
|
33833
|
+
const ignoredSet = await this.gitCheckIgnore(root, entries.map((e) => relOf(e.name, e.type === "dir")));
|
|
33834
|
+
for (const e of entries) {
|
|
33835
|
+
if (ignoredSet.has(relOf(e.name, e.type === "dir")))
|
|
33836
|
+
e.ignored = true;
|
|
33837
|
+
}
|
|
33838
|
+
return { workspace: workspace3, path: rel, entries, root, sep };
|
|
33839
|
+
}
|
|
33840
|
+
gitCheckIgnore(root, relPaths) {
|
|
33841
|
+
return new Promise((resolvePromise) => {
|
|
33842
|
+
if (!relPaths.length)
|
|
33843
|
+
return resolvePromise(new Set);
|
|
33844
|
+
const ignored = new Set;
|
|
33845
|
+
let out = "";
|
|
33846
|
+
let child;
|
|
33847
|
+
try {
|
|
33848
|
+
child = spawn11("git", ["-C", root, "check-ignore", "-z", "--stdin"], { stdio: ["pipe", "pipe", "ignore"] });
|
|
33849
|
+
} catch {
|
|
33850
|
+
return resolvePromise(ignored);
|
|
33851
|
+
}
|
|
33852
|
+
const killer = setTimeout(() => {
|
|
33853
|
+
try {
|
|
33854
|
+
child.kill("SIGKILL");
|
|
33855
|
+
} catch {}
|
|
33856
|
+
}, GIT_TIMEOUT_MS);
|
|
33857
|
+
child.on("error", () => {
|
|
33858
|
+
clearTimeout(killer);
|
|
33859
|
+
resolvePromise(ignored);
|
|
33860
|
+
});
|
|
33861
|
+
child.stdout.on("data", (b) => {
|
|
33862
|
+
out += b.toString();
|
|
33863
|
+
});
|
|
33864
|
+
child.on("close", () => {
|
|
33865
|
+
clearTimeout(killer);
|
|
33866
|
+
for (const p of out.split("\x00")) {
|
|
33867
|
+
if (p)
|
|
33868
|
+
ignored.add(p);
|
|
33869
|
+
}
|
|
33870
|
+
resolvePromise(ignored);
|
|
33871
|
+
});
|
|
33872
|
+
child.stdin.on("error", () => {});
|
|
33873
|
+
child.stdin.end(relPaths.join("\x00") + "\x00");
|
|
33874
|
+
});
|
|
33745
33875
|
}
|
|
33746
33876
|
async readFile(workspace3, relPath) {
|
|
33747
33877
|
const { abs, rel } = await this.resolve(workspace3, relPath);
|
|
@@ -33759,6 +33889,7 @@ class WorkspaceFs {
|
|
|
33759
33889
|
path: rel,
|
|
33760
33890
|
content: binary ? "" : slice.toString("utf8"),
|
|
33761
33891
|
size: info.size,
|
|
33892
|
+
mtimeMs: info.mtimeMs,
|
|
33762
33893
|
truncated: info.size > FILE_READ_CAP,
|
|
33763
33894
|
binary
|
|
33764
33895
|
};
|
|
@@ -33766,16 +33897,45 @@ class WorkspaceFs {
|
|
|
33766
33897
|
await fh.close();
|
|
33767
33898
|
}
|
|
33768
33899
|
}
|
|
33769
|
-
async
|
|
33770
|
-
const {
|
|
33771
|
-
const
|
|
33900
|
+
async writeFile(workspace3, relPath, content, expected) {
|
|
33901
|
+
const { abs, rel } = await this.resolve(workspace3, relPath);
|
|
33902
|
+
const info = await stat3(abs);
|
|
33903
|
+
if (!info.isFile())
|
|
33904
|
+
throw new Error("not-a-file");
|
|
33905
|
+
if (info.size > FILE_READ_CAP)
|
|
33906
|
+
throw new Error("file-too-large");
|
|
33907
|
+
if (content.includes("\x00"))
|
|
33908
|
+
throw new Error("is-binary");
|
|
33909
|
+
if (Buffer.byteLength(content, "utf8") > FILE_READ_CAP)
|
|
33910
|
+
throw new Error("file-too-large");
|
|
33911
|
+
if (info.mtimeMs !== expected.mtimeMs || info.size !== expected.size)
|
|
33912
|
+
throw new Error("stale-write");
|
|
33913
|
+
await import_write_file_atomic3.default(abs, content);
|
|
33914
|
+
const after = await stat3(abs);
|
|
33915
|
+
return { path: rel, mtimeMs: after.mtimeMs, size: after.size };
|
|
33916
|
+
}
|
|
33917
|
+
async search(workspace3, opts) {
|
|
33918
|
+
const { root, abs: base, rel: scopeRel } = await this.resolve(workspace3, opts.path);
|
|
33919
|
+
const query = opts.query.trim();
|
|
33920
|
+
const empty = { workspace: workspace3, query, matches: [], hits: [], truncated: false };
|
|
33921
|
+
if (!query)
|
|
33922
|
+
return empty;
|
|
33923
|
+
if ((opts.mode ?? "name") === "content") {
|
|
33924
|
+
return this.contentGrep(workspace3, root, base, scopeRel, opts, query);
|
|
33925
|
+
}
|
|
33926
|
+
const matcher = buildMatcher(query, opts);
|
|
33927
|
+
const inc = opts.include ? globToRegExp(opts.include) : null;
|
|
33928
|
+
const exc = opts.exclude ? globToRegExp(opts.exclude) : null;
|
|
33772
33929
|
const matches = [];
|
|
33773
|
-
if (!needle)
|
|
33774
|
-
return { workspace: workspace3, query, matches, truncated: false };
|
|
33775
33930
|
let scanned = 0;
|
|
33776
33931
|
let truncated = false;
|
|
33777
|
-
const
|
|
33932
|
+
const deadline = Date.now() + SEARCH_DEADLINE_MS;
|
|
33933
|
+
const queue = [base];
|
|
33778
33934
|
while (queue.length) {
|
|
33935
|
+
if (Date.now() > deadline) {
|
|
33936
|
+
truncated = true;
|
|
33937
|
+
break;
|
|
33938
|
+
}
|
|
33779
33939
|
const dir = queue.shift();
|
|
33780
33940
|
let dirents;
|
|
33781
33941
|
try {
|
|
@@ -33784,6 +33944,10 @@ class WorkspaceFs {
|
|
|
33784
33944
|
continue;
|
|
33785
33945
|
}
|
|
33786
33946
|
for (const d of dirents) {
|
|
33947
|
+
if (Date.now() > deadline) {
|
|
33948
|
+
truncated = true;
|
|
33949
|
+
break;
|
|
33950
|
+
}
|
|
33787
33951
|
if (++scanned > SEARCH_MAX_SCAN) {
|
|
33788
33952
|
truncated = true;
|
|
33789
33953
|
break;
|
|
@@ -33795,7 +33959,11 @@ class WorkspaceFs {
|
|
|
33795
33959
|
queue.push(resolve3(dir, d.name));
|
|
33796
33960
|
} else if (d.isFile()) {
|
|
33797
33961
|
const rel = relative(root, resolve3(dir, d.name)).split(sep).join("/");
|
|
33798
|
-
if (
|
|
33962
|
+
if (inc && !inc.test(rel))
|
|
33963
|
+
continue;
|
|
33964
|
+
if (exc && exc.test(rel))
|
|
33965
|
+
continue;
|
|
33966
|
+
if (matcher(rel)) {
|
|
33799
33967
|
matches.push(rel);
|
|
33800
33968
|
if (matches.length >= SEARCH_MAX_RESULTS) {
|
|
33801
33969
|
truncated = true;
|
|
@@ -33808,18 +33976,199 @@ class WorkspaceFs {
|
|
|
33808
33976
|
break;
|
|
33809
33977
|
}
|
|
33810
33978
|
matches.sort();
|
|
33811
|
-
return { workspace: workspace3, query, matches, truncated };
|
|
33979
|
+
return { workspace: workspace3, query, matches, hits: [], truncated };
|
|
33980
|
+
}
|
|
33981
|
+
async contentGrep(workspace3, root, base, scopeRel, opts, query) {
|
|
33982
|
+
const inc = opts.include ? globToRegExp(opts.include) : null;
|
|
33983
|
+
const exc = opts.exclude ? globToRegExp(opts.exclude) : null;
|
|
33984
|
+
const inGit = await execFileAsync("git", ["-C", root, "rev-parse", "--is-inside-work-tree"], { maxBuffer: GIT_MAX_BUFFER, timeout: GIT_TIMEOUT_MS, killSignal: "SIGKILL" }).then(() => true).catch(() => false);
|
|
33985
|
+
if (inGit) {
|
|
33986
|
+
const args = ["-C", root, "grep", "-n", "--column", "-I", "--no-color", "--untracked"];
|
|
33987
|
+
if (!opts.matchCase)
|
|
33988
|
+
args.push("-i");
|
|
33989
|
+
if (opts.wholeWord)
|
|
33990
|
+
args.push("-w");
|
|
33991
|
+
args.push(opts.regex ? "-E" : "-F");
|
|
33992
|
+
args.push("-e", query, "--", scopeRel || ".");
|
|
33993
|
+
let stdout2 = "";
|
|
33994
|
+
let killed = false;
|
|
33995
|
+
try {
|
|
33996
|
+
stdout2 = (await execFileAsync("git", args, { maxBuffer: GIT_MAX_BUFFER, timeout: GIT_TIMEOUT_MS, killSignal: "SIGKILL" })).stdout;
|
|
33997
|
+
} catch (e) {
|
|
33998
|
+
killed = e.killed === true || e.signal === "SIGKILL";
|
|
33999
|
+
const code = e.code;
|
|
34000
|
+
if (code === 1)
|
|
34001
|
+
stdout2 = "";
|
|
34002
|
+
else {
|
|
34003
|
+
const out = e.stdout;
|
|
34004
|
+
stdout2 = typeof out === "string" ? out : "";
|
|
34005
|
+
}
|
|
34006
|
+
}
|
|
34007
|
+
const hits2 = [];
|
|
34008
|
+
let truncated2 = killed;
|
|
34009
|
+
for (const raw of stdout2.split(`
|
|
34010
|
+
`)) {
|
|
34011
|
+
if (!raw)
|
|
34012
|
+
continue;
|
|
34013
|
+
const m = raw.match(/^(.*?):(\d+):(\d+):(.*)$/);
|
|
34014
|
+
if (!m)
|
|
34015
|
+
continue;
|
|
34016
|
+
const p = m[1].split(sep).join("/");
|
|
34017
|
+
if (inc && !inc.test(p))
|
|
34018
|
+
continue;
|
|
34019
|
+
if (exc && exc.test(p))
|
|
34020
|
+
continue;
|
|
34021
|
+
hits2.push({ path: p, line: Number(m[2]), text: m[4].slice(0, 400) });
|
|
34022
|
+
if (hits2.length >= SEARCH_CONTENT_MAX_HITS) {
|
|
34023
|
+
truncated2 = true;
|
|
34024
|
+
break;
|
|
34025
|
+
}
|
|
34026
|
+
}
|
|
34027
|
+
return { workspace: workspace3, query, matches: [], hits: hits2, truncated: truncated2 };
|
|
34028
|
+
}
|
|
34029
|
+
try {
|
|
34030
|
+
const args = ["grep", "--no-index", "-n", "--column", "-I", "--no-color"];
|
|
34031
|
+
if (!opts.matchCase)
|
|
34032
|
+
args.push("-i");
|
|
34033
|
+
if (opts.wholeWord)
|
|
34034
|
+
args.push("-w");
|
|
34035
|
+
args.push(opts.regex ? "-E" : "-F");
|
|
34036
|
+
args.push("-e", query, "--", ".", ":(exclude)node_modules/", ":(exclude).git/");
|
|
34037
|
+
let stdout2 = "";
|
|
34038
|
+
let killed = false;
|
|
34039
|
+
try {
|
|
34040
|
+
stdout2 = (await execFileAsync("git", args, { cwd: base, maxBuffer: GIT_MAX_BUFFER, timeout: GIT_TIMEOUT_MS, killSignal: "SIGKILL" })).stdout;
|
|
34041
|
+
} catch (e) {
|
|
34042
|
+
if (e.code === "ENOENT")
|
|
34043
|
+
throw e;
|
|
34044
|
+
killed = e.killed === true || e.signal === "SIGKILL";
|
|
34045
|
+
const code = e.code;
|
|
34046
|
+
if (code === 1)
|
|
34047
|
+
stdout2 = "";
|
|
34048
|
+
else {
|
|
34049
|
+
const out = e.stdout;
|
|
34050
|
+
stdout2 = typeof out === "string" ? out : "";
|
|
34051
|
+
}
|
|
34052
|
+
}
|
|
34053
|
+
const hits2 = [];
|
|
34054
|
+
let truncated2 = killed;
|
|
34055
|
+
for (const raw of stdout2.split(`
|
|
34056
|
+
`)) {
|
|
34057
|
+
if (!raw)
|
|
34058
|
+
continue;
|
|
34059
|
+
const m = raw.match(/^(.*?):(\d+):(\d+):(.*)$/);
|
|
34060
|
+
if (!m)
|
|
34061
|
+
continue;
|
|
34062
|
+
const relToBase = m[1].split(sep).join("/");
|
|
34063
|
+
const p = scopeRel ? `${scopeRel}/${relToBase}` : relToBase;
|
|
34064
|
+
if (inc && !inc.test(p))
|
|
34065
|
+
continue;
|
|
34066
|
+
if (exc && exc.test(p))
|
|
34067
|
+
continue;
|
|
34068
|
+
hits2.push({ path: p, line: Number(m[2]), text: m[4].slice(0, 400) });
|
|
34069
|
+
if (hits2.length >= SEARCH_CONTENT_MAX_HITS) {
|
|
34070
|
+
truncated2 = true;
|
|
34071
|
+
break;
|
|
34072
|
+
}
|
|
34073
|
+
}
|
|
34074
|
+
return { workspace: workspace3, query, matches: [], hits: hits2, truncated: truncated2 };
|
|
34075
|
+
} catch (e) {
|
|
34076
|
+
if (e.code !== "ENOENT") {
|
|
34077
|
+
return { workspace: workspace3, query, matches: [], hits: [], truncated: false };
|
|
34078
|
+
}
|
|
34079
|
+
}
|
|
34080
|
+
const matcher = buildLineMatcher(query, opts);
|
|
34081
|
+
const hits = [];
|
|
34082
|
+
let scanned = 0;
|
|
34083
|
+
let truncated = false;
|
|
34084
|
+
const deadline = Date.now() + SEARCH_DEADLINE_MS;
|
|
34085
|
+
const queue = [base];
|
|
34086
|
+
while (queue.length) {
|
|
34087
|
+
if (Date.now() > deadline) {
|
|
34088
|
+
truncated = true;
|
|
34089
|
+
break;
|
|
34090
|
+
}
|
|
34091
|
+
const dir = queue.shift();
|
|
34092
|
+
let dirents;
|
|
34093
|
+
try {
|
|
34094
|
+
dirents = await readdir4(dir, { withFileTypes: true });
|
|
34095
|
+
} catch {
|
|
34096
|
+
continue;
|
|
34097
|
+
}
|
|
34098
|
+
for (const d of dirents) {
|
|
34099
|
+
if (Date.now() > deadline) {
|
|
34100
|
+
truncated = true;
|
|
34101
|
+
break;
|
|
34102
|
+
}
|
|
34103
|
+
if (++scanned > SEARCH_MAX_SCAN) {
|
|
34104
|
+
truncated = true;
|
|
34105
|
+
break;
|
|
34106
|
+
}
|
|
34107
|
+
if (d.isSymbolicLink())
|
|
34108
|
+
continue;
|
|
34109
|
+
const full = resolve3(dir, d.name);
|
|
34110
|
+
if (d.isDirectory()) {
|
|
34111
|
+
if (!SEARCH_SKIP_DIRS.has(d.name))
|
|
34112
|
+
queue.push(full);
|
|
34113
|
+
continue;
|
|
34114
|
+
}
|
|
34115
|
+
if (!d.isFile())
|
|
34116
|
+
continue;
|
|
34117
|
+
const rel = relative(root, full).split(sep).join("/");
|
|
34118
|
+
if (inc && !inc.test(rel))
|
|
34119
|
+
continue;
|
|
34120
|
+
if (exc && exc.test(rel))
|
|
34121
|
+
continue;
|
|
34122
|
+
let info;
|
|
34123
|
+
try {
|
|
34124
|
+
info = await stat3(full);
|
|
34125
|
+
} catch {
|
|
34126
|
+
continue;
|
|
34127
|
+
}
|
|
34128
|
+
if (info.size > SEARCH_CONTENT_MAX_FILE)
|
|
34129
|
+
continue;
|
|
34130
|
+
let text;
|
|
34131
|
+
try {
|
|
34132
|
+
text = await readFile15(full, "utf8");
|
|
34133
|
+
} catch {
|
|
34134
|
+
continue;
|
|
34135
|
+
}
|
|
34136
|
+
if (text.includes("\x00"))
|
|
34137
|
+
continue;
|
|
34138
|
+
const lines = text.split(`
|
|
34139
|
+
`);
|
|
34140
|
+
for (let i = 0;i < lines.length; i++) {
|
|
34141
|
+
if (Date.now() > deadline) {
|
|
34142
|
+
truncated = true;
|
|
34143
|
+
break;
|
|
34144
|
+
}
|
|
34145
|
+
const line = lines[i];
|
|
34146
|
+
if (matcher(line.length > LINE_MATCH_CAP ? line.slice(0, LINE_MATCH_CAP) : line)) {
|
|
34147
|
+
hits.push({ path: rel, line: i + 1, text: line.slice(0, 400) });
|
|
34148
|
+
if (hits.length >= SEARCH_CONTENT_MAX_HITS) {
|
|
34149
|
+
truncated = true;
|
|
34150
|
+
break;
|
|
34151
|
+
}
|
|
34152
|
+
}
|
|
34153
|
+
}
|
|
34154
|
+
if (truncated)
|
|
34155
|
+
break;
|
|
34156
|
+
}
|
|
34157
|
+
if (truncated)
|
|
34158
|
+
break;
|
|
34159
|
+
}
|
|
34160
|
+
return { workspace: workspace3, query, matches: [], hits, truncated };
|
|
33812
34161
|
}
|
|
33813
34162
|
async gitDiff(workspace3, relPath) {
|
|
33814
34163
|
const { root, rel } = await this.resolve(workspace3, relPath);
|
|
33815
34164
|
try {
|
|
33816
|
-
await execFileAsync("git", ["-C", root, "rev-parse", "--is-inside-work-tree"], { maxBuffer: GIT_MAX_BUFFER });
|
|
34165
|
+
await execFileAsync("git", ["-C", root, "rev-parse", "--is-inside-work-tree"], { maxBuffer: GIT_MAX_BUFFER, timeout: GIT_TIMEOUT_MS, killSignal: "SIGKILL" });
|
|
33817
34166
|
} catch {
|
|
33818
34167
|
throw new Error("not-a-git-repo");
|
|
33819
34168
|
}
|
|
33820
34169
|
const files = [];
|
|
33821
34170
|
try {
|
|
33822
|
-
const { stdout: stdout2 } = await execFileAsync("git", ["-C", root, "-c", "core.quotePath=false", "status", "--porcelain", "-z"], { maxBuffer: GIT_MAX_BUFFER });
|
|
34171
|
+
const { stdout: stdout2 } = await execFileAsync("git", ["-C", root, "-c", "core.quotePath=false", "status", "--porcelain", "-z"], { maxBuffer: GIT_MAX_BUFFER, timeout: GIT_TIMEOUT_MS, killSignal: "SIGKILL" });
|
|
33823
34172
|
const fields = stdout2.split("\x00");
|
|
33824
34173
|
for (let i = 0;i < fields.length; i++) {
|
|
33825
34174
|
const field = fields[i];
|
|
@@ -33835,17 +34184,17 @@ class WorkspaceFs {
|
|
|
33835
34184
|
const diffArgs = (base) => ["-C", root, ...base, ...rel ? ["--", rel] : []];
|
|
33836
34185
|
let diff = "";
|
|
33837
34186
|
try {
|
|
33838
|
-
diff = (await execFileAsync("git", diffArgs(["diff", "HEAD"]), { maxBuffer: GIT_MAX_BUFFER })).stdout;
|
|
34187
|
+
diff = (await execFileAsync("git", diffArgs(["diff", "HEAD"]), { maxBuffer: GIT_MAX_BUFFER, timeout: GIT_TIMEOUT_MS, killSignal: "SIGKILL" })).stdout;
|
|
33839
34188
|
} catch {
|
|
33840
34189
|
try {
|
|
33841
|
-
diff = (await execFileAsync("git", diffArgs(["diff"]), { maxBuffer: GIT_MAX_BUFFER })).stdout;
|
|
34190
|
+
diff = (await execFileAsync("git", diffArgs(["diff"]), { maxBuffer: GIT_MAX_BUFFER, timeout: GIT_TIMEOUT_MS, killSignal: "SIGKILL" })).stdout;
|
|
33842
34191
|
} catch {
|
|
33843
34192
|
diff = "";
|
|
33844
34193
|
}
|
|
33845
34194
|
}
|
|
33846
34195
|
if (rel && !diff) {
|
|
33847
34196
|
try {
|
|
33848
|
-
diff = (await execFileAsync("git", ["-C", root, "-c", "core.quotePath=false", "diff", "--no-index", "--", "/dev/null", rel], { maxBuffer: GIT_MAX_BUFFER })).stdout;
|
|
34197
|
+
diff = (await execFileAsync("git", ["-C", root, "-c", "core.quotePath=false", "diff", "--no-index", "--", "/dev/null", rel], { maxBuffer: GIT_MAX_BUFFER, timeout: GIT_TIMEOUT_MS, killSignal: "SIGKILL" })).stdout;
|
|
33849
34198
|
} catch (e) {
|
|
33850
34199
|
const out = e.stdout;
|
|
33851
34200
|
if (typeof out === "string")
|
|
@@ -33858,7 +34207,7 @@ class WorkspaceFs {
|
|
|
33858
34207
|
async gitContext(root) {
|
|
33859
34208
|
const run = async (...args) => {
|
|
33860
34209
|
try {
|
|
33861
|
-
return (await execFileAsync("git", ["-C", root, ...args], { maxBuffer: GIT_MAX_BUFFER })).stdout.trim();
|
|
34210
|
+
return (await execFileAsync("git", ["-C", root, ...args], { maxBuffer: GIT_MAX_BUFFER, timeout: GIT_TIMEOUT_MS, killSignal: "SIGKILL" })).stdout.trim();
|
|
33862
34211
|
} catch {
|
|
33863
34212
|
return null;
|
|
33864
34213
|
}
|
|
@@ -33878,13 +34227,102 @@ class WorkspaceFs {
|
|
|
33878
34227
|
return ctx;
|
|
33879
34228
|
}
|
|
33880
34229
|
}
|
|
33881
|
-
|
|
34230
|
+
function escapeRe(s) {
|
|
34231
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
34232
|
+
}
|
|
34233
|
+
function copyName(existing, name) {
|
|
34234
|
+
const dot = name.lastIndexOf(".");
|
|
34235
|
+
const hasExt = dot > 0;
|
|
34236
|
+
const base = hasExt ? name.slice(0, dot) : name;
|
|
34237
|
+
const ext = hasExt ? name.slice(dot) : "";
|
|
34238
|
+
let candidate = `${base} copy${ext}`;
|
|
34239
|
+
let n = 2;
|
|
34240
|
+
while (existing.has(candidate))
|
|
34241
|
+
candidate = `${base} copy ${n++}${ext}`;
|
|
34242
|
+
return candidate;
|
|
34243
|
+
}
|
|
34244
|
+
function mimeForName(name) {
|
|
34245
|
+
const dot = name.lastIndexOf(".");
|
|
34246
|
+
const ext = dot > 0 ? name.slice(dot).toLowerCase() : "";
|
|
34247
|
+
return MIME_BY_EXT[ext] ?? "application/octet-stream";
|
|
34248
|
+
}
|
|
34249
|
+
function buildMatcher(query, opts) {
|
|
34250
|
+
return buildLineMatcher(query, opts);
|
|
34251
|
+
}
|
|
34252
|
+
function buildLineMatcher(query, opts) {
|
|
34253
|
+
const flags = opts.matchCase ? "" : "i";
|
|
34254
|
+
let pattern;
|
|
34255
|
+
if (opts.regex)
|
|
34256
|
+
pattern = query;
|
|
34257
|
+
else
|
|
34258
|
+
pattern = escapeRe(query);
|
|
34259
|
+
if (opts.wholeWord)
|
|
34260
|
+
pattern = `\\b${pattern}\\b`;
|
|
34261
|
+
let re = null;
|
|
34262
|
+
try {
|
|
34263
|
+
re = new RegExp(pattern, flags);
|
|
34264
|
+
} catch {
|
|
34265
|
+
re = null;
|
|
34266
|
+
}
|
|
34267
|
+
if (!re) {
|
|
34268
|
+
const needle = opts.matchCase ? query : query.toLowerCase();
|
|
34269
|
+
return (s) => (opts.matchCase ? s : s.toLowerCase()).includes(needle);
|
|
34270
|
+
}
|
|
34271
|
+
return (s) => re.test(s);
|
|
34272
|
+
}
|
|
34273
|
+
function globToRegExp(glob) {
|
|
34274
|
+
let re = "^";
|
|
34275
|
+
for (let i = 0;i < glob.length; i++) {
|
|
34276
|
+
const c = glob[i];
|
|
34277
|
+
if (c === "*") {
|
|
34278
|
+
if (glob[i + 1] === "*") {
|
|
34279
|
+
re += ".*";
|
|
34280
|
+
i++;
|
|
34281
|
+
if (glob[i + 1] === "/")
|
|
34282
|
+
i++;
|
|
34283
|
+
} else
|
|
34284
|
+
re += "[^/]*";
|
|
34285
|
+
} else if (c === "?")
|
|
34286
|
+
re += "[^/]";
|
|
34287
|
+
else
|
|
34288
|
+
re += escapeRe(c);
|
|
34289
|
+
}
|
|
34290
|
+
re += "$";
|
|
34291
|
+
try {
|
|
34292
|
+
return new RegExp(re);
|
|
34293
|
+
} catch {
|
|
34294
|
+
return /$^/;
|
|
34295
|
+
}
|
|
34296
|
+
}
|
|
34297
|
+
var import_write_file_atomic3, execFileAsync, MAX_ENTRIES = 2000, FILE_READ_CAP, DIFF_CAP, GIT_MAX_BUFFER, SEARCH_MAX_RESULTS = 200, SEARCH_MAX_SCAN = 20000, SEARCH_SKIP_DIRS, SEARCH_CONTENT_MAX_HITS = 500, SEARCH_CONTENT_MAX_FILE, LINE_MATCH_CAP = 2000, DOWNLOAD_MAX, GIT_TIMEOUT_MS = 1e4, SEARCH_DEADLINE_MS = 4000, MIME_BY_EXT;
|
|
33882
34298
|
var init_workspace_fs = __esm(() => {
|
|
34299
|
+
import_write_file_atomic3 = __toESM(require_lib(), 1);
|
|
33883
34300
|
execFileAsync = promisify(execFile);
|
|
33884
34301
|
FILE_READ_CAP = 256 * 1024;
|
|
33885
34302
|
DIFF_CAP = 512 * 1024;
|
|
33886
34303
|
GIT_MAX_BUFFER = 32 * 1024 * 1024;
|
|
33887
34304
|
SEARCH_SKIP_DIRS = new Set([".git", "node_modules"]);
|
|
34305
|
+
SEARCH_CONTENT_MAX_FILE = 1024 * 1024;
|
|
34306
|
+
DOWNLOAD_MAX = 5 * 1024 * 1024;
|
|
34307
|
+
MIME_BY_EXT = {
|
|
34308
|
+
".txt": "text/plain",
|
|
34309
|
+
".md": "text/markdown",
|
|
34310
|
+
".json": "application/json",
|
|
34311
|
+
".js": "text/javascript",
|
|
34312
|
+
".ts": "text/plain",
|
|
34313
|
+
".html": "text/html",
|
|
34314
|
+
".css": "text/css",
|
|
34315
|
+
".csv": "text/csv",
|
|
34316
|
+
".xml": "application/xml",
|
|
34317
|
+
".png": "image/png",
|
|
34318
|
+
".jpg": "image/jpeg",
|
|
34319
|
+
".jpeg": "image/jpeg",
|
|
34320
|
+
".gif": "image/gif",
|
|
34321
|
+
".svg": "image/svg+xml",
|
|
34322
|
+
".webp": "image/webp",
|
|
34323
|
+
".pdf": "application/pdf",
|
|
34324
|
+
".zip": "application/zip"
|
|
34325
|
+
};
|
|
33888
34326
|
});
|
|
33889
34327
|
|
|
33890
34328
|
// src/control/control-service.ts
|
|
@@ -33906,8 +34344,36 @@ class ControlService {
|
|
|
33906
34344
|
workspaceGitDiff(workspace3, path16) {
|
|
33907
34345
|
return this.workspaceFs.gitDiff(workspace3, path16);
|
|
33908
34346
|
}
|
|
33909
|
-
searchWorkspace(workspace3,
|
|
33910
|
-
return this.workspaceFs.search(workspace3,
|
|
34347
|
+
searchWorkspace(workspace3, opts) {
|
|
34348
|
+
return this.workspaceFs.search(workspace3, opts);
|
|
34349
|
+
}
|
|
34350
|
+
async fsCreate(workspace3, path16, kind) {
|
|
34351
|
+
if (!this.deps.filesWriteEnabled())
|
|
34352
|
+
throw new Error("files-write-disabled");
|
|
34353
|
+
return kind === "dir" ? this.workspaceFs.createDir(workspace3, path16) : this.workspaceFs.createFile(workspace3, path16);
|
|
34354
|
+
}
|
|
34355
|
+
async fsRename(workspace3, path16, newName) {
|
|
34356
|
+
if (!this.deps.filesWriteEnabled())
|
|
34357
|
+
throw new Error("files-write-disabled");
|
|
34358
|
+
return this.workspaceFs.rename(workspace3, path16, newName);
|
|
34359
|
+
}
|
|
34360
|
+
async fsDelete(workspace3, path16) {
|
|
34361
|
+
if (!this.deps.filesWriteEnabled())
|
|
34362
|
+
throw new Error("files-write-disabled");
|
|
34363
|
+
return this.workspaceFs.remove(workspace3, path16);
|
|
34364
|
+
}
|
|
34365
|
+
async fsCopy(workspace3, path16) {
|
|
34366
|
+
if (!this.deps.filesWriteEnabled())
|
|
34367
|
+
throw new Error("files-write-disabled");
|
|
34368
|
+
return this.workspaceFs.duplicate(workspace3, path16);
|
|
34369
|
+
}
|
|
34370
|
+
async fsWrite(workspace3, path16, content, expected) {
|
|
34371
|
+
if (!this.deps.filesWriteEnabled())
|
|
34372
|
+
throw new Error("files-write-disabled");
|
|
34373
|
+
return this.workspaceFs.writeFile(workspace3, path16, content, expected);
|
|
34374
|
+
}
|
|
34375
|
+
async fsDownload(workspace3, path16) {
|
|
34376
|
+
return this.workspaceFs.readFileBytes(workspace3, path16);
|
|
33911
34377
|
}
|
|
33912
34378
|
async uploadFile(input) {
|
|
33913
34379
|
return this.deps.uploadStore.save(input.filename, input.content, input.mimeType);
|
|
@@ -34445,7 +34911,7 @@ function realPtySpawn(file, args, opts) {
|
|
|
34445
34911
|
return spawnPty2(file, args, opts);
|
|
34446
34912
|
}
|
|
34447
34913
|
function createTerminalService(deps) {
|
|
34448
|
-
const
|
|
34914
|
+
const spawn12 = deps.spawn ?? realPtySpawn;
|
|
34449
34915
|
const platform = deps.platform ?? process.platform;
|
|
34450
34916
|
const sessions = new Map;
|
|
34451
34917
|
const setTimer = deps.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
@@ -34471,7 +34937,7 @@ function createTerminalService(deps) {
|
|
|
34471
34937
|
if (platform === "win32")
|
|
34472
34938
|
throw new Error("terminal-unsupported-platform");
|
|
34473
34939
|
const terminalId = randomUUID4();
|
|
34474
|
-
const handle =
|
|
34940
|
+
const handle = spawn12(defaultShell(platform), [], { name: "xterm-256color", cols, rows, cwd, env: scrubEnv() });
|
|
34475
34941
|
const session3 = { handle, seq: 0, idleTimer: null };
|
|
34476
34942
|
sessions.set(terminalId, session3);
|
|
34477
34943
|
handle.onData((data) => {
|
|
@@ -34545,7 +35011,7 @@ var init_terminal_service = __esm(() => {
|
|
|
34545
35011
|
});
|
|
34546
35012
|
|
|
34547
35013
|
// src/control/upload-store.ts
|
|
34548
|
-
import { mkdtemp as mkdtemp2, readdir as readdir5, rm as
|
|
35014
|
+
import { mkdtemp as mkdtemp2, readdir as readdir5, rm as rm11, stat as stat4, writeFile as writeFile9 } from "node:fs/promises";
|
|
34549
35015
|
import { homedir as homedir13 } from "node:os";
|
|
34550
35016
|
import path16 from "node:path";
|
|
34551
35017
|
function defaultRootDir() {
|
|
@@ -34581,11 +35047,11 @@ class UploadStore {
|
|
|
34581
35047
|
if (bytes.byteLength > this.maxBytes)
|
|
34582
35048
|
throw new Error("file-too-large");
|
|
34583
35049
|
const safeName = sanitizeUploadFilename(filename);
|
|
34584
|
-
const { mkdir:
|
|
34585
|
-
await
|
|
35050
|
+
const { mkdir: mkdir10 } = await import("node:fs/promises");
|
|
35051
|
+
await mkdir10(this.rootDir, { recursive: true });
|
|
34586
35052
|
const dir = await mkdtemp2(path16.join(this.rootDir, "u-"));
|
|
34587
35053
|
const filePath = path16.join(dir, safeName);
|
|
34588
|
-
await
|
|
35054
|
+
await writeFile9(filePath, bytes);
|
|
34589
35055
|
return {
|
|
34590
35056
|
id: path16.basename(dir),
|
|
34591
35057
|
path: filePath,
|
|
@@ -34608,7 +35074,7 @@ class UploadStore {
|
|
|
34608
35074
|
try {
|
|
34609
35075
|
const info = await stat4(dir);
|
|
34610
35076
|
if (info.mtimeMs < cutoff) {
|
|
34611
|
-
await
|
|
35077
|
+
await rm11(dir, { recursive: true, force: true });
|
|
34612
35078
|
removed += 1;
|
|
34613
35079
|
}
|
|
34614
35080
|
} catch {}
|
|
@@ -34666,12 +35132,12 @@ var init_agent_catalog = __esm(() => {
|
|
|
34666
35132
|
|
|
34667
35133
|
// src/config/config-watcher.ts
|
|
34668
35134
|
import { watch } from "node:fs";
|
|
34669
|
-
import { basename as
|
|
35135
|
+
import { basename as basename4, dirname as dirname13 } from "node:path";
|
|
34670
35136
|
function startConfigWatcher(options) {
|
|
34671
35137
|
const { configPath, onChange, debounceMs = 250, logger: logger2 } = options;
|
|
34672
35138
|
const watchFactory = options.watchFactory ?? watch;
|
|
34673
|
-
const dir =
|
|
34674
|
-
const target =
|
|
35139
|
+
const dir = dirname13(configPath);
|
|
35140
|
+
const target = basename4(configPath);
|
|
34675
35141
|
let timer;
|
|
34676
35142
|
let watcher;
|
|
34677
35143
|
const fire = () => {
|
|
@@ -34686,7 +35152,7 @@ function startConfigWatcher(options) {
|
|
|
34686
35152
|
};
|
|
34687
35153
|
try {
|
|
34688
35154
|
watcher = watchFactory(dir, { persistent: false }, (_event, filename) => {
|
|
34689
|
-
if (filename !== null &&
|
|
35155
|
+
if (filename !== null && basename4(filename.toString()) !== target)
|
|
34690
35156
|
return;
|
|
34691
35157
|
if (timer)
|
|
34692
35158
|
clearTimeout(timer);
|
|
@@ -34726,7 +35192,7 @@ __export(exports_main, {
|
|
|
34726
35192
|
});
|
|
34727
35193
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
34728
35194
|
import { homedir as homedir14 } from "node:os";
|
|
34729
|
-
import { dirname as
|
|
35195
|
+
import { dirname as dirname14, join as join23 } from "node:path";
|
|
34730
35196
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
34731
35197
|
function startProgressHeartbeat(orchestration3, config4, logger2, channel) {
|
|
34732
35198
|
const thresholdSeconds = config4.orchestration.progressHeartbeatSeconds;
|
|
@@ -35271,7 +35737,8 @@ async function buildApp(paths, deps = {}) {
|
|
|
35271
35737
|
},
|
|
35272
35738
|
uploadStore,
|
|
35273
35739
|
terminal: terminalService,
|
|
35274
|
-
terminalEnabled: () => terminalEnabled(config4)
|
|
35740
|
+
terminalEnabled: () => terminalEnabled(config4),
|
|
35741
|
+
filesWriteEnabled: () => filesWriteEnabled(config4)
|
|
35275
35742
|
});
|
|
35276
35743
|
const workspaceSignature = (cfg) => JSON.stringify(Object.keys(cfg.workspaces).sort().map((name) => {
|
|
35277
35744
|
const ws = cfg.workspaces[name];
|
|
@@ -35424,7 +35891,7 @@ async function main() {
|
|
|
35424
35891
|
}
|
|
35425
35892
|
}
|
|
35426
35893
|
async function prepareChannelMedia(configPath, config4) {
|
|
35427
|
-
const runtimeDir = join23(
|
|
35894
|
+
const runtimeDir = join23(dirname14(configPath), "runtime");
|
|
35428
35895
|
const mediaRootDir = join23(runtimeDir, "media");
|
|
35429
35896
|
const mediaStore = new RuntimeMediaStore({ rootDir: mediaRootDir });
|
|
35430
35897
|
await mediaStore.cleanupExpired().catch((error2) => {
|
|
@@ -35439,7 +35906,7 @@ function resolveRuntimePaths() {
|
|
|
35439
35906
|
throw new Error("Unable to resolve the current user home directory");
|
|
35440
35907
|
}
|
|
35441
35908
|
const configPath = coreEnv("CONFIG") ?? join23(coreHomeDir(home), "config.json");
|
|
35442
|
-
const runtimeDir = join23(
|
|
35909
|
+
const runtimeDir = join23(dirname14(configPath), "runtime");
|
|
35443
35910
|
return {
|
|
35444
35911
|
configPath,
|
|
35445
35912
|
statePath: coreEnv("STATE") ?? join23(coreHomeDir(home), "state.json"),
|
|
@@ -35454,12 +35921,12 @@ function resolveBridgeEntryPath() {
|
|
|
35454
35921
|
return fileURLToPath5(new URL("./bridge/bridge-main.ts", import.meta.url));
|
|
35455
35922
|
}
|
|
35456
35923
|
function resolveAppLogPath(configPath) {
|
|
35457
|
-
const rootDir =
|
|
35924
|
+
const rootDir = dirname14(configPath);
|
|
35458
35925
|
const runtimeDir = join23(rootDir, "runtime");
|
|
35459
35926
|
return join23(runtimeDir, "app.log");
|
|
35460
35927
|
}
|
|
35461
35928
|
function resolvePerfLogPath(configPath) {
|
|
35462
|
-
const rootDir =
|
|
35929
|
+
const rootDir = dirname14(configPath);
|
|
35463
35930
|
const runtimeDir = join23(rootDir, "runtime");
|
|
35464
35931
|
return join23(runtimeDir, "perf.log");
|
|
35465
35932
|
}
|
|
@@ -35517,7 +35984,7 @@ var init_main = __esm(async () => {
|
|
|
35517
35984
|
});
|
|
35518
35985
|
|
|
35519
35986
|
// src/doctor/checks/acpx-check.ts
|
|
35520
|
-
import { spawn as
|
|
35987
|
+
import { spawn as spawn12 } from "node:child_process";
|
|
35521
35988
|
async function checkAcpx(options = {}) {
|
|
35522
35989
|
const runtimePaths = (options.resolveRuntimePaths ?? resolveRuntimePaths)();
|
|
35523
35990
|
try {
|
|
@@ -35564,7 +36031,7 @@ function buildDetails(metadata, version2, verbose) {
|
|
|
35564
36031
|
async function defaultRunVersion(command) {
|
|
35565
36032
|
const spawnSpec = resolveSpawnCommand(command, ["--version"]);
|
|
35566
36033
|
return await new Promise((resolve4, reject) => {
|
|
35567
|
-
const child =
|
|
36034
|
+
const child = spawn12(spawnSpec.command, spawnSpec.args, {
|
|
35568
36035
|
stdio: ["ignore", "pipe", "pipe"]
|
|
35569
36036
|
});
|
|
35570
36037
|
let stdout2 = "";
|
|
@@ -35702,7 +36169,7 @@ var init_config_check = __esm(async () => {
|
|
|
35702
36169
|
});
|
|
35703
36170
|
|
|
35704
36171
|
// src/doctor/checks/daemon-check.ts
|
|
35705
|
-
import { readdir as readdir6, readFile as
|
|
36172
|
+
import { readdir as readdir6, readFile as readFile16, rm as rm12 } from "node:fs/promises";
|
|
35706
36173
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
35707
36174
|
import { homedir as homedir15 } from "node:os";
|
|
35708
36175
|
import { join as join24 } from "node:path";
|
|
@@ -35848,7 +36315,7 @@ async function defaultListConsumerLocks(runtimeDir) {
|
|
|
35848
36315
|
}
|
|
35849
36316
|
async function defaultReadConsumerLock(path17) {
|
|
35850
36317
|
try {
|
|
35851
|
-
const raw = await
|
|
36318
|
+
const raw = await readFile16(path17, "utf8");
|
|
35852
36319
|
const parsed = JSON.parse(raw);
|
|
35853
36320
|
return typeof parsed.pid === "number" ? { pid: parsed.pid } : null;
|
|
35854
36321
|
} catch {
|
|
@@ -35856,7 +36323,7 @@ async function defaultReadConsumerLock(path17) {
|
|
|
35856
36323
|
}
|
|
35857
36324
|
}
|
|
35858
36325
|
async function defaultRemoveConsumerLock(path17) {
|
|
35859
|
-
await
|
|
36326
|
+
await rm12(path17, { force: true });
|
|
35860
36327
|
}
|
|
35861
36328
|
function resolveCliEntryPath() {
|
|
35862
36329
|
return process.argv[1] ?? fileURLToPath6(import.meta.url);
|
|
@@ -35872,7 +36339,7 @@ var init_daemon_check = __esm(() => {
|
|
|
35872
36339
|
|
|
35873
36340
|
// src/doctor/checks/logs-check.ts
|
|
35874
36341
|
import { stat as stat5, readdir as readdir7 } from "node:fs/promises";
|
|
35875
|
-
import { basename as
|
|
36342
|
+
import { basename as basename5, join as join25 } from "node:path";
|
|
35876
36343
|
import { homedir as homedir16 } from "node:os";
|
|
35877
36344
|
async function checkLogs(options = {}) {
|
|
35878
36345
|
const home = options.home ?? process.env.HOME ?? homedir16();
|
|
@@ -35902,7 +36369,7 @@ async function checkLogs(options = {}) {
|
|
|
35902
36369
|
`error: ${formatError6(error2)}`
|
|
35903
36370
|
]);
|
|
35904
36371
|
}
|
|
35905
|
-
const baseNames = [
|
|
36372
|
+
const baseNames = [basename5(paths.appLog), basename5(paths.stdoutLog), basename5(paths.stderrLog)];
|
|
35906
36373
|
const tracked = new Set(baseNames);
|
|
35907
36374
|
const matched = entries.filter((entry) => isTrackedLogName(entry, tracked));
|
|
35908
36375
|
const files = [];
|
|
@@ -36233,7 +36700,7 @@ var init_plugin_check = __esm(async () => {
|
|
|
36233
36700
|
// src/doctor/checks/runtime-check.ts
|
|
36234
36701
|
import { constants } from "node:fs";
|
|
36235
36702
|
import { access as access4, stat as stat6 } from "node:fs/promises";
|
|
36236
|
-
import { dirname as
|
|
36703
|
+
import { dirname as dirname15 } from "node:path";
|
|
36237
36704
|
import { homedir as homedir18 } from "node:os";
|
|
36238
36705
|
async function checkRuntime(options = {}) {
|
|
36239
36706
|
const home = options.home ?? process.env.HOME ?? homedir18();
|
|
@@ -36389,7 +36856,7 @@ async function checkFileCreatable(label, path17, probe, platform) {
|
|
|
36389
36856
|
detail: `${label}: ${path17} (unusable: ${formatError9(error2)})`
|
|
36390
36857
|
};
|
|
36391
36858
|
}
|
|
36392
|
-
const parentCheck = await checkCreatableAncestorDirectory(
|
|
36859
|
+
const parentCheck = await checkCreatableAncestorDirectory(dirname15(path17), probe, platform);
|
|
36393
36860
|
if (!parentCheck.ok) {
|
|
36394
36861
|
return {
|
|
36395
36862
|
ok: false,
|
|
@@ -36425,7 +36892,7 @@ async function checkCreatableAncestorDirectory(path17, probe, platform) {
|
|
|
36425
36892
|
blockingPath: path17
|
|
36426
36893
|
};
|
|
36427
36894
|
}
|
|
36428
|
-
const parent =
|
|
36895
|
+
const parent = dirname15(path17);
|
|
36429
36896
|
if (parent === path17) {
|
|
36430
36897
|
return {
|
|
36431
36898
|
ok: false,
|
|
@@ -37210,7 +37677,7 @@ var init_doctor2 = __esm(async () => {
|
|
|
37210
37677
|
init_core_home();
|
|
37211
37678
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
37212
37679
|
import { homedir as homedir20 } from "node:os";
|
|
37213
|
-
import { dirname as
|
|
37680
|
+
import { dirname as dirname16, join as join27, sep as sep2 } from "node:path";
|
|
37214
37681
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
37215
37682
|
|
|
37216
37683
|
// src/runtime/migrate-core-home.ts
|
|
@@ -53839,7 +54306,7 @@ function safeDaemonLogPaths() {
|
|
|
53839
54306
|
const configPath = resolveConfigPathForCurrentEnv();
|
|
53840
54307
|
const paths = resolveDaemonPathsForCurrentConfig();
|
|
53841
54308
|
return {
|
|
53842
|
-
appLog: join27(
|
|
54309
|
+
appLog: join27(dirname16(configPath), "runtime", "app.log"),
|
|
53843
54310
|
stderrLog: paths.stderrLog
|
|
53844
54311
|
};
|
|
53845
54312
|
} catch {
|
package/dist/config/types.d.ts
CHANGED
|
@@ -48,6 +48,11 @@ export interface TerminalConfig {
|
|
|
48
48
|
/** Idle seconds before a terminal PTY is auto-killed. Defaults to 900 (15 min). */
|
|
49
49
|
idleTimeoutSeconds?: number;
|
|
50
50
|
}
|
|
51
|
+
export interface FilesConfig {
|
|
52
|
+
/** Default false. When false, all fs write ops (new/rename/delete/copy) are rejected
|
|
53
|
+
* before touching disk. Download is a read and stays available regardless. */
|
|
54
|
+
writeEnabled: boolean;
|
|
55
|
+
}
|
|
51
56
|
export type LoggingLevel = "error" | "info" | "debug";
|
|
52
57
|
export interface PerfLogConfig {
|
|
53
58
|
enabled: boolean;
|
|
@@ -110,6 +115,8 @@ export interface AppConfig {
|
|
|
110
115
|
later?: LaterConfig;
|
|
111
116
|
language?: Locale;
|
|
112
117
|
terminal?: TerminalConfig;
|
|
118
|
+
files?: FilesConfig;
|
|
113
119
|
}
|
|
114
120
|
export declare function terminalEnabled(config: AppConfig): boolean;
|
|
115
121
|
export declare function terminalIdleTimeoutSeconds(config: AppConfig): number;
|
|
122
|
+
export declare function filesWriteEnabled(config: AppConfig): boolean;
|
|
@@ -8,7 +8,7 @@ import type { CancelTaskInput, OrchestrationService, OrchestrationTaskFilter } f
|
|
|
8
8
|
import type { OrchestrationTaskRecord } from "../orchestration/orchestration-types";
|
|
9
9
|
import type { ControlEventBus } from "./control-event-bus";
|
|
10
10
|
import type { AgentCatalogEntry } from "../config/agent-catalog";
|
|
11
|
-
import { type DirListing, type FileContent, type SearchResult, type WorkspaceDiff } from "./workspace-fs";
|
|
11
|
+
import { type DirListing, type FileContent, type SearchOptions, type SearchResult, type WorkspaceDiff } from "./workspace-fs";
|
|
12
12
|
import type { PromptAttachmentRef } from "@ganglion/xacpx-relay-protocol";
|
|
13
13
|
import type { UploadStore } from "./upload-store.js";
|
|
14
14
|
export interface ControlSessionInfo {
|
|
@@ -79,6 +79,7 @@ export interface ControlServiceDeps {
|
|
|
79
79
|
uploadStore: UploadStore;
|
|
80
80
|
terminal: import("./terminal-service").TerminalService;
|
|
81
81
|
terminalEnabled: () => boolean;
|
|
82
|
+
filesWriteEnabled: () => boolean;
|
|
82
83
|
}
|
|
83
84
|
export interface ControlPromptInput {
|
|
84
85
|
chatKey: string;
|
|
@@ -138,7 +139,33 @@ export declare class ControlService {
|
|
|
138
139
|
listDirectory(workspace: string, path?: string): Promise<DirListing>;
|
|
139
140
|
readWorkspaceFile(workspace: string, path: string): Promise<FileContent>;
|
|
140
141
|
workspaceGitDiff(workspace: string, path?: string): Promise<WorkspaceDiff>;
|
|
141
|
-
searchWorkspace(workspace: string,
|
|
142
|
+
searchWorkspace(workspace: string, opts: SearchOptions): Promise<SearchResult>;
|
|
143
|
+
fsCreate(workspace: string, path: string, kind: "file" | "dir"): Promise<{
|
|
144
|
+
path: string;
|
|
145
|
+
}>;
|
|
146
|
+
fsRename(workspace: string, path: string, newName: string): Promise<{
|
|
147
|
+
path: string;
|
|
148
|
+
}>;
|
|
149
|
+
fsDelete(workspace: string, path: string): Promise<{
|
|
150
|
+
path: string;
|
|
151
|
+
}>;
|
|
152
|
+
fsCopy(workspace: string, path: string): Promise<{
|
|
153
|
+
path: string;
|
|
154
|
+
}>;
|
|
155
|
+
fsWrite(workspace: string, path: string, content: string, expected: {
|
|
156
|
+
mtimeMs: number;
|
|
157
|
+
size: number;
|
|
158
|
+
}): Promise<{
|
|
159
|
+
path: string;
|
|
160
|
+
mtimeMs: number;
|
|
161
|
+
size: number;
|
|
162
|
+
}>;
|
|
163
|
+
fsDownload(workspace: string, path: string): Promise<{
|
|
164
|
+
path: string;
|
|
165
|
+
base64: string;
|
|
166
|
+
size: number;
|
|
167
|
+
mimeType: string;
|
|
168
|
+
}>;
|
|
142
169
|
uploadFile(input: {
|
|
143
170
|
filename: string;
|
|
144
171
|
content: string;
|
|
@@ -2,17 +2,24 @@ export interface FsEntry {
|
|
|
2
2
|
name: string;
|
|
3
3
|
type: "dir" | "file";
|
|
4
4
|
size?: number;
|
|
5
|
+
/** True when git check-ignore matches this entry (omitted in non-git workspaces). */
|
|
6
|
+
ignored?: boolean;
|
|
5
7
|
}
|
|
6
8
|
export interface DirListing {
|
|
7
9
|
workspace: string;
|
|
8
10
|
path: string;
|
|
9
11
|
entries: FsEntry[];
|
|
12
|
+
/** Absolute realpath'd workspace root on the connector host. */
|
|
13
|
+
root: string;
|
|
14
|
+
/** Host path separator, so the client can render an absolute host path. */
|
|
15
|
+
sep: "/" | "\\";
|
|
10
16
|
}
|
|
11
17
|
export interface FileContent {
|
|
12
18
|
workspace: string;
|
|
13
19
|
path: string;
|
|
14
20
|
content: string;
|
|
15
21
|
size: number;
|
|
22
|
+
mtimeMs: number;
|
|
16
23
|
truncated: boolean;
|
|
17
24
|
binary: boolean;
|
|
18
25
|
}
|
|
@@ -20,12 +27,31 @@ export interface DiffFile {
|
|
|
20
27
|
path: string;
|
|
21
28
|
status: string;
|
|
22
29
|
}
|
|
30
|
+
export interface SearchHit {
|
|
31
|
+
path: string;
|
|
32
|
+
line: number;
|
|
33
|
+
text: string;
|
|
34
|
+
}
|
|
23
35
|
export interface SearchResult {
|
|
24
36
|
workspace: string;
|
|
25
37
|
query: string;
|
|
26
38
|
matches: string[];
|
|
39
|
+
hits: SearchHit[];
|
|
27
40
|
truncated: boolean;
|
|
28
41
|
}
|
|
42
|
+
export interface SearchOptions {
|
|
43
|
+
query: string;
|
|
44
|
+
mode?: "name" | "content";
|
|
45
|
+
matchCase?: boolean;
|
|
46
|
+
wholeWord?: boolean;
|
|
47
|
+
regex?: boolean;
|
|
48
|
+
/** Glob to restrict matches (relative to workspace root). */
|
|
49
|
+
include?: string;
|
|
50
|
+
/** Glob to drop matches. */
|
|
51
|
+
exclude?: string;
|
|
52
|
+
/** Base directory (relative) to scope the search; defaults to the whole workspace. */
|
|
53
|
+
path?: string;
|
|
54
|
+
}
|
|
29
55
|
export interface WorkspaceDiff {
|
|
30
56
|
workspace: string;
|
|
31
57
|
files: DiffFile[];
|
|
@@ -51,14 +77,69 @@ export declare class WorkspaceFs {
|
|
|
51
77
|
/** Resolve `relPath` within a named workspace, symlink-safe and contained. Throws
|
|
52
78
|
* on unknown workspace, missing target, or any path that escapes the root. */
|
|
53
79
|
private resolve;
|
|
80
|
+
/** Resolve the PARENT directory of a to-be-created/renamed target. Parent must exist
|
|
81
|
+
* and be contained; the final segment is validated (non-empty, no separators, not
|
|
82
|
+
* "."/".."). Because parent is realpath'd+contained and name has no separator, the
|
|
83
|
+
* join target stays inside the workspace root. */
|
|
84
|
+
private resolveParent;
|
|
85
|
+
createFile(workspace: string, relPath: string): Promise<{
|
|
86
|
+
path: string;
|
|
87
|
+
}>;
|
|
88
|
+
createDir(workspace: string, relPath: string): Promise<{
|
|
89
|
+
path: string;
|
|
90
|
+
}>;
|
|
91
|
+
rename(workspace: string, relPath: string, newName: string): Promise<{
|
|
92
|
+
path: string;
|
|
93
|
+
}>;
|
|
94
|
+
duplicate(workspace: string, relPath: string): Promise<{
|
|
95
|
+
path: string;
|
|
96
|
+
}>;
|
|
97
|
+
remove(workspace: string, relPath: string): Promise<{
|
|
98
|
+
path: string;
|
|
99
|
+
}>;
|
|
100
|
+
readFileBytes(workspace: string, relPath: string): Promise<{
|
|
101
|
+
path: string;
|
|
102
|
+
base64: string;
|
|
103
|
+
size: number;
|
|
104
|
+
mimeType: string;
|
|
105
|
+
}>;
|
|
54
106
|
listDirectory(workspace: string, relPath?: string): Promise<DirListing>;
|
|
107
|
+
/** Return the subset of the given root-relative paths that git considers ignored.
|
|
108
|
+
* Uses `check-ignore -z --stdin` (bounded by the caller's MAX_ENTRIES listing).
|
|
109
|
+
* Any git error (not a repo, git missing) yields an empty set — callers degrade to
|
|
110
|
+
* "nothing ignored". Never throws. */
|
|
111
|
+
private gitCheckIgnore;
|
|
55
112
|
readFile(workspace: string, relPath: string): Promise<FileContent>;
|
|
56
|
-
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
|
|
113
|
+
/** Overwrite an EXISTING file's content, guarded by a stale-write token. The target must
|
|
114
|
+
* resolve inside the workspace (realpath containment), be a regular file, and match the
|
|
115
|
+
* caller's `{mtimeMs,size}` token — otherwise `stale-write`. Content must be UTF-8 text
|
|
116
|
+
* (no NUL) and within the read cap. Writes atomically. Returns a fresh token. */
|
|
117
|
+
writeFile(workspace: string, relPath: string, content: string, expected: {
|
|
118
|
+
mtimeMs: number;
|
|
119
|
+
size: number;
|
|
120
|
+
}): Promise<{
|
|
121
|
+
path: string;
|
|
122
|
+
mtimeMs: number;
|
|
123
|
+
size: number;
|
|
124
|
+
}>;
|
|
125
|
+
/** Find files by name (default) or content. Name mode walks relative paths
|
|
126
|
+
* breadth-first, skipping `.git`/`node_modules` and never following symlinks (so it
|
|
127
|
+
* stays contained), applying the query plus any include/exclude globs. Content mode
|
|
128
|
+
* delegates to `contentGrep`. Both are bounded by a scan budget and a result cap. */
|
|
129
|
+
search(workspace: string, opts: SearchOptions): Promise<SearchResult>;
|
|
130
|
+
/** Content grep: `git grep` when the workspace root is a git repo (fast, respects
|
|
131
|
+
* .gitignore), else a bounded manual walk that scans each file line by line. Never
|
|
132
|
+
* throws on git absence/failure or an invalid user regex — both degrade gracefully.
|
|
133
|
+
* `scopeRel` is the already-resolved (canonicalized, containment-checked) scope from
|
|
134
|
+
* `resolve()`; include/exclude are applied as a post-filter on the parsed hits rather
|
|
135
|
+
* than as extra git pathspecs — git ORs multiple positive pathspecs together, so a
|
|
136
|
+
* scope pathspec plus an include pathspec would leak matches from outside the scope. */
|
|
137
|
+
private contentGrep;
|
|
60
138
|
gitDiff(workspace: string, relPath?: string): Promise<WorkspaceDiff>;
|
|
61
139
|
/** Branch + worktree context for a repo root. Best-effort: any git hiccup just
|
|
62
140
|
* omits the fields so the diff itself still returns. */
|
|
63
141
|
private gitContext;
|
|
64
142
|
}
|
|
143
|
+
/** First free "NAME copy", "NAME copy 2"… inserting before the extension. Dotfiles
|
|
144
|
+
* (leading dot, no other dot) and extension-less names get no split. */
|
|
145
|
+
export declare function copyName(existing: Set<string>, name: string): string;
|