@ganglion/xacpx 0.17.0-beta.1 → 0.17.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +494 -51
- package/dist/config/types.d.ts +7 -0
- package/dist/control/control-service.d.ts +21 -2
- package/dist/control/workspace-fs.d.ts +72 -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);
|
|
@@ -33766,16 +33896,28 @@ class WorkspaceFs {
|
|
|
33766
33896
|
await fh.close();
|
|
33767
33897
|
}
|
|
33768
33898
|
}
|
|
33769
|
-
async search(workspace3,
|
|
33770
|
-
const { root } = await this.resolve(workspace3,
|
|
33771
|
-
const
|
|
33899
|
+
async search(workspace3, opts) {
|
|
33900
|
+
const { root, abs: base, rel: scopeRel } = await this.resolve(workspace3, opts.path);
|
|
33901
|
+
const query = opts.query.trim();
|
|
33902
|
+
const empty = { workspace: workspace3, query, matches: [], hits: [], truncated: false };
|
|
33903
|
+
if (!query)
|
|
33904
|
+
return empty;
|
|
33905
|
+
if ((opts.mode ?? "name") === "content") {
|
|
33906
|
+
return this.contentGrep(workspace3, root, base, scopeRel, opts, query);
|
|
33907
|
+
}
|
|
33908
|
+
const matcher = buildMatcher(query, opts);
|
|
33909
|
+
const inc = opts.include ? globToRegExp(opts.include) : null;
|
|
33910
|
+
const exc = opts.exclude ? globToRegExp(opts.exclude) : null;
|
|
33772
33911
|
const matches = [];
|
|
33773
|
-
if (!needle)
|
|
33774
|
-
return { workspace: workspace3, query, matches, truncated: false };
|
|
33775
33912
|
let scanned = 0;
|
|
33776
33913
|
let truncated = false;
|
|
33777
|
-
const
|
|
33914
|
+
const deadline = Date.now() + SEARCH_DEADLINE_MS;
|
|
33915
|
+
const queue = [base];
|
|
33778
33916
|
while (queue.length) {
|
|
33917
|
+
if (Date.now() > deadline) {
|
|
33918
|
+
truncated = true;
|
|
33919
|
+
break;
|
|
33920
|
+
}
|
|
33779
33921
|
const dir = queue.shift();
|
|
33780
33922
|
let dirents;
|
|
33781
33923
|
try {
|
|
@@ -33784,6 +33926,10 @@ class WorkspaceFs {
|
|
|
33784
33926
|
continue;
|
|
33785
33927
|
}
|
|
33786
33928
|
for (const d of dirents) {
|
|
33929
|
+
if (Date.now() > deadline) {
|
|
33930
|
+
truncated = true;
|
|
33931
|
+
break;
|
|
33932
|
+
}
|
|
33787
33933
|
if (++scanned > SEARCH_MAX_SCAN) {
|
|
33788
33934
|
truncated = true;
|
|
33789
33935
|
break;
|
|
@@ -33795,7 +33941,11 @@ class WorkspaceFs {
|
|
|
33795
33941
|
queue.push(resolve3(dir, d.name));
|
|
33796
33942
|
} else if (d.isFile()) {
|
|
33797
33943
|
const rel = relative(root, resolve3(dir, d.name)).split(sep).join("/");
|
|
33798
|
-
if (
|
|
33944
|
+
if (inc && !inc.test(rel))
|
|
33945
|
+
continue;
|
|
33946
|
+
if (exc && exc.test(rel))
|
|
33947
|
+
continue;
|
|
33948
|
+
if (matcher(rel)) {
|
|
33799
33949
|
matches.push(rel);
|
|
33800
33950
|
if (matches.length >= SEARCH_MAX_RESULTS) {
|
|
33801
33951
|
truncated = true;
|
|
@@ -33808,18 +33958,199 @@ class WorkspaceFs {
|
|
|
33808
33958
|
break;
|
|
33809
33959
|
}
|
|
33810
33960
|
matches.sort();
|
|
33811
|
-
return { workspace: workspace3, query, matches, truncated };
|
|
33961
|
+
return { workspace: workspace3, query, matches, hits: [], truncated };
|
|
33962
|
+
}
|
|
33963
|
+
async contentGrep(workspace3, root, base, scopeRel, opts, query) {
|
|
33964
|
+
const inc = opts.include ? globToRegExp(opts.include) : null;
|
|
33965
|
+
const exc = opts.exclude ? globToRegExp(opts.exclude) : null;
|
|
33966
|
+
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);
|
|
33967
|
+
if (inGit) {
|
|
33968
|
+
const args = ["-C", root, "grep", "-n", "--column", "-I", "--no-color", "--untracked"];
|
|
33969
|
+
if (!opts.matchCase)
|
|
33970
|
+
args.push("-i");
|
|
33971
|
+
if (opts.wholeWord)
|
|
33972
|
+
args.push("-w");
|
|
33973
|
+
args.push(opts.regex ? "-E" : "-F");
|
|
33974
|
+
args.push("-e", query, "--", scopeRel || ".");
|
|
33975
|
+
let stdout2 = "";
|
|
33976
|
+
let killed = false;
|
|
33977
|
+
try {
|
|
33978
|
+
stdout2 = (await execFileAsync("git", args, { maxBuffer: GIT_MAX_BUFFER, timeout: GIT_TIMEOUT_MS, killSignal: "SIGKILL" })).stdout;
|
|
33979
|
+
} catch (e) {
|
|
33980
|
+
killed = e.killed === true || e.signal === "SIGKILL";
|
|
33981
|
+
const code = e.code;
|
|
33982
|
+
if (code === 1)
|
|
33983
|
+
stdout2 = "";
|
|
33984
|
+
else {
|
|
33985
|
+
const out = e.stdout;
|
|
33986
|
+
stdout2 = typeof out === "string" ? out : "";
|
|
33987
|
+
}
|
|
33988
|
+
}
|
|
33989
|
+
const hits2 = [];
|
|
33990
|
+
let truncated2 = killed;
|
|
33991
|
+
for (const raw of stdout2.split(`
|
|
33992
|
+
`)) {
|
|
33993
|
+
if (!raw)
|
|
33994
|
+
continue;
|
|
33995
|
+
const m = raw.match(/^(.*?):(\d+):(\d+):(.*)$/);
|
|
33996
|
+
if (!m)
|
|
33997
|
+
continue;
|
|
33998
|
+
const p = m[1].split(sep).join("/");
|
|
33999
|
+
if (inc && !inc.test(p))
|
|
34000
|
+
continue;
|
|
34001
|
+
if (exc && exc.test(p))
|
|
34002
|
+
continue;
|
|
34003
|
+
hits2.push({ path: p, line: Number(m[2]), text: m[4].slice(0, 400) });
|
|
34004
|
+
if (hits2.length >= SEARCH_CONTENT_MAX_HITS) {
|
|
34005
|
+
truncated2 = true;
|
|
34006
|
+
break;
|
|
34007
|
+
}
|
|
34008
|
+
}
|
|
34009
|
+
return { workspace: workspace3, query, matches: [], hits: hits2, truncated: truncated2 };
|
|
34010
|
+
}
|
|
34011
|
+
try {
|
|
34012
|
+
const args = ["grep", "--no-index", "-n", "--column", "-I", "--no-color"];
|
|
34013
|
+
if (!opts.matchCase)
|
|
34014
|
+
args.push("-i");
|
|
34015
|
+
if (opts.wholeWord)
|
|
34016
|
+
args.push("-w");
|
|
34017
|
+
args.push(opts.regex ? "-E" : "-F");
|
|
34018
|
+
args.push("-e", query, "--", ".", ":(exclude)node_modules/", ":(exclude).git/");
|
|
34019
|
+
let stdout2 = "";
|
|
34020
|
+
let killed = false;
|
|
34021
|
+
try {
|
|
34022
|
+
stdout2 = (await execFileAsync("git", args, { cwd: base, maxBuffer: GIT_MAX_BUFFER, timeout: GIT_TIMEOUT_MS, killSignal: "SIGKILL" })).stdout;
|
|
34023
|
+
} catch (e) {
|
|
34024
|
+
if (e.code === "ENOENT")
|
|
34025
|
+
throw e;
|
|
34026
|
+
killed = e.killed === true || e.signal === "SIGKILL";
|
|
34027
|
+
const code = e.code;
|
|
34028
|
+
if (code === 1)
|
|
34029
|
+
stdout2 = "";
|
|
34030
|
+
else {
|
|
34031
|
+
const out = e.stdout;
|
|
34032
|
+
stdout2 = typeof out === "string" ? out : "";
|
|
34033
|
+
}
|
|
34034
|
+
}
|
|
34035
|
+
const hits2 = [];
|
|
34036
|
+
let truncated2 = killed;
|
|
34037
|
+
for (const raw of stdout2.split(`
|
|
34038
|
+
`)) {
|
|
34039
|
+
if (!raw)
|
|
34040
|
+
continue;
|
|
34041
|
+
const m = raw.match(/^(.*?):(\d+):(\d+):(.*)$/);
|
|
34042
|
+
if (!m)
|
|
34043
|
+
continue;
|
|
34044
|
+
const relToBase = m[1].split(sep).join("/");
|
|
34045
|
+
const p = scopeRel ? `${scopeRel}/${relToBase}` : relToBase;
|
|
34046
|
+
if (inc && !inc.test(p))
|
|
34047
|
+
continue;
|
|
34048
|
+
if (exc && exc.test(p))
|
|
34049
|
+
continue;
|
|
34050
|
+
hits2.push({ path: p, line: Number(m[2]), text: m[4].slice(0, 400) });
|
|
34051
|
+
if (hits2.length >= SEARCH_CONTENT_MAX_HITS) {
|
|
34052
|
+
truncated2 = true;
|
|
34053
|
+
break;
|
|
34054
|
+
}
|
|
34055
|
+
}
|
|
34056
|
+
return { workspace: workspace3, query, matches: [], hits: hits2, truncated: truncated2 };
|
|
34057
|
+
} catch (e) {
|
|
34058
|
+
if (e.code !== "ENOENT") {
|
|
34059
|
+
return { workspace: workspace3, query, matches: [], hits: [], truncated: false };
|
|
34060
|
+
}
|
|
34061
|
+
}
|
|
34062
|
+
const matcher = buildLineMatcher(query, opts);
|
|
34063
|
+
const hits = [];
|
|
34064
|
+
let scanned = 0;
|
|
34065
|
+
let truncated = false;
|
|
34066
|
+
const deadline = Date.now() + SEARCH_DEADLINE_MS;
|
|
34067
|
+
const queue = [base];
|
|
34068
|
+
while (queue.length) {
|
|
34069
|
+
if (Date.now() > deadline) {
|
|
34070
|
+
truncated = true;
|
|
34071
|
+
break;
|
|
34072
|
+
}
|
|
34073
|
+
const dir = queue.shift();
|
|
34074
|
+
let dirents;
|
|
34075
|
+
try {
|
|
34076
|
+
dirents = await readdir4(dir, { withFileTypes: true });
|
|
34077
|
+
} catch {
|
|
34078
|
+
continue;
|
|
34079
|
+
}
|
|
34080
|
+
for (const d of dirents) {
|
|
34081
|
+
if (Date.now() > deadline) {
|
|
34082
|
+
truncated = true;
|
|
34083
|
+
break;
|
|
34084
|
+
}
|
|
34085
|
+
if (++scanned > SEARCH_MAX_SCAN) {
|
|
34086
|
+
truncated = true;
|
|
34087
|
+
break;
|
|
34088
|
+
}
|
|
34089
|
+
if (d.isSymbolicLink())
|
|
34090
|
+
continue;
|
|
34091
|
+
const full = resolve3(dir, d.name);
|
|
34092
|
+
if (d.isDirectory()) {
|
|
34093
|
+
if (!SEARCH_SKIP_DIRS.has(d.name))
|
|
34094
|
+
queue.push(full);
|
|
34095
|
+
continue;
|
|
34096
|
+
}
|
|
34097
|
+
if (!d.isFile())
|
|
34098
|
+
continue;
|
|
34099
|
+
const rel = relative(root, full).split(sep).join("/");
|
|
34100
|
+
if (inc && !inc.test(rel))
|
|
34101
|
+
continue;
|
|
34102
|
+
if (exc && exc.test(rel))
|
|
34103
|
+
continue;
|
|
34104
|
+
let info;
|
|
34105
|
+
try {
|
|
34106
|
+
info = await stat3(full);
|
|
34107
|
+
} catch {
|
|
34108
|
+
continue;
|
|
34109
|
+
}
|
|
34110
|
+
if (info.size > SEARCH_CONTENT_MAX_FILE)
|
|
34111
|
+
continue;
|
|
34112
|
+
let text;
|
|
34113
|
+
try {
|
|
34114
|
+
text = await readFile15(full, "utf8");
|
|
34115
|
+
} catch {
|
|
34116
|
+
continue;
|
|
34117
|
+
}
|
|
34118
|
+
if (text.includes("\x00"))
|
|
34119
|
+
continue;
|
|
34120
|
+
const lines = text.split(`
|
|
34121
|
+
`);
|
|
34122
|
+
for (let i = 0;i < lines.length; i++) {
|
|
34123
|
+
if (Date.now() > deadline) {
|
|
34124
|
+
truncated = true;
|
|
34125
|
+
break;
|
|
34126
|
+
}
|
|
34127
|
+
const line = lines[i];
|
|
34128
|
+
if (matcher(line.length > LINE_MATCH_CAP ? line.slice(0, LINE_MATCH_CAP) : line)) {
|
|
34129
|
+
hits.push({ path: rel, line: i + 1, text: line.slice(0, 400) });
|
|
34130
|
+
if (hits.length >= SEARCH_CONTENT_MAX_HITS) {
|
|
34131
|
+
truncated = true;
|
|
34132
|
+
break;
|
|
34133
|
+
}
|
|
34134
|
+
}
|
|
34135
|
+
}
|
|
34136
|
+
if (truncated)
|
|
34137
|
+
break;
|
|
34138
|
+
}
|
|
34139
|
+
if (truncated)
|
|
34140
|
+
break;
|
|
34141
|
+
}
|
|
34142
|
+
return { workspace: workspace3, query, matches: [], hits, truncated };
|
|
33812
34143
|
}
|
|
33813
34144
|
async gitDiff(workspace3, relPath) {
|
|
33814
34145
|
const { root, rel } = await this.resolve(workspace3, relPath);
|
|
33815
34146
|
try {
|
|
33816
|
-
await execFileAsync("git", ["-C", root, "rev-parse", "--is-inside-work-tree"], { maxBuffer: GIT_MAX_BUFFER });
|
|
34147
|
+
await execFileAsync("git", ["-C", root, "rev-parse", "--is-inside-work-tree"], { maxBuffer: GIT_MAX_BUFFER, timeout: GIT_TIMEOUT_MS, killSignal: "SIGKILL" });
|
|
33817
34148
|
} catch {
|
|
33818
34149
|
throw new Error("not-a-git-repo");
|
|
33819
34150
|
}
|
|
33820
34151
|
const files = [];
|
|
33821
34152
|
try {
|
|
33822
|
-
const { stdout: stdout2 } = await execFileAsync("git", ["-C", root, "-c", "core.quotePath=false", "status", "--porcelain", "-z"], { maxBuffer: GIT_MAX_BUFFER });
|
|
34153
|
+
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
34154
|
const fields = stdout2.split("\x00");
|
|
33824
34155
|
for (let i = 0;i < fields.length; i++) {
|
|
33825
34156
|
const field = fields[i];
|
|
@@ -33835,17 +34166,17 @@ class WorkspaceFs {
|
|
|
33835
34166
|
const diffArgs = (base) => ["-C", root, ...base, ...rel ? ["--", rel] : []];
|
|
33836
34167
|
let diff = "";
|
|
33837
34168
|
try {
|
|
33838
|
-
diff = (await execFileAsync("git", diffArgs(["diff", "HEAD"]), { maxBuffer: GIT_MAX_BUFFER })).stdout;
|
|
34169
|
+
diff = (await execFileAsync("git", diffArgs(["diff", "HEAD"]), { maxBuffer: GIT_MAX_BUFFER, timeout: GIT_TIMEOUT_MS, killSignal: "SIGKILL" })).stdout;
|
|
33839
34170
|
} catch {
|
|
33840
34171
|
try {
|
|
33841
|
-
diff = (await execFileAsync("git", diffArgs(["diff"]), { maxBuffer: GIT_MAX_BUFFER })).stdout;
|
|
34172
|
+
diff = (await execFileAsync("git", diffArgs(["diff"]), { maxBuffer: GIT_MAX_BUFFER, timeout: GIT_TIMEOUT_MS, killSignal: "SIGKILL" })).stdout;
|
|
33842
34173
|
} catch {
|
|
33843
34174
|
diff = "";
|
|
33844
34175
|
}
|
|
33845
34176
|
}
|
|
33846
34177
|
if (rel && !diff) {
|
|
33847
34178
|
try {
|
|
33848
|
-
diff = (await execFileAsync("git", ["-C", root, "-c", "core.quotePath=false", "diff", "--no-index", "--", "/dev/null", rel], { maxBuffer: GIT_MAX_BUFFER })).stdout;
|
|
34179
|
+
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
34180
|
} catch (e) {
|
|
33850
34181
|
const out = e.stdout;
|
|
33851
34182
|
if (typeof out === "string")
|
|
@@ -33858,7 +34189,7 @@ class WorkspaceFs {
|
|
|
33858
34189
|
async gitContext(root) {
|
|
33859
34190
|
const run = async (...args) => {
|
|
33860
34191
|
try {
|
|
33861
|
-
return (await execFileAsync("git", ["-C", root, ...args], { maxBuffer: GIT_MAX_BUFFER })).stdout.trim();
|
|
34192
|
+
return (await execFileAsync("git", ["-C", root, ...args], { maxBuffer: GIT_MAX_BUFFER, timeout: GIT_TIMEOUT_MS, killSignal: "SIGKILL" })).stdout.trim();
|
|
33862
34193
|
} catch {
|
|
33863
34194
|
return null;
|
|
33864
34195
|
}
|
|
@@ -33878,13 +34209,101 @@ class WorkspaceFs {
|
|
|
33878
34209
|
return ctx;
|
|
33879
34210
|
}
|
|
33880
34211
|
}
|
|
33881
|
-
|
|
34212
|
+
function escapeRe(s) {
|
|
34213
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
34214
|
+
}
|
|
34215
|
+
function copyName(existing, name) {
|
|
34216
|
+
const dot = name.lastIndexOf(".");
|
|
34217
|
+
const hasExt = dot > 0;
|
|
34218
|
+
const base = hasExt ? name.slice(0, dot) : name;
|
|
34219
|
+
const ext = hasExt ? name.slice(dot) : "";
|
|
34220
|
+
let candidate = `${base} copy${ext}`;
|
|
34221
|
+
let n = 2;
|
|
34222
|
+
while (existing.has(candidate))
|
|
34223
|
+
candidate = `${base} copy ${n++}${ext}`;
|
|
34224
|
+
return candidate;
|
|
34225
|
+
}
|
|
34226
|
+
function mimeForName(name) {
|
|
34227
|
+
const dot = name.lastIndexOf(".");
|
|
34228
|
+
const ext = dot > 0 ? name.slice(dot).toLowerCase() : "";
|
|
34229
|
+
return MIME_BY_EXT[ext] ?? "application/octet-stream";
|
|
34230
|
+
}
|
|
34231
|
+
function buildMatcher(query, opts) {
|
|
34232
|
+
return buildLineMatcher(query, opts);
|
|
34233
|
+
}
|
|
34234
|
+
function buildLineMatcher(query, opts) {
|
|
34235
|
+
const flags = opts.matchCase ? "" : "i";
|
|
34236
|
+
let pattern;
|
|
34237
|
+
if (opts.regex)
|
|
34238
|
+
pattern = query;
|
|
34239
|
+
else
|
|
34240
|
+
pattern = escapeRe(query);
|
|
34241
|
+
if (opts.wholeWord)
|
|
34242
|
+
pattern = `\\b${pattern}\\b`;
|
|
34243
|
+
let re = null;
|
|
34244
|
+
try {
|
|
34245
|
+
re = new RegExp(pattern, flags);
|
|
34246
|
+
} catch {
|
|
34247
|
+
re = null;
|
|
34248
|
+
}
|
|
34249
|
+
if (!re) {
|
|
34250
|
+
const needle = opts.matchCase ? query : query.toLowerCase();
|
|
34251
|
+
return (s) => (opts.matchCase ? s : s.toLowerCase()).includes(needle);
|
|
34252
|
+
}
|
|
34253
|
+
return (s) => re.test(s);
|
|
34254
|
+
}
|
|
34255
|
+
function globToRegExp(glob) {
|
|
34256
|
+
let re = "^";
|
|
34257
|
+
for (let i = 0;i < glob.length; i++) {
|
|
34258
|
+
const c = glob[i];
|
|
34259
|
+
if (c === "*") {
|
|
34260
|
+
if (glob[i + 1] === "*") {
|
|
34261
|
+
re += ".*";
|
|
34262
|
+
i++;
|
|
34263
|
+
if (glob[i + 1] === "/")
|
|
34264
|
+
i++;
|
|
34265
|
+
} else
|
|
34266
|
+
re += "[^/]*";
|
|
34267
|
+
} else if (c === "?")
|
|
34268
|
+
re += "[^/]";
|
|
34269
|
+
else
|
|
34270
|
+
re += escapeRe(c);
|
|
34271
|
+
}
|
|
34272
|
+
re += "$";
|
|
34273
|
+
try {
|
|
34274
|
+
return new RegExp(re);
|
|
34275
|
+
} catch {
|
|
34276
|
+
return /$^/;
|
|
34277
|
+
}
|
|
34278
|
+
}
|
|
34279
|
+
var 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
34280
|
var init_workspace_fs = __esm(() => {
|
|
33883
34281
|
execFileAsync = promisify(execFile);
|
|
33884
34282
|
FILE_READ_CAP = 256 * 1024;
|
|
33885
34283
|
DIFF_CAP = 512 * 1024;
|
|
33886
34284
|
GIT_MAX_BUFFER = 32 * 1024 * 1024;
|
|
33887
34285
|
SEARCH_SKIP_DIRS = new Set([".git", "node_modules"]);
|
|
34286
|
+
SEARCH_CONTENT_MAX_FILE = 1024 * 1024;
|
|
34287
|
+
DOWNLOAD_MAX = 5 * 1024 * 1024;
|
|
34288
|
+
MIME_BY_EXT = {
|
|
34289
|
+
".txt": "text/plain",
|
|
34290
|
+
".md": "text/markdown",
|
|
34291
|
+
".json": "application/json",
|
|
34292
|
+
".js": "text/javascript",
|
|
34293
|
+
".ts": "text/plain",
|
|
34294
|
+
".html": "text/html",
|
|
34295
|
+
".css": "text/css",
|
|
34296
|
+
".csv": "text/csv",
|
|
34297
|
+
".xml": "application/xml",
|
|
34298
|
+
".png": "image/png",
|
|
34299
|
+
".jpg": "image/jpeg",
|
|
34300
|
+
".jpeg": "image/jpeg",
|
|
34301
|
+
".gif": "image/gif",
|
|
34302
|
+
".svg": "image/svg+xml",
|
|
34303
|
+
".webp": "image/webp",
|
|
34304
|
+
".pdf": "application/pdf",
|
|
34305
|
+
".zip": "application/zip"
|
|
34306
|
+
};
|
|
33888
34307
|
});
|
|
33889
34308
|
|
|
33890
34309
|
// src/control/control-service.ts
|
|
@@ -33906,8 +34325,31 @@ class ControlService {
|
|
|
33906
34325
|
workspaceGitDiff(workspace3, path16) {
|
|
33907
34326
|
return this.workspaceFs.gitDiff(workspace3, path16);
|
|
33908
34327
|
}
|
|
33909
|
-
searchWorkspace(workspace3,
|
|
33910
|
-
return this.workspaceFs.search(workspace3,
|
|
34328
|
+
searchWorkspace(workspace3, opts) {
|
|
34329
|
+
return this.workspaceFs.search(workspace3, opts);
|
|
34330
|
+
}
|
|
34331
|
+
async fsCreate(workspace3, path16, kind) {
|
|
34332
|
+
if (!this.deps.filesWriteEnabled())
|
|
34333
|
+
throw new Error("files-write-disabled");
|
|
34334
|
+
return kind === "dir" ? this.workspaceFs.createDir(workspace3, path16) : this.workspaceFs.createFile(workspace3, path16);
|
|
34335
|
+
}
|
|
34336
|
+
async fsRename(workspace3, path16, newName) {
|
|
34337
|
+
if (!this.deps.filesWriteEnabled())
|
|
34338
|
+
throw new Error("files-write-disabled");
|
|
34339
|
+
return this.workspaceFs.rename(workspace3, path16, newName);
|
|
34340
|
+
}
|
|
34341
|
+
async fsDelete(workspace3, path16) {
|
|
34342
|
+
if (!this.deps.filesWriteEnabled())
|
|
34343
|
+
throw new Error("files-write-disabled");
|
|
34344
|
+
return this.workspaceFs.remove(workspace3, path16);
|
|
34345
|
+
}
|
|
34346
|
+
async fsCopy(workspace3, path16) {
|
|
34347
|
+
if (!this.deps.filesWriteEnabled())
|
|
34348
|
+
throw new Error("files-write-disabled");
|
|
34349
|
+
return this.workspaceFs.duplicate(workspace3, path16);
|
|
34350
|
+
}
|
|
34351
|
+
async fsDownload(workspace3, path16) {
|
|
34352
|
+
return this.workspaceFs.readFileBytes(workspace3, path16);
|
|
33911
34353
|
}
|
|
33912
34354
|
async uploadFile(input) {
|
|
33913
34355
|
return this.deps.uploadStore.save(input.filename, input.content, input.mimeType);
|
|
@@ -34445,7 +34887,7 @@ function realPtySpawn(file, args, opts) {
|
|
|
34445
34887
|
return spawnPty2(file, args, opts);
|
|
34446
34888
|
}
|
|
34447
34889
|
function createTerminalService(deps) {
|
|
34448
|
-
const
|
|
34890
|
+
const spawn12 = deps.spawn ?? realPtySpawn;
|
|
34449
34891
|
const platform = deps.platform ?? process.platform;
|
|
34450
34892
|
const sessions = new Map;
|
|
34451
34893
|
const setTimer = deps.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
@@ -34471,7 +34913,7 @@ function createTerminalService(deps) {
|
|
|
34471
34913
|
if (platform === "win32")
|
|
34472
34914
|
throw new Error("terminal-unsupported-platform");
|
|
34473
34915
|
const terminalId = randomUUID4();
|
|
34474
|
-
const handle =
|
|
34916
|
+
const handle = spawn12(defaultShell(platform), [], { name: "xterm-256color", cols, rows, cwd, env: scrubEnv() });
|
|
34475
34917
|
const session3 = { handle, seq: 0, idleTimer: null };
|
|
34476
34918
|
sessions.set(terminalId, session3);
|
|
34477
34919
|
handle.onData((data) => {
|
|
@@ -34545,7 +34987,7 @@ var init_terminal_service = __esm(() => {
|
|
|
34545
34987
|
});
|
|
34546
34988
|
|
|
34547
34989
|
// src/control/upload-store.ts
|
|
34548
|
-
import { mkdtemp as mkdtemp2, readdir as readdir5, rm as
|
|
34990
|
+
import { mkdtemp as mkdtemp2, readdir as readdir5, rm as rm11, stat as stat4, writeFile as writeFile9 } from "node:fs/promises";
|
|
34549
34991
|
import { homedir as homedir13 } from "node:os";
|
|
34550
34992
|
import path16 from "node:path";
|
|
34551
34993
|
function defaultRootDir() {
|
|
@@ -34581,11 +35023,11 @@ class UploadStore {
|
|
|
34581
35023
|
if (bytes.byteLength > this.maxBytes)
|
|
34582
35024
|
throw new Error("file-too-large");
|
|
34583
35025
|
const safeName = sanitizeUploadFilename(filename);
|
|
34584
|
-
const { mkdir:
|
|
34585
|
-
await
|
|
35026
|
+
const { mkdir: mkdir10 } = await import("node:fs/promises");
|
|
35027
|
+
await mkdir10(this.rootDir, { recursive: true });
|
|
34586
35028
|
const dir = await mkdtemp2(path16.join(this.rootDir, "u-"));
|
|
34587
35029
|
const filePath = path16.join(dir, safeName);
|
|
34588
|
-
await
|
|
35030
|
+
await writeFile9(filePath, bytes);
|
|
34589
35031
|
return {
|
|
34590
35032
|
id: path16.basename(dir),
|
|
34591
35033
|
path: filePath,
|
|
@@ -34608,7 +35050,7 @@ class UploadStore {
|
|
|
34608
35050
|
try {
|
|
34609
35051
|
const info = await stat4(dir);
|
|
34610
35052
|
if (info.mtimeMs < cutoff) {
|
|
34611
|
-
await
|
|
35053
|
+
await rm11(dir, { recursive: true, force: true });
|
|
34612
35054
|
removed += 1;
|
|
34613
35055
|
}
|
|
34614
35056
|
} catch {}
|
|
@@ -34666,12 +35108,12 @@ var init_agent_catalog = __esm(() => {
|
|
|
34666
35108
|
|
|
34667
35109
|
// src/config/config-watcher.ts
|
|
34668
35110
|
import { watch } from "node:fs";
|
|
34669
|
-
import { basename as
|
|
35111
|
+
import { basename as basename4, dirname as dirname13 } from "node:path";
|
|
34670
35112
|
function startConfigWatcher(options) {
|
|
34671
35113
|
const { configPath, onChange, debounceMs = 250, logger: logger2 } = options;
|
|
34672
35114
|
const watchFactory = options.watchFactory ?? watch;
|
|
34673
|
-
const dir =
|
|
34674
|
-
const target =
|
|
35115
|
+
const dir = dirname13(configPath);
|
|
35116
|
+
const target = basename4(configPath);
|
|
34675
35117
|
let timer;
|
|
34676
35118
|
let watcher;
|
|
34677
35119
|
const fire = () => {
|
|
@@ -34686,7 +35128,7 @@ function startConfigWatcher(options) {
|
|
|
34686
35128
|
};
|
|
34687
35129
|
try {
|
|
34688
35130
|
watcher = watchFactory(dir, { persistent: false }, (_event, filename) => {
|
|
34689
|
-
if (filename !== null &&
|
|
35131
|
+
if (filename !== null && basename4(filename.toString()) !== target)
|
|
34690
35132
|
return;
|
|
34691
35133
|
if (timer)
|
|
34692
35134
|
clearTimeout(timer);
|
|
@@ -34726,7 +35168,7 @@ __export(exports_main, {
|
|
|
34726
35168
|
});
|
|
34727
35169
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
34728
35170
|
import { homedir as homedir14 } from "node:os";
|
|
34729
|
-
import { dirname as
|
|
35171
|
+
import { dirname as dirname14, join as join23 } from "node:path";
|
|
34730
35172
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
34731
35173
|
function startProgressHeartbeat(orchestration3, config4, logger2, channel) {
|
|
34732
35174
|
const thresholdSeconds = config4.orchestration.progressHeartbeatSeconds;
|
|
@@ -35271,7 +35713,8 @@ async function buildApp(paths, deps = {}) {
|
|
|
35271
35713
|
},
|
|
35272
35714
|
uploadStore,
|
|
35273
35715
|
terminal: terminalService,
|
|
35274
|
-
terminalEnabled: () => terminalEnabled(config4)
|
|
35716
|
+
terminalEnabled: () => terminalEnabled(config4),
|
|
35717
|
+
filesWriteEnabled: () => filesWriteEnabled(config4)
|
|
35275
35718
|
});
|
|
35276
35719
|
const workspaceSignature = (cfg) => JSON.stringify(Object.keys(cfg.workspaces).sort().map((name) => {
|
|
35277
35720
|
const ws = cfg.workspaces[name];
|
|
@@ -35424,7 +35867,7 @@ async function main() {
|
|
|
35424
35867
|
}
|
|
35425
35868
|
}
|
|
35426
35869
|
async function prepareChannelMedia(configPath, config4) {
|
|
35427
|
-
const runtimeDir = join23(
|
|
35870
|
+
const runtimeDir = join23(dirname14(configPath), "runtime");
|
|
35428
35871
|
const mediaRootDir = join23(runtimeDir, "media");
|
|
35429
35872
|
const mediaStore = new RuntimeMediaStore({ rootDir: mediaRootDir });
|
|
35430
35873
|
await mediaStore.cleanupExpired().catch((error2) => {
|
|
@@ -35439,7 +35882,7 @@ function resolveRuntimePaths() {
|
|
|
35439
35882
|
throw new Error("Unable to resolve the current user home directory");
|
|
35440
35883
|
}
|
|
35441
35884
|
const configPath = coreEnv("CONFIG") ?? join23(coreHomeDir(home), "config.json");
|
|
35442
|
-
const runtimeDir = join23(
|
|
35885
|
+
const runtimeDir = join23(dirname14(configPath), "runtime");
|
|
35443
35886
|
return {
|
|
35444
35887
|
configPath,
|
|
35445
35888
|
statePath: coreEnv("STATE") ?? join23(coreHomeDir(home), "state.json"),
|
|
@@ -35454,12 +35897,12 @@ function resolveBridgeEntryPath() {
|
|
|
35454
35897
|
return fileURLToPath5(new URL("./bridge/bridge-main.ts", import.meta.url));
|
|
35455
35898
|
}
|
|
35456
35899
|
function resolveAppLogPath(configPath) {
|
|
35457
|
-
const rootDir =
|
|
35900
|
+
const rootDir = dirname14(configPath);
|
|
35458
35901
|
const runtimeDir = join23(rootDir, "runtime");
|
|
35459
35902
|
return join23(runtimeDir, "app.log");
|
|
35460
35903
|
}
|
|
35461
35904
|
function resolvePerfLogPath(configPath) {
|
|
35462
|
-
const rootDir =
|
|
35905
|
+
const rootDir = dirname14(configPath);
|
|
35463
35906
|
const runtimeDir = join23(rootDir, "runtime");
|
|
35464
35907
|
return join23(runtimeDir, "perf.log");
|
|
35465
35908
|
}
|
|
@@ -35517,7 +35960,7 @@ var init_main = __esm(async () => {
|
|
|
35517
35960
|
});
|
|
35518
35961
|
|
|
35519
35962
|
// src/doctor/checks/acpx-check.ts
|
|
35520
|
-
import { spawn as
|
|
35963
|
+
import { spawn as spawn12 } from "node:child_process";
|
|
35521
35964
|
async function checkAcpx(options = {}) {
|
|
35522
35965
|
const runtimePaths = (options.resolveRuntimePaths ?? resolveRuntimePaths)();
|
|
35523
35966
|
try {
|
|
@@ -35564,7 +36007,7 @@ function buildDetails(metadata, version2, verbose) {
|
|
|
35564
36007
|
async function defaultRunVersion(command) {
|
|
35565
36008
|
const spawnSpec = resolveSpawnCommand(command, ["--version"]);
|
|
35566
36009
|
return await new Promise((resolve4, reject) => {
|
|
35567
|
-
const child =
|
|
36010
|
+
const child = spawn12(spawnSpec.command, spawnSpec.args, {
|
|
35568
36011
|
stdio: ["ignore", "pipe", "pipe"]
|
|
35569
36012
|
});
|
|
35570
36013
|
let stdout2 = "";
|
|
@@ -35702,7 +36145,7 @@ var init_config_check = __esm(async () => {
|
|
|
35702
36145
|
});
|
|
35703
36146
|
|
|
35704
36147
|
// src/doctor/checks/daemon-check.ts
|
|
35705
|
-
import { readdir as readdir6, readFile as
|
|
36148
|
+
import { readdir as readdir6, readFile as readFile16, rm as rm12 } from "node:fs/promises";
|
|
35706
36149
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
35707
36150
|
import { homedir as homedir15 } from "node:os";
|
|
35708
36151
|
import { join as join24 } from "node:path";
|
|
@@ -35848,7 +36291,7 @@ async function defaultListConsumerLocks(runtimeDir) {
|
|
|
35848
36291
|
}
|
|
35849
36292
|
async function defaultReadConsumerLock(path17) {
|
|
35850
36293
|
try {
|
|
35851
|
-
const raw = await
|
|
36294
|
+
const raw = await readFile16(path17, "utf8");
|
|
35852
36295
|
const parsed = JSON.parse(raw);
|
|
35853
36296
|
return typeof parsed.pid === "number" ? { pid: parsed.pid } : null;
|
|
35854
36297
|
} catch {
|
|
@@ -35856,7 +36299,7 @@ async function defaultReadConsumerLock(path17) {
|
|
|
35856
36299
|
}
|
|
35857
36300
|
}
|
|
35858
36301
|
async function defaultRemoveConsumerLock(path17) {
|
|
35859
|
-
await
|
|
36302
|
+
await rm12(path17, { force: true });
|
|
35860
36303
|
}
|
|
35861
36304
|
function resolveCliEntryPath() {
|
|
35862
36305
|
return process.argv[1] ?? fileURLToPath6(import.meta.url);
|
|
@@ -35872,7 +36315,7 @@ var init_daemon_check = __esm(() => {
|
|
|
35872
36315
|
|
|
35873
36316
|
// src/doctor/checks/logs-check.ts
|
|
35874
36317
|
import { stat as stat5, readdir as readdir7 } from "node:fs/promises";
|
|
35875
|
-
import { basename as
|
|
36318
|
+
import { basename as basename5, join as join25 } from "node:path";
|
|
35876
36319
|
import { homedir as homedir16 } from "node:os";
|
|
35877
36320
|
async function checkLogs(options = {}) {
|
|
35878
36321
|
const home = options.home ?? process.env.HOME ?? homedir16();
|
|
@@ -35902,7 +36345,7 @@ async function checkLogs(options = {}) {
|
|
|
35902
36345
|
`error: ${formatError6(error2)}`
|
|
35903
36346
|
]);
|
|
35904
36347
|
}
|
|
35905
|
-
const baseNames = [
|
|
36348
|
+
const baseNames = [basename5(paths.appLog), basename5(paths.stdoutLog), basename5(paths.stderrLog)];
|
|
35906
36349
|
const tracked = new Set(baseNames);
|
|
35907
36350
|
const matched = entries.filter((entry) => isTrackedLogName(entry, tracked));
|
|
35908
36351
|
const files = [];
|
|
@@ -36233,7 +36676,7 @@ var init_plugin_check = __esm(async () => {
|
|
|
36233
36676
|
// src/doctor/checks/runtime-check.ts
|
|
36234
36677
|
import { constants } from "node:fs";
|
|
36235
36678
|
import { access as access4, stat as stat6 } from "node:fs/promises";
|
|
36236
|
-
import { dirname as
|
|
36679
|
+
import { dirname as dirname15 } from "node:path";
|
|
36237
36680
|
import { homedir as homedir18 } from "node:os";
|
|
36238
36681
|
async function checkRuntime(options = {}) {
|
|
36239
36682
|
const home = options.home ?? process.env.HOME ?? homedir18();
|
|
@@ -36389,7 +36832,7 @@ async function checkFileCreatable(label, path17, probe, platform) {
|
|
|
36389
36832
|
detail: `${label}: ${path17} (unusable: ${formatError9(error2)})`
|
|
36390
36833
|
};
|
|
36391
36834
|
}
|
|
36392
|
-
const parentCheck = await checkCreatableAncestorDirectory(
|
|
36835
|
+
const parentCheck = await checkCreatableAncestorDirectory(dirname15(path17), probe, platform);
|
|
36393
36836
|
if (!parentCheck.ok) {
|
|
36394
36837
|
return {
|
|
36395
36838
|
ok: false,
|
|
@@ -36425,7 +36868,7 @@ async function checkCreatableAncestorDirectory(path17, probe, platform) {
|
|
|
36425
36868
|
blockingPath: path17
|
|
36426
36869
|
};
|
|
36427
36870
|
}
|
|
36428
|
-
const parent =
|
|
36871
|
+
const parent = dirname15(path17);
|
|
36429
36872
|
if (parent === path17) {
|
|
36430
36873
|
return {
|
|
36431
36874
|
ok: false,
|
|
@@ -37210,7 +37653,7 @@ var init_doctor2 = __esm(async () => {
|
|
|
37210
37653
|
init_core_home();
|
|
37211
37654
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
37212
37655
|
import { homedir as homedir20 } from "node:os";
|
|
37213
|
-
import { dirname as
|
|
37656
|
+
import { dirname as dirname16, join as join27, sep as sep2 } from "node:path";
|
|
37214
37657
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
37215
37658
|
|
|
37216
37659
|
// src/runtime/migrate-core-home.ts
|
|
@@ -53839,7 +54282,7 @@ function safeDaemonLogPaths() {
|
|
|
53839
54282
|
const configPath = resolveConfigPathForCurrentEnv();
|
|
53840
54283
|
const paths = resolveDaemonPathsForCurrentConfig();
|
|
53841
54284
|
return {
|
|
53842
|
-
appLog: join27(
|
|
54285
|
+
appLog: join27(dirname16(configPath), "runtime", "app.log"),
|
|
53843
54286
|
stderrLog: paths.stderrLog
|
|
53844
54287
|
};
|
|
53845
54288
|
} 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,25 @@ 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
|
+
fsDownload(workspace: string, path: string): Promise<{
|
|
156
|
+
path: string;
|
|
157
|
+
base64: string;
|
|
158
|
+
size: number;
|
|
159
|
+
mimeType: string;
|
|
160
|
+
}>;
|
|
142
161
|
uploadFile(input: {
|
|
143
162
|
filename: string;
|
|
144
163
|
content: string;
|
|
@@ -2,11 +2,17 @@ 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;
|
|
@@ -20,12 +26,31 @@ export interface DiffFile {
|
|
|
20
26
|
path: string;
|
|
21
27
|
status: string;
|
|
22
28
|
}
|
|
29
|
+
export interface SearchHit {
|
|
30
|
+
path: string;
|
|
31
|
+
line: number;
|
|
32
|
+
text: string;
|
|
33
|
+
}
|
|
23
34
|
export interface SearchResult {
|
|
24
35
|
workspace: string;
|
|
25
36
|
query: string;
|
|
26
37
|
matches: string[];
|
|
38
|
+
hits: SearchHit[];
|
|
27
39
|
truncated: boolean;
|
|
28
40
|
}
|
|
41
|
+
export interface SearchOptions {
|
|
42
|
+
query: string;
|
|
43
|
+
mode?: "name" | "content";
|
|
44
|
+
matchCase?: boolean;
|
|
45
|
+
wholeWord?: boolean;
|
|
46
|
+
regex?: boolean;
|
|
47
|
+
/** Glob to restrict matches (relative to workspace root). */
|
|
48
|
+
include?: string;
|
|
49
|
+
/** Glob to drop matches. */
|
|
50
|
+
exclude?: string;
|
|
51
|
+
/** Base directory (relative) to scope the search; defaults to the whole workspace. */
|
|
52
|
+
path?: string;
|
|
53
|
+
}
|
|
29
54
|
export interface WorkspaceDiff {
|
|
30
55
|
workspace: string;
|
|
31
56
|
files: DiffFile[];
|
|
@@ -51,14 +76,57 @@ export declare class WorkspaceFs {
|
|
|
51
76
|
/** Resolve `relPath` within a named workspace, symlink-safe and contained. Throws
|
|
52
77
|
* on unknown workspace, missing target, or any path that escapes the root. */
|
|
53
78
|
private resolve;
|
|
79
|
+
/** Resolve the PARENT directory of a to-be-created/renamed target. Parent must exist
|
|
80
|
+
* and be contained; the final segment is validated (non-empty, no separators, not
|
|
81
|
+
* "."/".."). Because parent is realpath'd+contained and name has no separator, the
|
|
82
|
+
* join target stays inside the workspace root. */
|
|
83
|
+
private resolveParent;
|
|
84
|
+
createFile(workspace: string, relPath: string): Promise<{
|
|
85
|
+
path: string;
|
|
86
|
+
}>;
|
|
87
|
+
createDir(workspace: string, relPath: string): Promise<{
|
|
88
|
+
path: string;
|
|
89
|
+
}>;
|
|
90
|
+
rename(workspace: string, relPath: string, newName: string): Promise<{
|
|
91
|
+
path: string;
|
|
92
|
+
}>;
|
|
93
|
+
duplicate(workspace: string, relPath: string): Promise<{
|
|
94
|
+
path: string;
|
|
95
|
+
}>;
|
|
96
|
+
remove(workspace: string, relPath: string): Promise<{
|
|
97
|
+
path: string;
|
|
98
|
+
}>;
|
|
99
|
+
readFileBytes(workspace: string, relPath: string): Promise<{
|
|
100
|
+
path: string;
|
|
101
|
+
base64: string;
|
|
102
|
+
size: number;
|
|
103
|
+
mimeType: string;
|
|
104
|
+
}>;
|
|
54
105
|
listDirectory(workspace: string, relPath?: string): Promise<DirListing>;
|
|
106
|
+
/** Return the subset of the given root-relative paths that git considers ignored.
|
|
107
|
+
* Uses `check-ignore -z --stdin` (bounded by the caller's MAX_ENTRIES listing).
|
|
108
|
+
* Any git error (not a repo, git missing) yields an empty set — callers degrade to
|
|
109
|
+
* "nothing ignored". Never throws. */
|
|
110
|
+
private gitCheckIgnore;
|
|
55
111
|
readFile(workspace: string, relPath: string): Promise<FileContent>;
|
|
56
|
-
/** Find files
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
|
|
112
|
+
/** Find files by name (default) or content. Name mode walks relative paths
|
|
113
|
+
* breadth-first, skipping `.git`/`node_modules` and never following symlinks (so it
|
|
114
|
+
* stays contained), applying the query plus any include/exclude globs. Content mode
|
|
115
|
+
* delegates to `contentGrep`. Both are bounded by a scan budget and a result cap. */
|
|
116
|
+
search(workspace: string, opts: SearchOptions): Promise<SearchResult>;
|
|
117
|
+
/** Content grep: `git grep` when the workspace root is a git repo (fast, respects
|
|
118
|
+
* .gitignore), else a bounded manual walk that scans each file line by line. Never
|
|
119
|
+
* throws on git absence/failure or an invalid user regex — both degrade gracefully.
|
|
120
|
+
* `scopeRel` is the already-resolved (canonicalized, containment-checked) scope from
|
|
121
|
+
* `resolve()`; include/exclude are applied as a post-filter on the parsed hits rather
|
|
122
|
+
* than as extra git pathspecs — git ORs multiple positive pathspecs together, so a
|
|
123
|
+
* scope pathspec plus an include pathspec would leak matches from outside the scope. */
|
|
124
|
+
private contentGrep;
|
|
60
125
|
gitDiff(workspace: string, relPath?: string): Promise<WorkspaceDiff>;
|
|
61
126
|
/** Branch + worktree context for a repo root. Best-effort: any git hiccup just
|
|
62
127
|
* omits the fields so the diff itself still returns. */
|
|
63
128
|
private gitContext;
|
|
64
129
|
}
|
|
130
|
+
/** First free "NAME copy", "NAME copy 2"… inserting before the extension. Dotfiles
|
|
131
|
+
* (leading dot, no other dot) and extension-less names get no split. */
|
|
132
|
+
export declare function copyName(existing: Set<string>, name: string): string;
|