@ganglion/xacpx 0.17.0-beta.0 → 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
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) {
|
|
33727
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) {
|
|
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,17 +34209,106 @@ 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
|
|
33891
34310
|
import path15 from "node:path";
|
|
34311
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
33892
34312
|
|
|
33893
34313
|
class ControlService {
|
|
33894
34314
|
deps;
|
|
@@ -33905,8 +34325,31 @@ class ControlService {
|
|
|
33905
34325
|
workspaceGitDiff(workspace3, path16) {
|
|
33906
34326
|
return this.workspaceFs.gitDiff(workspace3, path16);
|
|
33907
34327
|
}
|
|
33908
|
-
searchWorkspace(workspace3,
|
|
33909
|
-
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);
|
|
33910
34353
|
}
|
|
33911
34354
|
async uploadFile(input) {
|
|
33912
34355
|
return this.deps.uploadStore.save(input.filename, input.content, input.mimeType);
|
|
@@ -34056,12 +34499,23 @@ class ControlService {
|
|
|
34056
34499
|
return task;
|
|
34057
34500
|
}
|
|
34058
34501
|
inFlight = new Map;
|
|
34502
|
+
queues = new Map;
|
|
34503
|
+
draining = new Set;
|
|
34504
|
+
emitQueueUpdated(chatKey, sessionAlias) {
|
|
34505
|
+
const items = (this.queues.get(turnKey(chatKey, sessionAlias)) ?? []).map((q) => ({
|
|
34506
|
+
id: q.id,
|
|
34507
|
+
textPreview: q.text.length > QUEUE_PREVIEW_MAX ? q.text.slice(0, QUEUE_PREVIEW_MAX) : q.text,
|
|
34508
|
+
enqueuedAt: q.enqueuedAt
|
|
34509
|
+
}));
|
|
34510
|
+
this.deps.events.emit({ type: "queue-updated", chatKey, sessionAlias, items });
|
|
34511
|
+
}
|
|
34059
34512
|
async prompt(input) {
|
|
34060
34513
|
return this.executeTurn({
|
|
34061
34514
|
chatKey: input.chatKey,
|
|
34062
34515
|
sessionAlias: input.sessionAlias,
|
|
34063
34516
|
text: input.text,
|
|
34064
34517
|
senderId: input.senderId,
|
|
34518
|
+
queueable: true,
|
|
34065
34519
|
...input.isOwner !== undefined ? { isOwner: input.isOwner } : {},
|
|
34066
34520
|
...input.accountId !== undefined ? { accountId: input.accountId } : {},
|
|
34067
34521
|
...input.media !== undefined ? { media: input.media } : {}
|
|
@@ -34081,14 +34535,34 @@ class ControlService {
|
|
|
34081
34535
|
}
|
|
34082
34536
|
async executeTurn(params) {
|
|
34083
34537
|
const key = turnKey(params.chatKey, params.sessionAlias);
|
|
34084
|
-
|
|
34085
|
-
|
|
34086
|
-
|
|
34538
|
+
if (!params.drained) {
|
|
34539
|
+
const existing = this.inFlight.get(key);
|
|
34540
|
+
const busy = this.draining.has(key) || existing !== undefined && !existing.controller.signal.aborted;
|
|
34541
|
+
if (busy) {
|
|
34542
|
+
if (params.queueable) {
|
|
34543
|
+
const id = randomUUID3();
|
|
34544
|
+
const item = {
|
|
34545
|
+
id,
|
|
34546
|
+
text: params.text,
|
|
34547
|
+
enqueuedAt: new Date().toISOString(),
|
|
34548
|
+
senderId: params.senderId,
|
|
34549
|
+
...params.isOwner !== undefined ? { isOwner: params.isOwner } : {},
|
|
34550
|
+
...params.accountId !== undefined ? { accountId: params.accountId } : {},
|
|
34551
|
+
...params.media !== undefined ? { media: params.media } : {}
|
|
34552
|
+
};
|
|
34553
|
+
const q = this.queues.get(key) ?? [];
|
|
34554
|
+
q.push(item);
|
|
34555
|
+
this.queues.set(key, q);
|
|
34556
|
+
this.emitQueueUpdated(params.chatKey, params.sessionAlias);
|
|
34557
|
+
return { ok: true, queued: true, queueItemId: id };
|
|
34558
|
+
}
|
|
34087
34559
|
return { ok: false, errorMessage: "turn-already-running" };
|
|
34088
34560
|
}
|
|
34089
|
-
|
|
34090
|
-
|
|
34091
|
-
|
|
34561
|
+
if (existing) {
|
|
34562
|
+
await raceWithTimeout(existing.settled, CANCEL_DRAIN_TIMEOUT_MS);
|
|
34563
|
+
if (this.inFlight.has(key)) {
|
|
34564
|
+
return { ok: false, errorMessage: "turn-already-running" };
|
|
34565
|
+
}
|
|
34092
34566
|
}
|
|
34093
34567
|
}
|
|
34094
34568
|
const controller = new AbortController;
|
|
@@ -34103,6 +34577,9 @@ class ControlService {
|
|
|
34103
34577
|
resolveSettled = resolve4;
|
|
34104
34578
|
});
|
|
34105
34579
|
this.inFlight.set(key, { controller, settled });
|
|
34580
|
+
if (params.drained) {
|
|
34581
|
+
this.draining.delete(key);
|
|
34582
|
+
}
|
|
34106
34583
|
let internalAlias;
|
|
34107
34584
|
let wasArchived = false;
|
|
34108
34585
|
let priorTransportSession;
|
|
@@ -34115,8 +34592,8 @@ class ControlService {
|
|
|
34115
34592
|
try {
|
|
34116
34593
|
await this.deps.sessions.useSession(params.chatKey, params.sessionAlias);
|
|
34117
34594
|
} catch (error2) {
|
|
34118
|
-
this.inFlight.delete(key);
|
|
34119
34595
|
resolveSettled();
|
|
34596
|
+
this.advanceQueue(key, params.chatKey, params.sessionAlias);
|
|
34120
34597
|
return { ok: false, errorMessage: toErrorMessage(error2) };
|
|
34121
34598
|
}
|
|
34122
34599
|
if (wasArchived) {
|
|
@@ -34127,7 +34604,8 @@ class ControlService {
|
|
|
34127
34604
|
chatKey: params.chatKey,
|
|
34128
34605
|
sessionAlias: params.sessionAlias,
|
|
34129
34606
|
...params.turnStarted?.prompt ? { prompt: params.turnStarted.prompt } : {},
|
|
34130
|
-
...params.turnStarted?.scheduled ? { scheduled: params.turnStarted.scheduled } : {}
|
|
34607
|
+
...params.turnStarted?.scheduled ? { scheduled: params.turnStarted.scheduled } : {},
|
|
34608
|
+
...params.turnStarted?.queueItemId ? { queueItemId: params.turnStarted.queueItemId } : {}
|
|
34131
34609
|
});
|
|
34132
34610
|
let streamMode = false;
|
|
34133
34611
|
try {
|
|
@@ -34251,8 +34729,9 @@ ${chunk}` : chunk
|
|
|
34251
34729
|
});
|
|
34252
34730
|
return { ok: false, errorMessage };
|
|
34253
34731
|
} finally {
|
|
34254
|
-
this.
|
|
34255
|
-
|
|
34732
|
+
if ((this.queues.get(key)?.length ?? 0) > 0) {
|
|
34733
|
+
this.draining.add(key);
|
|
34734
|
+
}
|
|
34256
34735
|
if (internalAlias && priorTransportSession) {
|
|
34257
34736
|
try {
|
|
34258
34737
|
const after = await this.deps.sessions.getSession(internalAlias);
|
|
@@ -34261,6 +34740,33 @@ ${chunk}` : chunk
|
|
|
34261
34740
|
}
|
|
34262
34741
|
} catch {}
|
|
34263
34742
|
}
|
|
34743
|
+
resolveSettled();
|
|
34744
|
+
this.advanceQueue(key, params.chatKey, params.sessionAlias);
|
|
34745
|
+
}
|
|
34746
|
+
}
|
|
34747
|
+
advanceQueue(key, chatKey, sessionAlias) {
|
|
34748
|
+
const q = this.queues.get(key);
|
|
34749
|
+
const next = q?.shift();
|
|
34750
|
+
if (q && q.length === 0)
|
|
34751
|
+
this.queues.delete(key);
|
|
34752
|
+
if (next) {
|
|
34753
|
+
this.draining.add(key);
|
|
34754
|
+
this.emitQueueUpdated(chatKey, sessionAlias);
|
|
34755
|
+
this.executeTurn({
|
|
34756
|
+
chatKey,
|
|
34757
|
+
sessionAlias,
|
|
34758
|
+
text: next.text,
|
|
34759
|
+
senderId: next.senderId,
|
|
34760
|
+
queueable: true,
|
|
34761
|
+
drained: true,
|
|
34762
|
+
...next.isOwner !== undefined ? { isOwner: next.isOwner } : {},
|
|
34763
|
+
...next.accountId !== undefined ? { accountId: next.accountId } : {},
|
|
34764
|
+
...next.media !== undefined ? { media: next.media } : {},
|
|
34765
|
+
turnStarted: { queueItemId: next.id }
|
|
34766
|
+
});
|
|
34767
|
+
} else {
|
|
34768
|
+
this.draining.delete(key);
|
|
34769
|
+
this.inFlight.delete(key);
|
|
34264
34770
|
}
|
|
34265
34771
|
}
|
|
34266
34772
|
cancelTurn(chatKey, sessionAlias) {
|
|
@@ -34271,6 +34777,20 @@ ${chunk}` : chunk
|
|
|
34271
34777
|
entry.controller.abort();
|
|
34272
34778
|
return true;
|
|
34273
34779
|
}
|
|
34780
|
+
cancelQueuedItem(chatKey, sessionAlias, itemId) {
|
|
34781
|
+
const key = turnKey(chatKey, sessionAlias);
|
|
34782
|
+
const q = this.queues.get(key);
|
|
34783
|
+
if (!q)
|
|
34784
|
+
return { cancelled: false };
|
|
34785
|
+
const i = q.findIndex((x) => x.id === itemId);
|
|
34786
|
+
if (i < 0)
|
|
34787
|
+
return { cancelled: false };
|
|
34788
|
+
q.splice(i, 1);
|
|
34789
|
+
if (q.length === 0)
|
|
34790
|
+
this.queues.delete(key);
|
|
34791
|
+
this.emitQueueUpdated(chatKey, sessionAlias);
|
|
34792
|
+
return { cancelled: true };
|
|
34793
|
+
}
|
|
34274
34794
|
async executeCommand(input) {
|
|
34275
34795
|
const chunks = [];
|
|
34276
34796
|
const response = await this.deps.agent.chat({
|
|
@@ -34332,7 +34852,7 @@ function buildControlMetadata(senderId, isOwner) {
|
|
|
34332
34852
|
...isOwner === undefined ? {} : { isOwner }
|
|
34333
34853
|
};
|
|
34334
34854
|
}
|
|
34335
|
-
var CANCEL_DRAIN_TIMEOUT_MS = 5000;
|
|
34855
|
+
var CANCEL_DRAIN_TIMEOUT_MS = 5000, QUEUE_PREVIEW_MAX = 120;
|
|
34336
34856
|
var init_control_service = __esm(() => {
|
|
34337
34857
|
init_channel_scope();
|
|
34338
34858
|
init_native_session_history();
|
|
@@ -34340,7 +34860,7 @@ var init_control_service = __esm(() => {
|
|
|
34340
34860
|
});
|
|
34341
34861
|
|
|
34342
34862
|
// src/control/terminal-service.ts
|
|
34343
|
-
import { randomUUID as
|
|
34863
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
34344
34864
|
import { createRequire as createRequire6 } from "node:module";
|
|
34345
34865
|
import { spawn as spawnPty2 } from "node-pty";
|
|
34346
34866
|
function scrubEnv() {
|
|
@@ -34367,7 +34887,7 @@ function realPtySpawn(file, args, opts) {
|
|
|
34367
34887
|
return spawnPty2(file, args, opts);
|
|
34368
34888
|
}
|
|
34369
34889
|
function createTerminalService(deps) {
|
|
34370
|
-
const
|
|
34890
|
+
const spawn12 = deps.spawn ?? realPtySpawn;
|
|
34371
34891
|
const platform = deps.platform ?? process.platform;
|
|
34372
34892
|
const sessions = new Map;
|
|
34373
34893
|
const setTimer = deps.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
@@ -34392,8 +34912,8 @@ function createTerminalService(deps) {
|
|
|
34392
34912
|
create({ cwd, cols, rows }) {
|
|
34393
34913
|
if (platform === "win32")
|
|
34394
34914
|
throw new Error("terminal-unsupported-platform");
|
|
34395
|
-
const terminalId =
|
|
34396
|
-
const handle =
|
|
34915
|
+
const terminalId = randomUUID4();
|
|
34916
|
+
const handle = spawn12(defaultShell(platform), [], { name: "xterm-256color", cols, rows, cwd, env: scrubEnv() });
|
|
34397
34917
|
const session3 = { handle, seq: 0, idleTimer: null };
|
|
34398
34918
|
sessions.set(terminalId, session3);
|
|
34399
34919
|
handle.onData((data) => {
|
|
@@ -34467,7 +34987,7 @@ var init_terminal_service = __esm(() => {
|
|
|
34467
34987
|
});
|
|
34468
34988
|
|
|
34469
34989
|
// src/control/upload-store.ts
|
|
34470
|
-
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";
|
|
34471
34991
|
import { homedir as homedir13 } from "node:os";
|
|
34472
34992
|
import path16 from "node:path";
|
|
34473
34993
|
function defaultRootDir() {
|
|
@@ -34503,11 +35023,11 @@ class UploadStore {
|
|
|
34503
35023
|
if (bytes.byteLength > this.maxBytes)
|
|
34504
35024
|
throw new Error("file-too-large");
|
|
34505
35025
|
const safeName = sanitizeUploadFilename(filename);
|
|
34506
|
-
const { mkdir:
|
|
34507
|
-
await
|
|
35026
|
+
const { mkdir: mkdir10 } = await import("node:fs/promises");
|
|
35027
|
+
await mkdir10(this.rootDir, { recursive: true });
|
|
34508
35028
|
const dir = await mkdtemp2(path16.join(this.rootDir, "u-"));
|
|
34509
35029
|
const filePath = path16.join(dir, safeName);
|
|
34510
|
-
await
|
|
35030
|
+
await writeFile9(filePath, bytes);
|
|
34511
35031
|
return {
|
|
34512
35032
|
id: path16.basename(dir),
|
|
34513
35033
|
path: filePath,
|
|
@@ -34530,7 +35050,7 @@ class UploadStore {
|
|
|
34530
35050
|
try {
|
|
34531
35051
|
const info = await stat4(dir);
|
|
34532
35052
|
if (info.mtimeMs < cutoff) {
|
|
34533
|
-
await
|
|
35053
|
+
await rm11(dir, { recursive: true, force: true });
|
|
34534
35054
|
removed += 1;
|
|
34535
35055
|
}
|
|
34536
35056
|
} catch {}
|
|
@@ -34588,12 +35108,12 @@ var init_agent_catalog = __esm(() => {
|
|
|
34588
35108
|
|
|
34589
35109
|
// src/config/config-watcher.ts
|
|
34590
35110
|
import { watch } from "node:fs";
|
|
34591
|
-
import { basename as
|
|
35111
|
+
import { basename as basename4, dirname as dirname13 } from "node:path";
|
|
34592
35112
|
function startConfigWatcher(options) {
|
|
34593
35113
|
const { configPath, onChange, debounceMs = 250, logger: logger2 } = options;
|
|
34594
35114
|
const watchFactory = options.watchFactory ?? watch;
|
|
34595
|
-
const dir =
|
|
34596
|
-
const target =
|
|
35115
|
+
const dir = dirname13(configPath);
|
|
35116
|
+
const target = basename4(configPath);
|
|
34597
35117
|
let timer;
|
|
34598
35118
|
let watcher;
|
|
34599
35119
|
const fire = () => {
|
|
@@ -34608,7 +35128,7 @@ function startConfigWatcher(options) {
|
|
|
34608
35128
|
};
|
|
34609
35129
|
try {
|
|
34610
35130
|
watcher = watchFactory(dir, { persistent: false }, (_event, filename) => {
|
|
34611
|
-
if (filename !== null &&
|
|
35131
|
+
if (filename !== null && basename4(filename.toString()) !== target)
|
|
34612
35132
|
return;
|
|
34613
35133
|
if (timer)
|
|
34614
35134
|
clearTimeout(timer);
|
|
@@ -34646,9 +35166,9 @@ __export(exports_main, {
|
|
|
34646
35166
|
main: () => main,
|
|
34647
35167
|
buildApp: () => buildApp
|
|
34648
35168
|
});
|
|
34649
|
-
import { randomUUID as
|
|
35169
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
34650
35170
|
import { homedir as homedir14 } from "node:os";
|
|
34651
|
-
import { dirname as
|
|
35171
|
+
import { dirname as dirname14, join as join23 } from "node:path";
|
|
34652
35172
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
34653
35173
|
function startProgressHeartbeat(orchestration3, config4, logger2, channel) {
|
|
34654
35174
|
const thresholdSeconds = config4.orchestration.progressHeartbeatSeconds;
|
|
@@ -35026,7 +35546,7 @@ async function buildApp(paths, deps = {}) {
|
|
|
35026
35546
|
};
|
|
35027
35547
|
orchestration3 = new OrchestrationService({
|
|
35028
35548
|
now: deps.loggerNow ?? (() => new Date),
|
|
35029
|
-
createId: () =>
|
|
35549
|
+
createId: () => randomUUID5(),
|
|
35030
35550
|
config: config4,
|
|
35031
35551
|
loadState: async () => JSON.parse(JSON.stringify(state)),
|
|
35032
35552
|
saveState: async (nextState) => {
|
|
@@ -35193,7 +35713,8 @@ async function buildApp(paths, deps = {}) {
|
|
|
35193
35713
|
},
|
|
35194
35714
|
uploadStore,
|
|
35195
35715
|
terminal: terminalService,
|
|
35196
|
-
terminalEnabled: () => terminalEnabled(config4)
|
|
35716
|
+
terminalEnabled: () => terminalEnabled(config4),
|
|
35717
|
+
filesWriteEnabled: () => filesWriteEnabled(config4)
|
|
35197
35718
|
});
|
|
35198
35719
|
const workspaceSignature = (cfg) => JSON.stringify(Object.keys(cfg.workspaces).sort().map((name) => {
|
|
35199
35720
|
const ws = cfg.workspaces[name];
|
|
@@ -35346,7 +35867,7 @@ async function main() {
|
|
|
35346
35867
|
}
|
|
35347
35868
|
}
|
|
35348
35869
|
async function prepareChannelMedia(configPath, config4) {
|
|
35349
|
-
const runtimeDir = join23(
|
|
35870
|
+
const runtimeDir = join23(dirname14(configPath), "runtime");
|
|
35350
35871
|
const mediaRootDir = join23(runtimeDir, "media");
|
|
35351
35872
|
const mediaStore = new RuntimeMediaStore({ rootDir: mediaRootDir });
|
|
35352
35873
|
await mediaStore.cleanupExpired().catch((error2) => {
|
|
@@ -35361,7 +35882,7 @@ function resolveRuntimePaths() {
|
|
|
35361
35882
|
throw new Error("Unable to resolve the current user home directory");
|
|
35362
35883
|
}
|
|
35363
35884
|
const configPath = coreEnv("CONFIG") ?? join23(coreHomeDir(home), "config.json");
|
|
35364
|
-
const runtimeDir = join23(
|
|
35885
|
+
const runtimeDir = join23(dirname14(configPath), "runtime");
|
|
35365
35886
|
return {
|
|
35366
35887
|
configPath,
|
|
35367
35888
|
statePath: coreEnv("STATE") ?? join23(coreHomeDir(home), "state.json"),
|
|
@@ -35376,12 +35897,12 @@ function resolveBridgeEntryPath() {
|
|
|
35376
35897
|
return fileURLToPath5(new URL("./bridge/bridge-main.ts", import.meta.url));
|
|
35377
35898
|
}
|
|
35378
35899
|
function resolveAppLogPath(configPath) {
|
|
35379
|
-
const rootDir =
|
|
35900
|
+
const rootDir = dirname14(configPath);
|
|
35380
35901
|
const runtimeDir = join23(rootDir, "runtime");
|
|
35381
35902
|
return join23(runtimeDir, "app.log");
|
|
35382
35903
|
}
|
|
35383
35904
|
function resolvePerfLogPath(configPath) {
|
|
35384
|
-
const rootDir =
|
|
35905
|
+
const rootDir = dirname14(configPath);
|
|
35385
35906
|
const runtimeDir = join23(rootDir, "runtime");
|
|
35386
35907
|
return join23(runtimeDir, "perf.log");
|
|
35387
35908
|
}
|
|
@@ -35439,7 +35960,7 @@ var init_main = __esm(async () => {
|
|
|
35439
35960
|
});
|
|
35440
35961
|
|
|
35441
35962
|
// src/doctor/checks/acpx-check.ts
|
|
35442
|
-
import { spawn as
|
|
35963
|
+
import { spawn as spawn12 } from "node:child_process";
|
|
35443
35964
|
async function checkAcpx(options = {}) {
|
|
35444
35965
|
const runtimePaths = (options.resolveRuntimePaths ?? resolveRuntimePaths)();
|
|
35445
35966
|
try {
|
|
@@ -35486,7 +36007,7 @@ function buildDetails(metadata, version2, verbose) {
|
|
|
35486
36007
|
async function defaultRunVersion(command) {
|
|
35487
36008
|
const spawnSpec = resolveSpawnCommand(command, ["--version"]);
|
|
35488
36009
|
return await new Promise((resolve4, reject) => {
|
|
35489
|
-
const child =
|
|
36010
|
+
const child = spawn12(spawnSpec.command, spawnSpec.args, {
|
|
35490
36011
|
stdio: ["ignore", "pipe", "pipe"]
|
|
35491
36012
|
});
|
|
35492
36013
|
let stdout2 = "";
|
|
@@ -35624,7 +36145,7 @@ var init_config_check = __esm(async () => {
|
|
|
35624
36145
|
});
|
|
35625
36146
|
|
|
35626
36147
|
// src/doctor/checks/daemon-check.ts
|
|
35627
|
-
import { readdir as readdir6, readFile as
|
|
36148
|
+
import { readdir as readdir6, readFile as readFile16, rm as rm12 } from "node:fs/promises";
|
|
35628
36149
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
35629
36150
|
import { homedir as homedir15 } from "node:os";
|
|
35630
36151
|
import { join as join24 } from "node:path";
|
|
@@ -35770,7 +36291,7 @@ async function defaultListConsumerLocks(runtimeDir) {
|
|
|
35770
36291
|
}
|
|
35771
36292
|
async function defaultReadConsumerLock(path17) {
|
|
35772
36293
|
try {
|
|
35773
|
-
const raw = await
|
|
36294
|
+
const raw = await readFile16(path17, "utf8");
|
|
35774
36295
|
const parsed = JSON.parse(raw);
|
|
35775
36296
|
return typeof parsed.pid === "number" ? { pid: parsed.pid } : null;
|
|
35776
36297
|
} catch {
|
|
@@ -35778,7 +36299,7 @@ async function defaultReadConsumerLock(path17) {
|
|
|
35778
36299
|
}
|
|
35779
36300
|
}
|
|
35780
36301
|
async function defaultRemoveConsumerLock(path17) {
|
|
35781
|
-
await
|
|
36302
|
+
await rm12(path17, { force: true });
|
|
35782
36303
|
}
|
|
35783
36304
|
function resolveCliEntryPath() {
|
|
35784
36305
|
return process.argv[1] ?? fileURLToPath6(import.meta.url);
|
|
@@ -35794,7 +36315,7 @@ var init_daemon_check = __esm(() => {
|
|
|
35794
36315
|
|
|
35795
36316
|
// src/doctor/checks/logs-check.ts
|
|
35796
36317
|
import { stat as stat5, readdir as readdir7 } from "node:fs/promises";
|
|
35797
|
-
import { basename as
|
|
36318
|
+
import { basename as basename5, join as join25 } from "node:path";
|
|
35798
36319
|
import { homedir as homedir16 } from "node:os";
|
|
35799
36320
|
async function checkLogs(options = {}) {
|
|
35800
36321
|
const home = options.home ?? process.env.HOME ?? homedir16();
|
|
@@ -35824,7 +36345,7 @@ async function checkLogs(options = {}) {
|
|
|
35824
36345
|
`error: ${formatError6(error2)}`
|
|
35825
36346
|
]);
|
|
35826
36347
|
}
|
|
35827
|
-
const baseNames = [
|
|
36348
|
+
const baseNames = [basename5(paths.appLog), basename5(paths.stdoutLog), basename5(paths.stderrLog)];
|
|
35828
36349
|
const tracked = new Set(baseNames);
|
|
35829
36350
|
const matched = entries.filter((entry) => isTrackedLogName(entry, tracked));
|
|
35830
36351
|
const files = [];
|
|
@@ -36155,7 +36676,7 @@ var init_plugin_check = __esm(async () => {
|
|
|
36155
36676
|
// src/doctor/checks/runtime-check.ts
|
|
36156
36677
|
import { constants } from "node:fs";
|
|
36157
36678
|
import { access as access4, stat as stat6 } from "node:fs/promises";
|
|
36158
|
-
import { dirname as
|
|
36679
|
+
import { dirname as dirname15 } from "node:path";
|
|
36159
36680
|
import { homedir as homedir18 } from "node:os";
|
|
36160
36681
|
async function checkRuntime(options = {}) {
|
|
36161
36682
|
const home = options.home ?? process.env.HOME ?? homedir18();
|
|
@@ -36311,7 +36832,7 @@ async function checkFileCreatable(label, path17, probe, platform) {
|
|
|
36311
36832
|
detail: `${label}: ${path17} (unusable: ${formatError9(error2)})`
|
|
36312
36833
|
};
|
|
36313
36834
|
}
|
|
36314
|
-
const parentCheck = await checkCreatableAncestorDirectory(
|
|
36835
|
+
const parentCheck = await checkCreatableAncestorDirectory(dirname15(path17), probe, platform);
|
|
36315
36836
|
if (!parentCheck.ok) {
|
|
36316
36837
|
return {
|
|
36317
36838
|
ok: false,
|
|
@@ -36347,7 +36868,7 @@ async function checkCreatableAncestorDirectory(path17, probe, platform) {
|
|
|
36347
36868
|
blockingPath: path17
|
|
36348
36869
|
};
|
|
36349
36870
|
}
|
|
36350
|
-
const parent =
|
|
36871
|
+
const parent = dirname15(path17);
|
|
36351
36872
|
if (parent === path17) {
|
|
36352
36873
|
return {
|
|
36353
36874
|
ok: false,
|
|
@@ -37130,9 +37651,9 @@ var init_doctor2 = __esm(async () => {
|
|
|
37130
37651
|
|
|
37131
37652
|
// src/cli.ts
|
|
37132
37653
|
init_core_home();
|
|
37133
|
-
import { randomUUID as
|
|
37654
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
37134
37655
|
import { homedir as homedir20 } from "node:os";
|
|
37135
|
-
import { dirname as
|
|
37656
|
+
import { dirname as dirname16, join as join27, sep as sep2 } from "node:path";
|
|
37136
37657
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
37137
37658
|
|
|
37138
37659
|
// src/runtime/migrate-core-home.ts
|
|
@@ -52806,7 +53327,7 @@ async function prepareMcpCoordinatorStartup(input) {
|
|
|
52806
53327
|
return { kind: "external-coordinator" };
|
|
52807
53328
|
}
|
|
52808
53329
|
function createMcpStdioIdentityResolver(input) {
|
|
52809
|
-
const instanceId =
|
|
53330
|
+
const instanceId = randomUUID6().slice(0, 8);
|
|
52810
53331
|
return async (context) => {
|
|
52811
53332
|
const parsedCoordinatorSession = input.parsedCoordinatorSession?.trim() || null;
|
|
52812
53333
|
const workspace3 = input.workspace?.trim() || null;
|
|
@@ -53761,7 +54282,7 @@ function safeDaemonLogPaths() {
|
|
|
53761
54282
|
const configPath = resolveConfigPathForCurrentEnv();
|
|
53762
54283
|
const paths = resolveDaemonPathsForCurrentConfig();
|
|
53763
54284
|
return {
|
|
53764
|
-
appLog: join27(
|
|
54285
|
+
appLog: join27(dirname16(configPath), "runtime", "app.log"),
|
|
53765
54286
|
stderrLog: paths.stderrLog
|
|
53766
54287
|
};
|
|
53767
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;
|
|
@@ -6,6 +6,13 @@ export interface ScheduledOrigin {
|
|
|
6
6
|
taskId: string;
|
|
7
7
|
executeAt: string;
|
|
8
8
|
}
|
|
9
|
+
/** A single pending item in the per-session server-side prompt queue, as surfaced on
|
|
10
|
+
* the wire by `queue-updated`. `textPreview` is truncated server-side (~120 chars). */
|
|
11
|
+
export interface QueuedItemInfo {
|
|
12
|
+
id: string;
|
|
13
|
+
textPreview: string;
|
|
14
|
+
enqueuedAt: string;
|
|
15
|
+
}
|
|
9
16
|
export type ControlEvent = {
|
|
10
17
|
type: "turn-output";
|
|
11
18
|
chatKey: string;
|
|
@@ -17,6 +24,12 @@ export type ControlEvent = {
|
|
|
17
24
|
sessionAlias: string;
|
|
18
25
|
prompt?: string;
|
|
19
26
|
scheduled?: ScheduledOrigin;
|
|
27
|
+
queueItemId?: string;
|
|
28
|
+
} | {
|
|
29
|
+
type: "queue-updated";
|
|
30
|
+
chatKey: string;
|
|
31
|
+
sessionAlias: string;
|
|
32
|
+
items: QueuedItemInfo[];
|
|
20
33
|
} | {
|
|
21
34
|
type: "tool-event";
|
|
22
35
|
chatKey: string;
|
|
@@ -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;
|
|
@@ -93,6 +94,23 @@ export interface ControlPromptResult {
|
|
|
93
94
|
ok: boolean;
|
|
94
95
|
text?: string;
|
|
95
96
|
errorMessage?: string;
|
|
97
|
+
/** True when this prompt did not run immediately and was instead appended to the
|
|
98
|
+
* per-session server-side queue (a turn was already in flight). */
|
|
99
|
+
queued?: boolean;
|
|
100
|
+
/** Id of the queued item, present only when `queued` is true. Used to cancel it via
|
|
101
|
+
* `cancelQueuedItem` before it drains. */
|
|
102
|
+
queueItemId?: string;
|
|
103
|
+
}
|
|
104
|
+
/** A prompt held in the per-session server-side queue while a turn is in flight.
|
|
105
|
+
* Runs through the same `executeTurn` machinery as a manual prompt once drained. */
|
|
106
|
+
export interface QueuedPrompt {
|
|
107
|
+
id: string;
|
|
108
|
+
text: string;
|
|
109
|
+
enqueuedAt: string;
|
|
110
|
+
senderId: string;
|
|
111
|
+
isOwner?: boolean;
|
|
112
|
+
accountId?: string;
|
|
113
|
+
media?: PromptAttachmentRef[];
|
|
96
114
|
}
|
|
97
115
|
/** A turn started by a fired scheduled task. Runs through the same agent + turn-event
|
|
98
116
|
* machinery as a normal prompt, so it streams live and persists to history — but it
|
|
@@ -121,7 +139,25 @@ export declare class ControlService {
|
|
|
121
139
|
listDirectory(workspace: string, path?: string): Promise<DirListing>;
|
|
122
140
|
readWorkspaceFile(workspace: string, path: string): Promise<FileContent>;
|
|
123
141
|
workspaceGitDiff(workspace: string, path?: string): Promise<WorkspaceDiff>;
|
|
124
|
-
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
|
+
}>;
|
|
125
161
|
uploadFile(input: {
|
|
126
162
|
filename: string;
|
|
127
163
|
content: string;
|
|
@@ -173,6 +209,9 @@ export declare class ControlService {
|
|
|
173
209
|
getOrchestrationTask(taskId: string): Promise<OrchestrationTaskRecord | null>;
|
|
174
210
|
cancelOrchestrationTask(input: CancelTaskInput): Promise<OrchestrationTaskRecord>;
|
|
175
211
|
private readonly inFlight;
|
|
212
|
+
private readonly queues;
|
|
213
|
+
private readonly draining;
|
|
214
|
+
private emitQueueUpdated;
|
|
176
215
|
prompt(input: ControlPromptInput): Promise<ControlPromptResult>;
|
|
177
216
|
/** Run a fired scheduled task as a real turn through the same machinery as a manual
|
|
178
217
|
* prompt — so it streams live and persists to history — while tagging turn-started
|
|
@@ -180,7 +219,15 @@ export declare class ControlService {
|
|
|
180
219
|
* the web can badge it. Owner-authorized: the task was owner-gated at creation. */
|
|
181
220
|
runScheduledTurn(input: ControlScheduledTurnInput): Promise<ControlPromptResult>;
|
|
182
221
|
private executeTurn;
|
|
222
|
+
private advanceQueue;
|
|
183
223
|
cancelTurn(chatKey: string, sessionAlias: string): boolean;
|
|
224
|
+
/** Remove a pending queued prompt (by id) before it drains. No-ops (returns
|
|
225
|
+
* `{ cancelled: false }`) when the queue or the id is absent/already drained —
|
|
226
|
+
* e.g. a race where the item drained into a running turn just before the cancel
|
|
227
|
+
* arrived. Does NOT touch a turn that is already running (use `cancelTurn`). */
|
|
228
|
+
cancelQueuedItem(chatKey: string, sessionAlias: string, itemId: string): {
|
|
229
|
+
cancelled: boolean;
|
|
230
|
+
};
|
|
184
231
|
executeCommand(input: ControlExecuteCommandInput): Promise<string>;
|
|
185
232
|
/** Open an interactive terminal in the session's workspace cwd. Rejected when terminal is disabled. */
|
|
186
233
|
createTerminal(chatKey: string, sessionAlias: string, cols: number, rows: number): Promise<{
|
|
@@ -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;
|