@mtreeai/msapling-cli 2.3.6-beta.46 → 2.3.6-beta.47
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/index.js +1422 -402
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2194,7 +2194,7 @@ var init_RunCommandTool = __esm({
|
|
|
2194
2194
|
this.activeCommands++;
|
|
2195
2195
|
return;
|
|
2196
2196
|
}
|
|
2197
|
-
return new Promise((
|
|
2197
|
+
return new Promise((resolve21) => this.queue.push(resolve21));
|
|
2198
2198
|
}
|
|
2199
2199
|
static releaseLock() {
|
|
2200
2200
|
if (this.queue.length > 0) {
|
|
@@ -2274,9 +2274,9 @@ var init_RunCommandTool = __esm({
|
|
|
2274
2274
|
const chunks = { stdout: [], stderr: [] };
|
|
2275
2275
|
proc.stdout?.on("data", (chunk) => chunks.stdout.push(chunk));
|
|
2276
2276
|
proc.stderr?.on("data", (chunk) => chunks.stderr.push(chunk));
|
|
2277
|
-
const exitCode = await new Promise((
|
|
2278
|
-
proc.on("exit", (code) =>
|
|
2279
|
-
proc.on("error", () =>
|
|
2277
|
+
const exitCode = await new Promise((resolve21) => {
|
|
2278
|
+
proc.on("exit", (code) => resolve21(code ?? 1));
|
|
2279
|
+
proc.on("error", () => resolve21(1));
|
|
2280
2280
|
});
|
|
2281
2281
|
const stdout = Buffer.concat(chunks.stdout).toString("utf-8");
|
|
2282
2282
|
const stderr = Buffer.concat(chunks.stderr).toString("utf-8");
|
|
@@ -2434,7 +2434,7 @@ var init_src2 = __esm({
|
|
|
2434
2434
|
const message = `Content-Length: ${Buffer.byteLength(content, "utf8")}\r
|
|
2435
2435
|
\r
|
|
2436
2436
|
${content}`;
|
|
2437
|
-
return new Promise((
|
|
2437
|
+
return new Promise((resolve21, reject) => {
|
|
2438
2438
|
const timeoutHandle = setTimeout(() => {
|
|
2439
2439
|
this.pendingRequests.delete(id);
|
|
2440
2440
|
reject(new Error(`LSP request timeout after ${timeoutMs}ms: ${method}`));
|
|
@@ -2442,7 +2442,7 @@ ${content}`;
|
|
|
2442
2442
|
this.pendingRequests.set(id, {
|
|
2443
2443
|
resolve: (response) => {
|
|
2444
2444
|
clearTimeout(timeoutHandle);
|
|
2445
|
-
|
|
2445
|
+
resolve21(response);
|
|
2446
2446
|
},
|
|
2447
2447
|
reject: (error) => {
|
|
2448
2448
|
clearTimeout(timeoutHandle);
|
|
@@ -2595,9 +2595,9 @@ var init_SubShellTool = __esm({
|
|
|
2595
2595
|
}
|
|
2596
2596
|
throw e;
|
|
2597
2597
|
}
|
|
2598
|
-
await new Promise((
|
|
2599
|
-
proc.on("exit", () =>
|
|
2600
|
-
proc.on("error", () =>
|
|
2598
|
+
await new Promise((resolve21) => {
|
|
2599
|
+
proc.on("exit", () => resolve21());
|
|
2600
|
+
proc.on("error", () => resolve21());
|
|
2601
2601
|
});
|
|
2602
2602
|
return { content: `Successfully launched separate window for ${args2.worker_id}` };
|
|
2603
2603
|
}
|
|
@@ -2676,27 +2676,42 @@ async function* walkFiles(root, current = root) {
|
|
|
2676
2676
|
}
|
|
2677
2677
|
}
|
|
2678
2678
|
}
|
|
2679
|
+
function rgCandidates() {
|
|
2680
|
+
if (process.platform === "win32") {
|
|
2681
|
+
return [
|
|
2682
|
+
"rg.exe",
|
|
2683
|
+
"rg",
|
|
2684
|
+
"C:\\Program Files\\ripgrep\\rg.exe",
|
|
2685
|
+
"C:\\ProgramData\\chocolatey\\bin\\rg.exe"
|
|
2686
|
+
];
|
|
2687
|
+
}
|
|
2688
|
+
return ["rg", "/usr/bin/rg", "/usr/local/bin/rg", "/opt/homebrew/bin/rg"];
|
|
2689
|
+
}
|
|
2679
2690
|
async function findRg() {
|
|
2680
|
-
|
|
2681
|
-
for (const bin of
|
|
2691
|
+
if (_rgCache.resolved) return _rgCache.value;
|
|
2692
|
+
for (const bin of rgCandidates()) {
|
|
2682
2693
|
try {
|
|
2683
|
-
const exited = await new Promise((
|
|
2694
|
+
const exited = await new Promise((resolve21) => {
|
|
2684
2695
|
try {
|
|
2685
2696
|
const p = spawn4(bin, ["--version"], { stdio: ["ignore", "pipe", "pipe"] });
|
|
2686
|
-
p.on("error", () =>
|
|
2687
|
-
p.on("exit", (code) =>
|
|
2697
|
+
p.on("error", () => resolve21(null));
|
|
2698
|
+
p.on("exit", (code) => resolve21(code));
|
|
2688
2699
|
} catch {
|
|
2689
|
-
|
|
2700
|
+
resolve21(null);
|
|
2690
2701
|
}
|
|
2691
2702
|
});
|
|
2692
|
-
if (exited === 0)
|
|
2703
|
+
if (exited === 0) {
|
|
2704
|
+
_rgCache = { resolved: true, value: bin };
|
|
2705
|
+
return bin;
|
|
2706
|
+
}
|
|
2693
2707
|
} catch {
|
|
2694
2708
|
}
|
|
2695
2709
|
}
|
|
2710
|
+
_rgCache = { resolved: true, value: null };
|
|
2696
2711
|
return null;
|
|
2697
2712
|
}
|
|
2698
2713
|
function runRg(bin, args2) {
|
|
2699
|
-
return new Promise((
|
|
2714
|
+
return new Promise((resolve21) => {
|
|
2700
2715
|
const p = spawn4(bin, args2, { stdio: ["ignore", "pipe", "pipe"] });
|
|
2701
2716
|
let stdout = "";
|
|
2702
2717
|
let stderr = "";
|
|
@@ -2707,17 +2722,24 @@ function runRg(bin, args2) {
|
|
|
2707
2722
|
stderr += d.toString("utf8");
|
|
2708
2723
|
});
|
|
2709
2724
|
p.on("error", (e) => {
|
|
2710
|
-
|
|
2725
|
+
resolve21({ stdout, stderr: stderr + (e?.message ?? ""), exitCode: -1 });
|
|
2711
2726
|
});
|
|
2712
2727
|
p.on("exit", (code) => {
|
|
2713
|
-
|
|
2728
|
+
resolve21({ stdout, stderr, exitCode: code });
|
|
2714
2729
|
});
|
|
2715
2730
|
});
|
|
2716
2731
|
}
|
|
2717
|
-
async function nodeGrepFallback(pattern, searchRoot, maxMatches, caseSensitive) {
|
|
2732
|
+
async function nodeGrepFallback(pattern, searchRoot, maxMatches, caseSensitive, include, exclude) {
|
|
2718
2733
|
const regex = new RegExp(pattern, caseSensitive ? "" : "i");
|
|
2719
2734
|
const matches2 = [];
|
|
2735
|
+
const relPaths = [];
|
|
2720
2736
|
for await (const relPath of walkFiles(searchRoot)) {
|
|
2737
|
+
if (include && !include.test(relPath)) continue;
|
|
2738
|
+
if (exclude && exclude.test(relPath)) continue;
|
|
2739
|
+
relPaths.push(relPath);
|
|
2740
|
+
}
|
|
2741
|
+
relPaths.sort();
|
|
2742
|
+
for (const relPath of relPaths) {
|
|
2721
2743
|
if (matches2.length >= maxMatches) break;
|
|
2722
2744
|
const fullPath = join6(searchRoot, relPath);
|
|
2723
2745
|
try {
|
|
@@ -2737,7 +2759,7 @@ async function nodeGrepFallback(pattern, searchRoot, maxMatches, caseSensitive)
|
|
|
2737
2759
|
}
|
|
2738
2760
|
return matches2;
|
|
2739
2761
|
}
|
|
2740
|
-
var MAX_GLOB_RESULTS, MAX_GREP_MATCHES, SKIP_DIRS, GlobFilesTool, GrepSearchTool;
|
|
2762
|
+
var MAX_GLOB_RESULTS, MAX_GREP_MATCHES, SKIP_DIRS, GlobFilesTool, _rgCache, GrepSearchTool;
|
|
2741
2763
|
var init_SearchTools = __esm({
|
|
2742
2764
|
"../core/src/tools/SearchTools.ts"() {
|
|
2743
2765
|
"use strict";
|
|
@@ -2825,9 +2847,13 @@ var init_SearchTools = __esm({
|
|
|
2825
2847
|
}
|
|
2826
2848
|
}
|
|
2827
2849
|
};
|
|
2850
|
+
_rgCache = {
|
|
2851
|
+
resolved: false,
|
|
2852
|
+
value: null
|
|
2853
|
+
};
|
|
2828
2854
|
GrepSearchTool = class extends BaseTool {
|
|
2829
2855
|
name = "grep_search";
|
|
2830
|
-
description =
|
|
2856
|
+
description = 'Search file contents for a regex pattern. Returns matching lines with file path and line number, sorted deterministically. Uses ripgrep (rg) when available \u2014 fast on large repos, respects .gitignore \u2014 and falls back to a built-in scanner otherwise. Optional include/exclude glob filters narrow which files are searched (e.g. include "*.ts", exclude "*.test.ts").';
|
|
2831
2857
|
parameters = {
|
|
2832
2858
|
type: "object",
|
|
2833
2859
|
required: ["pattern"],
|
|
@@ -2843,6 +2869,14 @@ var init_SearchTools = __esm({
|
|
|
2843
2869
|
case_sensitive: {
|
|
2844
2870
|
type: "boolean",
|
|
2845
2871
|
description: "Whether the search is case-sensitive (default: false)."
|
|
2872
|
+
},
|
|
2873
|
+
include: {
|
|
2874
|
+
type: "string",
|
|
2875
|
+
description: 'Optional glob of files to include (e.g. "*.ts", "src/**/*.tsx"). Passed to ripgrep as --glob.'
|
|
2876
|
+
},
|
|
2877
|
+
exclude: {
|
|
2878
|
+
type: "string",
|
|
2879
|
+
description: 'Optional glob of files to exclude (e.g. "*.test.ts", "dist/**"). Passed to ripgrep as a negated --glob.'
|
|
2846
2880
|
}
|
|
2847
2881
|
}
|
|
2848
2882
|
};
|
|
@@ -2870,6 +2904,8 @@ var init_SearchTools = __esm({
|
|
|
2870
2904
|
};
|
|
2871
2905
|
}
|
|
2872
2906
|
const caseSensitive = args2?.case_sensitive === true;
|
|
2907
|
+
const include = typeof args2?.include === "string" && args2.include.trim() ? args2.include.trim() : void 0;
|
|
2908
|
+
const exclude = typeof args2?.exclude === "string" && args2.exclude.trim() ? args2.exclude.trim() : void 0;
|
|
2873
2909
|
const rg = await findRg();
|
|
2874
2910
|
if (rg) {
|
|
2875
2911
|
try {
|
|
@@ -2877,9 +2913,14 @@ var init_SearchTools = __esm({
|
|
|
2877
2913
|
"--line-number",
|
|
2878
2914
|
`--max-count=${MAX_GREP_MATCHES}`,
|
|
2879
2915
|
"--no-heading",
|
|
2880
|
-
"--color=never"
|
|
2916
|
+
"--color=never",
|
|
2917
|
+
// Deterministic output: sort by file path so repeated queries return
|
|
2918
|
+
// identical ordering (ripgrep parallelises by default → non-stable).
|
|
2919
|
+
"--sort=path"
|
|
2881
2920
|
];
|
|
2882
2921
|
if (!caseSensitive) rgArgs.push("--ignore-case");
|
|
2922
|
+
if (include) rgArgs.push("--glob", include);
|
|
2923
|
+
if (exclude) rgArgs.push("--glob", `!${exclude}`);
|
|
2883
2924
|
rgArgs.push("--", pattern, searchPath);
|
|
2884
2925
|
const { stdout, stderr, exitCode } = await runRg(rg, rgArgs);
|
|
2885
2926
|
if (exitCode !== 0 && exitCode !== 1) {
|
|
@@ -2899,15 +2940,28 @@ var init_SearchTools = __esm({
|
|
|
2899
2940
|
}
|
|
2900
2941
|
try {
|
|
2901
2942
|
const isFile = statSync(searchPath).isFile();
|
|
2943
|
+
const includeRe = include ? globToRegExp(include) : void 0;
|
|
2944
|
+
const excludeRe = exclude ? globToRegExp(exclude) : void 0;
|
|
2902
2945
|
let matches2;
|
|
2903
2946
|
if (isFile) {
|
|
2904
|
-
const content = await readFile4(searchPath, "utf8");
|
|
2905
|
-
const regex = new RegExp(pattern, caseSensitive ? "" : "i");
|
|
2906
|
-
const lines = content.split("\n");
|
|
2907
2947
|
const relPath = args2?.path ?? searchPath;
|
|
2908
|
-
|
|
2948
|
+
if (includeRe && !includeRe.test(String(relPath)) || excludeRe && excludeRe.test(String(relPath))) {
|
|
2949
|
+
matches2 = [];
|
|
2950
|
+
} else {
|
|
2951
|
+
const content = await readFile4(searchPath, "utf8");
|
|
2952
|
+
const regex = new RegExp(pattern, caseSensitive ? "" : "i");
|
|
2953
|
+
const lines = content.split("\n");
|
|
2954
|
+
matches2 = lines.flatMap((line, idx) => regex.test(line) ? [`${relPath}:${idx + 1}:${line}`] : []).slice(0, MAX_GREP_MATCHES);
|
|
2955
|
+
}
|
|
2909
2956
|
} else {
|
|
2910
|
-
matches2 = await nodeGrepFallback(
|
|
2957
|
+
matches2 = await nodeGrepFallback(
|
|
2958
|
+
pattern,
|
|
2959
|
+
searchPath,
|
|
2960
|
+
MAX_GREP_MATCHES,
|
|
2961
|
+
caseSensitive,
|
|
2962
|
+
includeRe,
|
|
2963
|
+
excludeRe
|
|
2964
|
+
);
|
|
2911
2965
|
}
|
|
2912
2966
|
if (matches2.length === 0) return { content: "No matches found." };
|
|
2913
2967
|
const suffix = matches2.length >= MAX_GREP_MATCHES ? `
|
|
@@ -3400,13 +3454,164 @@ Pre-edit content backed up to: ${backedUpTo}`;
|
|
|
3400
3454
|
}
|
|
3401
3455
|
});
|
|
3402
3456
|
|
|
3457
|
+
// ../core/src/agent/namedAgents.ts
|
|
3458
|
+
import { readdirSync as readdirSync2, statSync as statSync3, readFileSync as readFileSync2 } from "fs";
|
|
3459
|
+
import { join as join9 } from "path";
|
|
3460
|
+
function normalizeToolName(raw) {
|
|
3461
|
+
const trimmed = raw.trim();
|
|
3462
|
+
if (!trimmed) return "";
|
|
3463
|
+
const lower = trimmed.toLowerCase();
|
|
3464
|
+
const key = lower.replace(/[^a-z0-9]/g, "");
|
|
3465
|
+
if (TOOL_NAME_ALIASES[key]) return TOOL_NAME_ALIASES[key];
|
|
3466
|
+
return lower;
|
|
3467
|
+
}
|
|
3468
|
+
function discoverAgentFiles(cwd, homeDir) {
|
|
3469
|
+
const seen = /* @__PURE__ */ new Map();
|
|
3470
|
+
for (const scope of [
|
|
3471
|
+
{ dir: join9(homeDir, ".msapling", "agents"), scope: "user" },
|
|
3472
|
+
{ dir: join9(cwd, ".msapling", "agents"), scope: "project" }
|
|
3473
|
+
]) {
|
|
3474
|
+
const entries = safeReaddir(scope.dir);
|
|
3475
|
+
for (const fname of entries) {
|
|
3476
|
+
if (!fname.toLowerCase().endsWith(".md")) continue;
|
|
3477
|
+
const path2 = join9(scope.dir, fname);
|
|
3478
|
+
if (!isRegularFile(path2)) continue;
|
|
3479
|
+
const base = fname.slice(0, -".md".length);
|
|
3480
|
+
seen.set(base, { name: base, path: path2, scope: scope.scope });
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
return [...seen.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
3484
|
+
}
|
|
3485
|
+
function safeReaddir(dir) {
|
|
3486
|
+
try {
|
|
3487
|
+
return readdirSync2(dir);
|
|
3488
|
+
} catch {
|
|
3489
|
+
return [];
|
|
3490
|
+
}
|
|
3491
|
+
}
|
|
3492
|
+
function isRegularFile(p) {
|
|
3493
|
+
try {
|
|
3494
|
+
return statSync3(p).isFile();
|
|
3495
|
+
} catch {
|
|
3496
|
+
return false;
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
function parseToolsValue(raw) {
|
|
3500
|
+
if (raw === void 0) return void 0;
|
|
3501
|
+
let s = raw.trim();
|
|
3502
|
+
if (s === "") return void 0;
|
|
3503
|
+
if (s === "*" || s.toLowerCase() === "all") return void 0;
|
|
3504
|
+
if (s.startsWith("[") && s.endsWith("]")) s = s.slice(1, -1);
|
|
3505
|
+
const parts = s.split(",").map((t) => t.trim().replace(/^['"]|['"]$/g, "")).map((t) => normalizeToolName(t)).filter(Boolean);
|
|
3506
|
+
return [...new Set(parts)];
|
|
3507
|
+
}
|
|
3508
|
+
function parseAgentFile(text, fallbackName) {
|
|
3509
|
+
const fm = text.match(/^---\s*\n([\s\S]*?)\n---\s*\n?/);
|
|
3510
|
+
if (!fm) {
|
|
3511
|
+
const body = text.trim();
|
|
3512
|
+
const firstLine = body.split(/\r?\n/).find((l) => l.trim()) ?? "";
|
|
3513
|
+
return {
|
|
3514
|
+
name: fallbackName.toLowerCase(),
|
|
3515
|
+
description: firstLine.replace(/^#\s+/, "").trim() || `(agent: ${fallbackName})`,
|
|
3516
|
+
systemPrompt: body
|
|
3517
|
+
};
|
|
3518
|
+
}
|
|
3519
|
+
const meta = fm[1];
|
|
3520
|
+
const systemPrompt = text.slice(fm[0].length).trim();
|
|
3521
|
+
const scalar = (key) => {
|
|
3522
|
+
const m = meta.match(new RegExp(`^${key}[ \\t]*:[ \\t]*(.+)$`, "m"));
|
|
3523
|
+
return m ? m[1].trim().replace(/^['"]|['"]$/g, "") : void 0;
|
|
3524
|
+
};
|
|
3525
|
+
const name = (scalar("name") ?? fallbackName).toLowerCase();
|
|
3526
|
+
const description = scalar("description") ?? `(agent: ${name})`;
|
|
3527
|
+
const model = scalar("model");
|
|
3528
|
+
let tools;
|
|
3529
|
+
const inlineTools = scalar("tools");
|
|
3530
|
+
const blockMatch = meta.match(/^tools[ \t]*:[ \t]*\n((?:[ \t]*-[ \t]*.+\n?)+)/m);
|
|
3531
|
+
if (blockMatch && (inlineTools === void 0 || inlineTools === "")) {
|
|
3532
|
+
const items = blockMatch[1].split(/\r?\n/).map((l) => l.replace(/^[ \t]*-[ \t]*/, "").trim().replace(/^['"]|['"]$/g, "")).map((t) => normalizeToolName(t)).filter(Boolean);
|
|
3533
|
+
tools = [...new Set(items)];
|
|
3534
|
+
} else {
|
|
3535
|
+
tools = parseToolsValue(inlineTools);
|
|
3536
|
+
}
|
|
3537
|
+
return { name, description, model, tools, systemPrompt };
|
|
3538
|
+
}
|
|
3539
|
+
function loadNamedAgents(cwd, homeDir) {
|
|
3540
|
+
const out = [];
|
|
3541
|
+
for (const file of discoverAgentFiles(cwd, homeDir)) {
|
|
3542
|
+
let text;
|
|
3543
|
+
try {
|
|
3544
|
+
text = readFileSync2(file.path, "utf8");
|
|
3545
|
+
} catch {
|
|
3546
|
+
continue;
|
|
3547
|
+
}
|
|
3548
|
+
const parsed = parseAgentFile(text, file.name);
|
|
3549
|
+
out.push({
|
|
3550
|
+
name: parsed.name,
|
|
3551
|
+
description: parsed.description,
|
|
3552
|
+
model: parsed.model,
|
|
3553
|
+
tools: parsed.tools,
|
|
3554
|
+
systemPrompt: parsed.systemPrompt,
|
|
3555
|
+
path: file.path,
|
|
3556
|
+
scope: file.scope
|
|
3557
|
+
});
|
|
3558
|
+
}
|
|
3559
|
+
const byName = /* @__PURE__ */ new Map();
|
|
3560
|
+
for (const a of out) {
|
|
3561
|
+
const prev = byName.get(a.name);
|
|
3562
|
+
if (!prev || prev.scope === "user" && a.scope === "project") {
|
|
3563
|
+
byName.set(a.name, a);
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
3567
|
+
}
|
|
3568
|
+
function findNamedAgent(name, cwd, homeDir) {
|
|
3569
|
+
const target = name.trim().toLowerCase();
|
|
3570
|
+
return loadNamedAgents(cwd, homeDir).find((a) => a.name === target) ?? null;
|
|
3571
|
+
}
|
|
3572
|
+
var TOOL_NAME_ALIASES;
|
|
3573
|
+
var init_namedAgents = __esm({
|
|
3574
|
+
"../core/src/agent/namedAgents.ts"() {
|
|
3575
|
+
"use strict";
|
|
3576
|
+
init_esm_shims();
|
|
3577
|
+
TOOL_NAME_ALIASES = {
|
|
3578
|
+
read: "read_file",
|
|
3579
|
+
write: "write_file",
|
|
3580
|
+
edit: "edit_file",
|
|
3581
|
+
multiedit: "multi_edit_file",
|
|
3582
|
+
patch: "patch_file",
|
|
3583
|
+
move: "move_file",
|
|
3584
|
+
delete: "delete_file",
|
|
3585
|
+
bash: "bash_command",
|
|
3586
|
+
shell: "bash_command",
|
|
3587
|
+
run: "run_command",
|
|
3588
|
+
glob: "glob_files",
|
|
3589
|
+
grep: "grep_search",
|
|
3590
|
+
ls: "list_directory",
|
|
3591
|
+
list: "list_directory",
|
|
3592
|
+
webfetch: "web_fetch",
|
|
3593
|
+
websearch: "web_search",
|
|
3594
|
+
fetch: "web_fetch",
|
|
3595
|
+
search: "web_search",
|
|
3596
|
+
task: "dispatch_agent",
|
|
3597
|
+
dispatch: "dispatch_agent",
|
|
3598
|
+
todowrite: "todo_write",
|
|
3599
|
+
todoread: "todo_read",
|
|
3600
|
+
notebookread: "notebook_read",
|
|
3601
|
+
notebookedit: "notebook_edit_cell"
|
|
3602
|
+
};
|
|
3603
|
+
}
|
|
3604
|
+
});
|
|
3605
|
+
|
|
3403
3606
|
// ../core/src/tools/DispatchAgentTool.ts
|
|
3607
|
+
import { homedir as homedir5 } from "os";
|
|
3404
3608
|
var SUB_AGENT_TIMEOUT_MS, MAX_PROMPT_CHARS, MAX_RESPONSE_CHARS, DEFAULT_SUB_AGENT_MODEL, DispatchAgentTool;
|
|
3405
3609
|
var init_DispatchAgentTool = __esm({
|
|
3406
3610
|
"../core/src/tools/DispatchAgentTool.ts"() {
|
|
3407
3611
|
"use strict";
|
|
3408
3612
|
init_esm_shims();
|
|
3409
3613
|
init_BaseTool();
|
|
3614
|
+
init_namedAgents();
|
|
3410
3615
|
SUB_AGENT_TIMEOUT_MS = 12e4;
|
|
3411
3616
|
MAX_PROMPT_CHARS = 8e3;
|
|
3412
3617
|
MAX_RESPONSE_CHARS = 16e3;
|
|
@@ -3422,6 +3627,10 @@ var init_DispatchAgentTool = __esm({
|
|
|
3422
3627
|
type: "string",
|
|
3423
3628
|
description: `The full self-contained prompt for the sub-agent. Include all necessary context \u2014 the sub-agent does not have access to prior conversation history. Maximum ${MAX_PROMPT_CHARS} characters.`
|
|
3424
3629
|
},
|
|
3630
|
+
agent: {
|
|
3631
|
+
type: "string",
|
|
3632
|
+
description: "Optional NAMED subagent to run (defined in .msapling/agents/<name>.md). The named agent supplies its own system prompt, model, and tool allowlist \u2014 unlike the anonymous dispatch path it CAN run tools (gated by the permission system). Run /agents to list available named agents. When omitted, an anonymous read/reasoning sub-agent runs."
|
|
3633
|
+
},
|
|
3425
3634
|
description: {
|
|
3426
3635
|
type: "string",
|
|
3427
3636
|
description: 'Short human-readable label for this sub-task (e.g. "Summarise auth.ts"). Appears in the session log alongside the result.'
|
|
@@ -3440,12 +3649,19 @@ var init_DispatchAgentTool = __esm({
|
|
|
3440
3649
|
parentChatId;
|
|
3441
3650
|
projectRoot;
|
|
3442
3651
|
onSubagentStop;
|
|
3652
|
+
runNamedAgent;
|
|
3443
3653
|
constructor(options) {
|
|
3444
3654
|
super();
|
|
3445
3655
|
this.client = options.client;
|
|
3446
3656
|
this.parentChatId = options.parentChatId ?? void 0;
|
|
3447
3657
|
this.projectRoot = options.projectRoot;
|
|
3448
3658
|
this.onSubagentStop = options.onSubagentStop;
|
|
3659
|
+
this.runNamedAgent = options.runNamedAgent;
|
|
3660
|
+
}
|
|
3661
|
+
/** Resolve the user home dir, preferring env overrides so test isolation
|
|
3662
|
+
* (HOME / USERPROFILE) is honored exactly like outputStyle.ts. */
|
|
3663
|
+
resolveHome() {
|
|
3664
|
+
return process.env.HOME || process.env.USERPROFILE || homedir5();
|
|
3449
3665
|
}
|
|
3450
3666
|
/** Fire the subagent-stop callback defensively — it must never throw. */
|
|
3451
3667
|
signalStop(info) {
|
|
@@ -3462,9 +3678,19 @@ var init_DispatchAgentTool = __esm({
|
|
|
3462
3678
|
};
|
|
3463
3679
|
}
|
|
3464
3680
|
const prompt4 = args2.prompt.trim().slice(0, MAX_PROMPT_CHARS);
|
|
3465
|
-
const model = typeof args2.model === "string" && args2.model.trim() ? args2.model.trim() : DEFAULT_SUB_AGENT_MODEL;
|
|
3466
3681
|
const chatId = typeof args2.chat_id === "string" && args2.chat_id.trim() ? args2.chat_id.trim() : this.parentChatId;
|
|
3467
|
-
|
|
3682
|
+
let named = null;
|
|
3683
|
+
if (typeof args2.agent === "string" && args2.agent.trim()) {
|
|
3684
|
+
named = findNamedAgent(args2.agent.trim(), this.projectRoot, this.resolveHome());
|
|
3685
|
+
if (!named) {
|
|
3686
|
+
return {
|
|
3687
|
+
content: `dispatch_agent error: no named agent "${args2.agent.trim()}" found in .msapling/agents/. Run /agents to list available agents.`,
|
|
3688
|
+
isError: true
|
|
3689
|
+
};
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
const model = typeof args2.model === "string" && args2.model.trim() ? args2.model.trim() : named?.model && named.model.trim() ? named.model.trim() : DEFAULT_SUB_AGENT_MODEL;
|
|
3693
|
+
const description = typeof args2.description === "string" && args2.description.trim() ? args2.description.trim() : named ? `agent:${named.name}` : "sub-task";
|
|
3468
3694
|
let response = "";
|
|
3469
3695
|
const timeoutPromise = new Promise(
|
|
3470
3696
|
(_, reject) => setTimeout(
|
|
@@ -3473,10 +3699,28 @@ var init_DispatchAgentTool = __esm({
|
|
|
3473
3699
|
)
|
|
3474
3700
|
);
|
|
3475
3701
|
const streamPromise = (async () => {
|
|
3702
|
+
if (named && this.runNamedAgent) {
|
|
3703
|
+
await this.runNamedAgent({
|
|
3704
|
+
agent: named,
|
|
3705
|
+
prompt: prompt4,
|
|
3706
|
+
model,
|
|
3707
|
+
chatId,
|
|
3708
|
+
onContent: (chunk) => {
|
|
3709
|
+
response += chunk;
|
|
3710
|
+
}
|
|
3711
|
+
});
|
|
3712
|
+
return;
|
|
3713
|
+
}
|
|
3714
|
+
const effectivePrompt = named ? `[System prompt for agent "${named.name}"]
|
|
3715
|
+
${named.systemPrompt}
|
|
3716
|
+
|
|
3717
|
+
---
|
|
3718
|
+
|
|
3719
|
+
${prompt4}`.slice(0, MAX_PROMPT_CHARS) : prompt4;
|
|
3476
3720
|
const stream = this.client.streamChat({
|
|
3477
|
-
prompt:
|
|
3721
|
+
prompt: effectivePrompt,
|
|
3478
3722
|
model,
|
|
3479
|
-
// No tools — sub-agent is read/reasoning only.
|
|
3723
|
+
// No tools — sub-agent is read/reasoning only on this path.
|
|
3480
3724
|
tools: [],
|
|
3481
3725
|
...chatId ? { chat_id: chatId } : {},
|
|
3482
3726
|
// Forward project root as context so the backend can enrich the prompt
|
|
@@ -4051,12 +4295,12 @@ Command: ${command}`,
|
|
|
4051
4295
|
proc.stdout?.on("data", (chunk) => chunks.stdout.push(chunk));
|
|
4052
4296
|
proc.stderr?.on("data", (chunk) => chunks.stderr.push(chunk));
|
|
4053
4297
|
const timeoutPromise = new Promise(
|
|
4054
|
-
(
|
|
4298
|
+
(resolve21) => setTimeout(() => resolve21("timeout"), timeoutMs)
|
|
4055
4299
|
);
|
|
4056
4300
|
const processPromise = (async () => {
|
|
4057
|
-
const exitCode2 = await new Promise((
|
|
4058
|
-
proc.on("exit", (code) =>
|
|
4059
|
-
proc.on("error", () =>
|
|
4301
|
+
const exitCode2 = await new Promise((resolve21) => {
|
|
4302
|
+
proc.on("exit", (code) => resolve21(code ?? 1));
|
|
4303
|
+
proc.on("error", () => resolve21(1));
|
|
4060
4304
|
});
|
|
4061
4305
|
const stdout2 = Buffer.concat(chunks.stdout).toString("utf-8");
|
|
4062
4306
|
const stderr2 = Buffer.concat(chunks.stderr).toString("utf-8");
|
|
@@ -4090,8 +4334,315 @@ ${stderr}`;
|
|
|
4090
4334
|
}
|
|
4091
4335
|
});
|
|
4092
4336
|
|
|
4337
|
+
// ../core/src/tools/BackgroundShellTool.ts
|
|
4338
|
+
import { resolve as resolve8, normalize as normalize7, relative as relative8, isAbsolute as isAbsolute8 } from "path";
|
|
4339
|
+
import { spawn as spawn6 } from "child_process";
|
|
4340
|
+
function formatBackgroundShells(shells) {
|
|
4341
|
+
if (shells.length === 0) return "No background shells.";
|
|
4342
|
+
return shells.map((s) => {
|
|
4343
|
+
const dur = ((s.endedAt ?? Date.now()) - s.startedAt) / 1e3;
|
|
4344
|
+
const exit = s.exitCode !== void 0 && s.exitCode !== null ? ` exit=${s.exitCode}` : "";
|
|
4345
|
+
const pid = s.pid !== void 0 ? ` pid=${s.pid}` : "";
|
|
4346
|
+
return `[${s.id}] ${s.status.toUpperCase()}${pid}${exit} ${dur.toFixed(1)}s $ ${s.command}`;
|
|
4347
|
+
}).join("\n");
|
|
4348
|
+
}
|
|
4349
|
+
function resolveCwd(projectRoot, rawArg) {
|
|
4350
|
+
let cwd = projectRoot;
|
|
4351
|
+
if (typeof rawArg === "string" && rawArg.trim()) {
|
|
4352
|
+
const raw = rawArg.trim();
|
|
4353
|
+
const resolved = isAbsolute8(raw) ? normalize7(raw) : resolve8(projectRoot, raw);
|
|
4354
|
+
const rel = relative8(projectRoot, resolved);
|
|
4355
|
+
if (rel.startsWith("..") || isAbsolute8(rel)) {
|
|
4356
|
+
return { error: `Security Block: cwd "${raw}" resolves outside the project root.` };
|
|
4357
|
+
}
|
|
4358
|
+
cwd = resolved;
|
|
4359
|
+
}
|
|
4360
|
+
return { cwd };
|
|
4361
|
+
}
|
|
4362
|
+
var MAX_BUFFER_BYTES, MAX_FINISHED_RETAINED, BackgroundShellRegistryImpl, BackgroundShellRegistry, BashBackgroundTool, ListBackgroundShellsTool, ReadBackgroundShellTool, KillBackgroundShellTool;
|
|
4363
|
+
var init_BackgroundShellTool = __esm({
|
|
4364
|
+
"../core/src/tools/BackgroundShellTool.ts"() {
|
|
4365
|
+
"use strict";
|
|
4366
|
+
init_esm_shims();
|
|
4367
|
+
init_BaseTool();
|
|
4368
|
+
init_BashTool();
|
|
4369
|
+
MAX_BUFFER_BYTES = 256 * 1024;
|
|
4370
|
+
MAX_FINISHED_RETAINED = 50;
|
|
4371
|
+
BackgroundShellRegistryImpl = class {
|
|
4372
|
+
shells = /* @__PURE__ */ new Map();
|
|
4373
|
+
counter = 0;
|
|
4374
|
+
/** Monotonic, human-readable id: bg-1, bg-2, … */
|
|
4375
|
+
nextId() {
|
|
4376
|
+
this.counter += 1;
|
|
4377
|
+
return `bg-${this.counter}`;
|
|
4378
|
+
}
|
|
4379
|
+
/**
|
|
4380
|
+
* Register an already-spawned child process and start buffering its output.
|
|
4381
|
+
* Returns the assigned shell id.
|
|
4382
|
+
*/
|
|
4383
|
+
register(proc, command, cwd) {
|
|
4384
|
+
const id = this.nextId();
|
|
4385
|
+
const entry = {
|
|
4386
|
+
id,
|
|
4387
|
+
command,
|
|
4388
|
+
cwd,
|
|
4389
|
+
status: "running",
|
|
4390
|
+
pid: proc.pid,
|
|
4391
|
+
startedAt: Date.now(),
|
|
4392
|
+
proc,
|
|
4393
|
+
buffer: "",
|
|
4394
|
+
bufferTruncated: false
|
|
4395
|
+
};
|
|
4396
|
+
this.shells.set(id, entry);
|
|
4397
|
+
const append = (chunk) => {
|
|
4398
|
+
entry.buffer += chunk.toString("utf-8");
|
|
4399
|
+
if (entry.buffer.length > MAX_BUFFER_BYTES) {
|
|
4400
|
+
entry.buffer = entry.buffer.slice(entry.buffer.length - MAX_BUFFER_BYTES);
|
|
4401
|
+
entry.bufferTruncated = true;
|
|
4402
|
+
}
|
|
4403
|
+
};
|
|
4404
|
+
proc.stdout?.on("data", append);
|
|
4405
|
+
proc.stderr?.on("data", append);
|
|
4406
|
+
proc.on("error", (err) => {
|
|
4407
|
+
if (entry.status === "running") {
|
|
4408
|
+
entry.status = "error";
|
|
4409
|
+
entry.endedAt = Date.now();
|
|
4410
|
+
entry.buffer += `
|
|
4411
|
+
[spawn error: ${err.message}]`;
|
|
4412
|
+
}
|
|
4413
|
+
this.reapFinished();
|
|
4414
|
+
});
|
|
4415
|
+
proc.on("exit", (code, signal) => {
|
|
4416
|
+
if (entry.status === "running") {
|
|
4417
|
+
entry.status = "exited";
|
|
4418
|
+
}
|
|
4419
|
+
entry.exitCode = code;
|
|
4420
|
+
entry.endedAt = Date.now();
|
|
4421
|
+
if (signal) entry.buffer += `
|
|
4422
|
+
[terminated by signal ${signal}]`;
|
|
4423
|
+
this.reapFinished();
|
|
4424
|
+
});
|
|
4425
|
+
return id;
|
|
4426
|
+
}
|
|
4427
|
+
/** Evict the oldest finished shells once we exceed the retention cap. */
|
|
4428
|
+
reapFinished() {
|
|
4429
|
+
const finished = [...this.shells.values()].filter((s) => s.status !== "running").sort((a, b) => (a.endedAt ?? 0) - (b.endedAt ?? 0));
|
|
4430
|
+
let excess = finished.length - MAX_FINISHED_RETAINED;
|
|
4431
|
+
for (let i = 0; i < finished.length && excess > 0; i++, excess--) {
|
|
4432
|
+
this.shells.delete(finished[i].id);
|
|
4433
|
+
}
|
|
4434
|
+
}
|
|
4435
|
+
/** Public view of all tracked shells, newest first. */
|
|
4436
|
+
list() {
|
|
4437
|
+
return [...this.shells.values()].sort((a, b) => b.startedAt - a.startedAt).map((s) => this.toInfo(s));
|
|
4438
|
+
}
|
|
4439
|
+
get(id) {
|
|
4440
|
+
const s = this.shells.get(id);
|
|
4441
|
+
return s ? this.toInfo(s) : void 0;
|
|
4442
|
+
}
|
|
4443
|
+
/**
|
|
4444
|
+
* Read the buffered output of a shell. `lines` (optional) tails the last N
|
|
4445
|
+
* lines. Returns null if the id is unknown.
|
|
4446
|
+
*/
|
|
4447
|
+
readOutput(id, lines) {
|
|
4448
|
+
const s = this.shells.get(id);
|
|
4449
|
+
if (!s) return null;
|
|
4450
|
+
let output = s.buffer;
|
|
4451
|
+
if (lines && lines > 0) {
|
|
4452
|
+
const all = output.split("\n");
|
|
4453
|
+
output = all.slice(-lines).join("\n");
|
|
4454
|
+
}
|
|
4455
|
+
return { output, truncated: s.bufferTruncated };
|
|
4456
|
+
}
|
|
4457
|
+
/**
|
|
4458
|
+
* Terminate a running shell. Returns:
|
|
4459
|
+
* - 'killed' on success
|
|
4460
|
+
* - 'not-found' if the id is unknown
|
|
4461
|
+
* - 'already' if the shell had already finished
|
|
4462
|
+
*/
|
|
4463
|
+
kill(id) {
|
|
4464
|
+
const s = this.shells.get(id);
|
|
4465
|
+
if (!s) return "not-found";
|
|
4466
|
+
if (s.status !== "running") return "already";
|
|
4467
|
+
s.status = "killed";
|
|
4468
|
+
s.endedAt = Date.now();
|
|
4469
|
+
try {
|
|
4470
|
+
s.proc.kill();
|
|
4471
|
+
} catch {
|
|
4472
|
+
}
|
|
4473
|
+
return "killed";
|
|
4474
|
+
}
|
|
4475
|
+
/** Kill every running shell (used on CLI shutdown). */
|
|
4476
|
+
killAll() {
|
|
4477
|
+
for (const s of this.shells.values()) {
|
|
4478
|
+
if (s.status === "running") {
|
|
4479
|
+
s.status = "killed";
|
|
4480
|
+
s.endedAt = Date.now();
|
|
4481
|
+
try {
|
|
4482
|
+
s.proc.kill();
|
|
4483
|
+
} catch {
|
|
4484
|
+
}
|
|
4485
|
+
}
|
|
4486
|
+
}
|
|
4487
|
+
}
|
|
4488
|
+
/** Test-only: drop all tracked shells (does not kill — caller's responsibility). */
|
|
4489
|
+
_reset() {
|
|
4490
|
+
this.shells.clear();
|
|
4491
|
+
this.counter = 0;
|
|
4492
|
+
}
|
|
4493
|
+
toInfo(s) {
|
|
4494
|
+
return {
|
|
4495
|
+
id: s.id,
|
|
4496
|
+
command: s.command,
|
|
4497
|
+
cwd: s.cwd,
|
|
4498
|
+
status: s.status,
|
|
4499
|
+
pid: s.pid,
|
|
4500
|
+
exitCode: s.exitCode,
|
|
4501
|
+
startedAt: s.startedAt,
|
|
4502
|
+
endedAt: s.endedAt
|
|
4503
|
+
};
|
|
4504
|
+
}
|
|
4505
|
+
};
|
|
4506
|
+
BackgroundShellRegistry = new BackgroundShellRegistryImpl();
|
|
4507
|
+
BashBackgroundTool = class extends BaseTool {
|
|
4508
|
+
name = "bash_background";
|
|
4509
|
+
description = "Launch a shell command in the BACKGROUND and return immediately with a shell id. Use for long-running processes (dev servers, watchers, `tail -f`) that should keep running while you continue working. Inspect output with read_background_shell, list with list_background_shells, and stop with kill_background_shell. Runs via bash (Unix) / cmd (Windows) so shell operators work. For commands that must finish before you proceed, use bash_command instead.";
|
|
4510
|
+
parameters = {
|
|
4511
|
+
type: "object",
|
|
4512
|
+
required: ["command"],
|
|
4513
|
+
properties: {
|
|
4514
|
+
command: {
|
|
4515
|
+
type: "string",
|
|
4516
|
+
description: "The shell command to run in the background. May contain pipes, redirects, and other shell operators."
|
|
4517
|
+
},
|
|
4518
|
+
cwd: {
|
|
4519
|
+
type: "string",
|
|
4520
|
+
description: "Optional working directory (relative to project root). Defaults to the project root."
|
|
4521
|
+
}
|
|
4522
|
+
}
|
|
4523
|
+
};
|
|
4524
|
+
async execute(args2, projectRoot) {
|
|
4525
|
+
if (!args2?.command || typeof args2.command !== "string" || args2.command.trim() === "") {
|
|
4526
|
+
return {
|
|
4527
|
+
content: 'Error: bash_background requires a non-empty "command" argument.',
|
|
4528
|
+
isError: true
|
|
4529
|
+
};
|
|
4530
|
+
}
|
|
4531
|
+
const command = args2.command;
|
|
4532
|
+
const block = BashTool.checkBlocklist(command);
|
|
4533
|
+
if (block.blocked) {
|
|
4534
|
+
return {
|
|
4535
|
+
content: `Security Block: command rejected \u2014 ${block.reason}.
|
|
4536
|
+
Command: ${command}`,
|
|
4537
|
+
isError: true
|
|
4538
|
+
};
|
|
4539
|
+
}
|
|
4540
|
+
const cwdResult = resolveCwd(projectRoot, args2.cwd);
|
|
4541
|
+
if ("error" in cwdResult) {
|
|
4542
|
+
return { content: cwdResult.error, isError: true };
|
|
4543
|
+
}
|
|
4544
|
+
const cwd = cwdResult.cwd;
|
|
4545
|
+
const isWindows = process.platform === "win32";
|
|
4546
|
+
const shellArgv = isWindows ? ["cmd", "/c", command] : ["bash", "-c", command];
|
|
4547
|
+
let proc;
|
|
4548
|
+
try {
|
|
4549
|
+
proc = spawn6(shellArgv[0], shellArgv.slice(1), {
|
|
4550
|
+
cwd,
|
|
4551
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
4552
|
+
shell: false
|
|
4553
|
+
});
|
|
4554
|
+
} catch (e) {
|
|
4555
|
+
return { content: `Error: failed to spawn background shell: ${e.message}`, isError: true };
|
|
4556
|
+
}
|
|
4557
|
+
const id = BackgroundShellRegistry.register(proc, command, cwd);
|
|
4558
|
+
return {
|
|
4559
|
+
content: `Started background shell ${id} (pid ${proc.pid ?? "?"}).
|
|
4560
|
+
Command: ${command}
|
|
4561
|
+
Use read_background_shell with shell_id="${id}" to read its output, or kill_background_shell to stop it.`
|
|
4562
|
+
};
|
|
4563
|
+
}
|
|
4564
|
+
};
|
|
4565
|
+
ListBackgroundShellsTool = class extends BaseTool {
|
|
4566
|
+
name = "list_background_shells";
|
|
4567
|
+
description = "List all background shells started in this session (running and finished), with their id, status, pid, exit code, and command.";
|
|
4568
|
+
parameters = {
|
|
4569
|
+
type: "object",
|
|
4570
|
+
properties: {}
|
|
4571
|
+
};
|
|
4572
|
+
async execute() {
|
|
4573
|
+
return { content: formatBackgroundShells(BackgroundShellRegistry.list()) };
|
|
4574
|
+
}
|
|
4575
|
+
};
|
|
4576
|
+
ReadBackgroundShellTool = class extends BaseTool {
|
|
4577
|
+
name = "read_background_shell";
|
|
4578
|
+
description = "Read the buffered output of a background shell by its id. Optionally tail only the last N lines.";
|
|
4579
|
+
parameters = {
|
|
4580
|
+
type: "object",
|
|
4581
|
+
required: ["shell_id"],
|
|
4582
|
+
properties: {
|
|
4583
|
+
shell_id: {
|
|
4584
|
+
type: "string",
|
|
4585
|
+
description: 'The background shell id (e.g. "bg-1") returned by bash_background.'
|
|
4586
|
+
},
|
|
4587
|
+
lines: {
|
|
4588
|
+
type: "number",
|
|
4589
|
+
description: "Optional: return only the last N lines of buffered output."
|
|
4590
|
+
}
|
|
4591
|
+
}
|
|
4592
|
+
};
|
|
4593
|
+
async execute(args2) {
|
|
4594
|
+
const id = typeof args2?.shell_id === "string" ? args2.shell_id.trim() : "";
|
|
4595
|
+
if (!id) {
|
|
4596
|
+
return { content: 'Error: read_background_shell requires a "shell_id" argument.', isError: true };
|
|
4597
|
+
}
|
|
4598
|
+
const lines = typeof args2?.lines === "number" && args2.lines > 0 ? Math.floor(args2.lines) : void 0;
|
|
4599
|
+
const read = BackgroundShellRegistry.readOutput(id, lines);
|
|
4600
|
+
if (!read) {
|
|
4601
|
+
return { content: `Error: no background shell with id "${id}".`, isError: true };
|
|
4602
|
+
}
|
|
4603
|
+
const info = BackgroundShellRegistry.get(id);
|
|
4604
|
+
const header = `[${info.id}] ${info.status.toUpperCase()}` + (info.exitCode !== void 0 && info.exitCode !== null ? ` exit=${info.exitCode}` : "") + ` $ ${info.command}
|
|
4605
|
+
`;
|
|
4606
|
+
const body = read.output.trim() === "" ? "(no output yet)" : read.output;
|
|
4607
|
+
const truncNote = read.truncated ? "\n... [output buffer truncated to most recent 256 KB]" : "";
|
|
4608
|
+
return { content: header + body + truncNote };
|
|
4609
|
+
}
|
|
4610
|
+
};
|
|
4611
|
+
KillBackgroundShellTool = class extends BaseTool {
|
|
4612
|
+
name = "kill_background_shell";
|
|
4613
|
+
description = "Terminate a running background shell by its id.";
|
|
4614
|
+
parameters = {
|
|
4615
|
+
type: "object",
|
|
4616
|
+
required: ["shell_id"],
|
|
4617
|
+
properties: {
|
|
4618
|
+
shell_id: {
|
|
4619
|
+
type: "string",
|
|
4620
|
+
description: 'The background shell id (e.g. "bg-1") to terminate.'
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4623
|
+
};
|
|
4624
|
+
async execute(args2) {
|
|
4625
|
+
const id = typeof args2?.shell_id === "string" ? args2.shell_id.trim() : "";
|
|
4626
|
+
if (!id) {
|
|
4627
|
+
return { content: 'Error: kill_background_shell requires a "shell_id" argument.', isError: true };
|
|
4628
|
+
}
|
|
4629
|
+
const result = BackgroundShellRegistry.kill(id);
|
|
4630
|
+
switch (result) {
|
|
4631
|
+
case "killed":
|
|
4632
|
+
return { content: `Killed background shell ${id}.` };
|
|
4633
|
+
case "already":
|
|
4634
|
+
return { content: `Background shell ${id} had already finished.` };
|
|
4635
|
+
case "not-found":
|
|
4636
|
+
default:
|
|
4637
|
+
return { content: `Error: no background shell with id "${id}".`, isError: true };
|
|
4638
|
+
}
|
|
4639
|
+
}
|
|
4640
|
+
};
|
|
4641
|
+
}
|
|
4642
|
+
});
|
|
4643
|
+
|
|
4093
4644
|
// ../core/src/tools/NotebookReadTool.ts
|
|
4094
|
-
import { resolve as
|
|
4645
|
+
import { resolve as resolve9, normalize as normalize8, relative as relative9, isAbsolute as isAbsolute9, extname } from "path";
|
|
4095
4646
|
import { readFile as readFile6 } from "fs/promises";
|
|
4096
4647
|
import { existsSync as existsSync7 } from "fs";
|
|
4097
4648
|
function joinSource(source) {
|
|
@@ -4246,9 +4797,9 @@ var init_NotebookReadTool = __esm({
|
|
|
4246
4797
|
isError: true
|
|
4247
4798
|
};
|
|
4248
4799
|
}
|
|
4249
|
-
const absPath =
|
|
4250
|
-
const rel =
|
|
4251
|
-
if (rel.startsWith("..") ||
|
|
4800
|
+
const absPath = isAbsolute9(rawPath) ? normalize8(rawPath) : resolve9(projectRoot, rawPath);
|
|
4801
|
+
const rel = relative9(projectRoot, absPath);
|
|
4802
|
+
if (rel.startsWith("..") || isAbsolute9(rel)) {
|
|
4252
4803
|
return {
|
|
4253
4804
|
content: `Security Block: path "${rawPath}" resolves outside the project root.`,
|
|
4254
4805
|
isError: true
|
|
@@ -4316,10 +4867,10 @@ _(Note: notebook has ${nb.cells.length} cells; only first ${MAX_CELLS} shown.)_`
|
|
|
4316
4867
|
});
|
|
4317
4868
|
|
|
4318
4869
|
// ../core/src/tools/NotebookEditTool.ts
|
|
4319
|
-
import { resolve as
|
|
4870
|
+
import { resolve as resolve10, normalize as normalize9, relative as relative10, isAbsolute as isAbsolute10, extname as extname2, join as join11 } from "path";
|
|
4320
4871
|
import { readFile as readFile7, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
4321
4872
|
import { existsSync as existsSync8 } from "fs";
|
|
4322
|
-
import { homedir as
|
|
4873
|
+
import { homedir as homedir6 } from "os";
|
|
4323
4874
|
import { randomBytes as randomBytes5 } from "crypto";
|
|
4324
4875
|
function normaliseSource(source) {
|
|
4325
4876
|
if (source === "") return [];
|
|
@@ -4440,9 +4991,9 @@ var init_NotebookEditTool = __esm({
|
|
|
4440
4991
|
isError: true
|
|
4441
4992
|
};
|
|
4442
4993
|
}
|
|
4443
|
-
const absPath =
|
|
4444
|
-
const rel =
|
|
4445
|
-
if (rel.startsWith("..") ||
|
|
4994
|
+
const absPath = isAbsolute10(rawPath) ? normalize9(rawPath) : resolve10(projectRoot, rawPath);
|
|
4995
|
+
const rel = relative10(projectRoot, absPath);
|
|
4996
|
+
if (rel.startsWith("..") || isAbsolute10(rel)) {
|
|
4446
4997
|
return {
|
|
4447
4998
|
content: `Security Block: path "${rawPath}" resolves outside the project root.`,
|
|
4448
4999
|
isError: true
|
|
@@ -4499,13 +5050,13 @@ var init_NotebookEditTool = __esm({
|
|
|
4499
5050
|
const filename = absPath.split(/[\\/]/).pop() ?? "notebook.ipynb";
|
|
4500
5051
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4501
5052
|
const suffix = randomBytes5(4).toString("hex");
|
|
4502
|
-
const backupPath =
|
|
4503
|
-
|
|
5053
|
+
const backupPath = join11(
|
|
5054
|
+
homedir6(),
|
|
4504
5055
|
".msapling",
|
|
4505
5056
|
"backups",
|
|
4506
5057
|
`${filename}.backup-${stamp}-${suffix}.bak`
|
|
4507
5058
|
);
|
|
4508
|
-
await mkdir3(
|
|
5059
|
+
await mkdir3(join11(homedir6(), ".msapling", "backups"), { recursive: true });
|
|
4509
5060
|
await writeFile3(backupPath, rawJson, "utf8");
|
|
4510
5061
|
backedUpTo = backupPath;
|
|
4511
5062
|
} catch {
|
|
@@ -4575,10 +5126,10 @@ Backup: ${backedUpTo}`;
|
|
|
4575
5126
|
});
|
|
4576
5127
|
|
|
4577
5128
|
// ../core/src/tools/MultiEditFileTool.ts
|
|
4578
|
-
import { resolve as
|
|
5129
|
+
import { resolve as resolve11, normalize as normalize10, relative as relative11, isAbsolute as isAbsolute11, join as join12 } from "path";
|
|
4579
5130
|
import { readFile as readFile8, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
|
|
4580
5131
|
import { existsSync as existsSync9 } from "fs";
|
|
4581
|
-
import { homedir as
|
|
5132
|
+
import { homedir as homedir7 } from "os";
|
|
4582
5133
|
import { randomBytes as randomBytes6 } from "crypto";
|
|
4583
5134
|
var MAX_EDITS, MultiEditFileTool;
|
|
4584
5135
|
var init_MultiEditFileTool = __esm({
|
|
@@ -4672,9 +5223,9 @@ var init_MultiEditFileTool = __esm({
|
|
|
4672
5223
|
};
|
|
4673
5224
|
}
|
|
4674
5225
|
}
|
|
4675
|
-
const normalizedTarget =
|
|
4676
|
-
const rel =
|
|
4677
|
-
if (rel.startsWith("..") ||
|
|
5226
|
+
const normalizedTarget = isAbsolute11(args2.path) ? normalize10(args2.path) : resolve11(projectRoot, args2.path.trim());
|
|
5227
|
+
const rel = relative11(projectRoot, normalizedTarget);
|
|
5228
|
+
if (rel.startsWith("..") || isAbsolute11(rel)) {
|
|
4678
5229
|
return {
|
|
4679
5230
|
content: `Security Block: path "${args2.path}" resolves outside the project root.`,
|
|
4680
5231
|
isError: true
|
|
@@ -4742,13 +5293,13 @@ No changes were written (atomic: all-or-nothing).`,
|
|
|
4742
5293
|
const filename = resolvedTarget.split(/[\\/]/).pop() ?? "file";
|
|
4743
5294
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4744
5295
|
const suffix = randomBytes6(4).toString("hex");
|
|
4745
|
-
const backupPath =
|
|
4746
|
-
|
|
5296
|
+
const backupPath = join12(
|
|
5297
|
+
homedir7(),
|
|
4747
5298
|
".msapling",
|
|
4748
5299
|
"backups",
|
|
4749
5300
|
`${filename}.backup-${stamp}-${suffix}.bak`
|
|
4750
5301
|
);
|
|
4751
|
-
await mkdir4(
|
|
5302
|
+
await mkdir4(join12(homedir7(), ".msapling", "backups"), { recursive: true });
|
|
4752
5303
|
await writeFile4(backupPath, originalContent, "utf8");
|
|
4753
5304
|
backedUpTo = backupPath;
|
|
4754
5305
|
} catch {
|
|
@@ -4776,14 +5327,14 @@ Pre-edit content backed up to: ${backedUpTo}`;
|
|
|
4776
5327
|
});
|
|
4777
5328
|
|
|
4778
5329
|
// ../core/src/tools/MoveFileTool.ts
|
|
4779
|
-
import { resolve as
|
|
5330
|
+
import { resolve as resolve12, normalize as normalize11, relative as relative12, isAbsolute as isAbsolute12, dirname as dirname2 } from "path";
|
|
4780
5331
|
import { rename, mkdir as mkdir5, copyFile, rm, stat as stat2, readdir as readdir2 } from "fs/promises";
|
|
4781
|
-
import { existsSync as existsSync10, statSync as
|
|
5332
|
+
import { existsSync as existsSync10, statSync as statSync4 } from "fs";
|
|
4782
5333
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
4783
5334
|
function containedPath(p, root) {
|
|
4784
|
-
const abs =
|
|
4785
|
-
const rel =
|
|
4786
|
-
if (rel.startsWith("..") ||
|
|
5335
|
+
const abs = isAbsolute12(p) ? normalize11(p) : resolve12(root, p.trim());
|
|
5336
|
+
const rel = relative12(root, abs);
|
|
5337
|
+
if (rel.startsWith("..") || isAbsolute12(rel)) {
|
|
4787
5338
|
return { ok: false, reason: `path "${p}" resolves outside the project root` };
|
|
4788
5339
|
}
|
|
4789
5340
|
return { ok: true, abs };
|
|
@@ -4792,8 +5343,8 @@ async function copyDir(src, dst) {
|
|
|
4792
5343
|
await mkdir5(dst, { recursive: true });
|
|
4793
5344
|
const entries = await readdir2(src, { withFileTypes: true });
|
|
4794
5345
|
for (const entry of entries) {
|
|
4795
|
-
const srcPath =
|
|
4796
|
-
const dstPath =
|
|
5346
|
+
const srcPath = resolve12(src, entry.name);
|
|
5347
|
+
const dstPath = resolve12(dst, entry.name);
|
|
4797
5348
|
if (entry.isDirectory()) {
|
|
4798
5349
|
await copyDir(srcPath, dstPath);
|
|
4799
5350
|
} else {
|
|
@@ -4804,18 +5355,18 @@ async function copyDir(src, dst) {
|
|
|
4804
5355
|
async function backupFile(absPath) {
|
|
4805
5356
|
try {
|
|
4806
5357
|
const { readFile: readFile30, writeFile: writeFile19, mkdir: mkdir10 } = await import("fs/promises");
|
|
4807
|
-
const { homedir:
|
|
4808
|
-
const { join:
|
|
5358
|
+
const { homedir: homedir25 } = await import("os");
|
|
5359
|
+
const { join: join41 } = await import("path");
|
|
4809
5360
|
const filename = absPath.split(/[\\/]/).pop() ?? "file";
|
|
4810
5361
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4811
5362
|
const suffix = randomBytes7(4).toString("hex");
|
|
4812
|
-
const backupPath =
|
|
4813
|
-
|
|
5363
|
+
const backupPath = join41(
|
|
5364
|
+
homedir25(),
|
|
4814
5365
|
".msapling",
|
|
4815
5366
|
"backups",
|
|
4816
5367
|
`${filename}.backup-${stamp}-${suffix}.bak`
|
|
4817
5368
|
);
|
|
4818
|
-
await mkdir10(
|
|
5369
|
+
await mkdir10(join41(homedir25(), ".msapling", "backups"), { recursive: true });
|
|
4819
5370
|
const content = await readFile30(absPath, "utf8");
|
|
4820
5371
|
await writeFile19(backupPath, content, "utf8");
|
|
4821
5372
|
return backupPath;
|
|
@@ -4875,7 +5426,7 @@ var init_MoveFileTool = __esm({
|
|
|
4875
5426
|
isError: true
|
|
4876
5427
|
};
|
|
4877
5428
|
}
|
|
4878
|
-
const srcStat =
|
|
5429
|
+
const srcStat = statSync4(absSrc);
|
|
4879
5430
|
const srcIsDir = srcStat.isDirectory();
|
|
4880
5431
|
let rawDst = args2.destination.trim();
|
|
4881
5432
|
const dstPrelimCheck = containedPath(rawDst, projectRoot);
|
|
@@ -4883,11 +5434,11 @@ var init_MoveFileTool = __esm({
|
|
|
4883
5434
|
return { content: `Security Block: ${dstPrelimCheck.reason}.`, isError: true };
|
|
4884
5435
|
}
|
|
4885
5436
|
let absDst = dstPrelimCheck.abs;
|
|
4886
|
-
if (existsSync10(absDst) &&
|
|
5437
|
+
if (existsSync10(absDst) && statSync4(absDst).isDirectory() && !srcIsDir) {
|
|
4887
5438
|
const srcName = absSrc.split(/[\\/]/).pop();
|
|
4888
|
-
absDst =
|
|
4889
|
-
const rel2 =
|
|
4890
|
-
if (rel2.startsWith("..") ||
|
|
5439
|
+
absDst = resolve12(absDst, srcName);
|
|
5440
|
+
const rel2 = relative12(projectRoot, absDst);
|
|
5441
|
+
if (rel2.startsWith("..") || isAbsolute12(rel2)) {
|
|
4891
5442
|
return {
|
|
4892
5443
|
content: `Security Block: adjusted destination "${absDst}" resolves outside the project root.`,
|
|
4893
5444
|
isError: true
|
|
@@ -4908,7 +5459,7 @@ var init_MoveFileTool = __esm({
|
|
|
4908
5459
|
isError: true
|
|
4909
5460
|
};
|
|
4910
5461
|
}
|
|
4911
|
-
if (!
|
|
5462
|
+
if (!statSync4(absDst).isDirectory()) {
|
|
4912
5463
|
backedUpTo = await backupFile(absDst);
|
|
4913
5464
|
}
|
|
4914
5465
|
}
|
|
@@ -4967,7 +5518,7 @@ Overwritten destination backed up to: ${backedUpTo}`;
|
|
|
4967
5518
|
});
|
|
4968
5519
|
|
|
4969
5520
|
// ../core/src/tools/DeleteFileTool.ts
|
|
4970
|
-
import { resolve as
|
|
5521
|
+
import { resolve as resolve13, normalize as normalize12, relative as relative13, isAbsolute as isAbsolute13, join as join14 } from "path";
|
|
4971
5522
|
import { rm as rm2, stat as stat3 } from "fs/promises";
|
|
4972
5523
|
import { randomBytes as randomBytes8 } from "crypto";
|
|
4973
5524
|
var DeleteFileTool;
|
|
@@ -5002,15 +5553,15 @@ var init_DeleteFileTool = __esm({
|
|
|
5002
5553
|
}
|
|
5003
5554
|
const rawPath = args2.path.trim();
|
|
5004
5555
|
const recursive = args2.recursive === true;
|
|
5005
|
-
const abs =
|
|
5006
|
-
const rel =
|
|
5007
|
-
if (rel.startsWith("..") ||
|
|
5556
|
+
const abs = isAbsolute13(rawPath) ? normalize12(rawPath) : resolve13(projectRoot, rawPath);
|
|
5557
|
+
const rel = relative13(projectRoot, abs);
|
|
5558
|
+
if (rel.startsWith("..") || isAbsolute13(rel)) {
|
|
5008
5559
|
return {
|
|
5009
5560
|
content: `Security Block: path "${rawPath}" resolves outside the project root.`,
|
|
5010
5561
|
isError: true
|
|
5011
5562
|
};
|
|
5012
5563
|
}
|
|
5013
|
-
if (rel === "" || abs ===
|
|
5564
|
+
if (rel === "" || abs === normalize12(projectRoot)) {
|
|
5014
5565
|
return {
|
|
5015
5566
|
content: "Security Block: refusing to delete the project root directory.",
|
|
5016
5567
|
isError: true
|
|
@@ -5043,13 +5594,13 @@ var init_DeleteFileTool = __esm({
|
|
|
5043
5594
|
if (isFile) {
|
|
5044
5595
|
try {
|
|
5045
5596
|
const { readFile: readFile30, writeFile: writeFile19, mkdir: mkdir10 } = await import("fs/promises");
|
|
5046
|
-
const { homedir:
|
|
5597
|
+
const { homedir: homedir25 } = await import("os");
|
|
5047
5598
|
const existingContent = await readFile30(abs, "utf8");
|
|
5048
5599
|
const filename = abs.split(/[\\/]/).pop() ?? "file";
|
|
5049
5600
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
5050
5601
|
const suffix = randomBytes8(4).toString("hex");
|
|
5051
|
-
const backupPath =
|
|
5052
|
-
await mkdir10(
|
|
5602
|
+
const backupPath = join14(homedir25(), ".msapling", "backups", `${filename}.backup-${stamp}-${suffix}.bak`);
|
|
5603
|
+
await mkdir10(join14(homedir25(), ".msapling", "backups"), { recursive: true });
|
|
5053
5604
|
await writeFile19(backupPath, existingContent, "utf8");
|
|
5054
5605
|
backedUpTo = backupPath;
|
|
5055
5606
|
} catch {
|
|
@@ -5191,7 +5742,7 @@ var init_MDrive = __esm({
|
|
|
5191
5742
|
});
|
|
5192
5743
|
|
|
5193
5744
|
// ../core/src/Sandbox.ts
|
|
5194
|
-
import { resolve as
|
|
5745
|
+
import { resolve as resolve14, normalize as normalize13, relative as relative14, isAbsolute as isAbsolute14 } from "path";
|
|
5195
5746
|
import { realpathSync } from "fs";
|
|
5196
5747
|
import { realpath as realpath2 } from "fs/promises";
|
|
5197
5748
|
import { createHash as createHash3 } from "crypto";
|
|
@@ -5250,7 +5801,7 @@ var init_Sandbox = __esm({
|
|
|
5250
5801
|
"sc"
|
|
5251
5802
|
]);
|
|
5252
5803
|
constructor(projectRoot, opts) {
|
|
5253
|
-
this.projectRoot =
|
|
5804
|
+
this.projectRoot = resolve14(projectRoot);
|
|
5254
5805
|
this.realpathSyncFn = opts?.realpathSync ?? realpathSync;
|
|
5255
5806
|
}
|
|
5256
5807
|
setPermissions(state) {
|
|
@@ -5265,15 +5816,15 @@ var init_Sandbox = __esm({
|
|
|
5265
5816
|
return hasher.digest("hex");
|
|
5266
5817
|
}
|
|
5267
5818
|
isPathSafe(targetPath) {
|
|
5268
|
-
const normalizedTarget =
|
|
5819
|
+
const normalizedTarget = isAbsolute14(targetPath) ? normalize13(targetPath) : resolve14(this.projectRoot, targetPath);
|
|
5269
5820
|
let resolvedTarget = normalizedTarget;
|
|
5270
5821
|
try {
|
|
5271
5822
|
resolvedTarget = this.realpathSyncFn(normalizedTarget);
|
|
5272
5823
|
} catch {
|
|
5273
5824
|
resolvedTarget = normalizedTarget;
|
|
5274
5825
|
}
|
|
5275
|
-
const rel =
|
|
5276
|
-
const isOutside = rel.startsWith("..") ||
|
|
5826
|
+
const rel = relative14(this.projectRoot, resolvedTarget);
|
|
5827
|
+
const isOutside = rel.startsWith("..") || isAbsolute14(rel);
|
|
5277
5828
|
if (isOutside) {
|
|
5278
5829
|
if (this.permissions.trustedPaths.includes(resolvedTarget) || this.permissions.trustedPaths.includes(normalizedTarget)) {
|
|
5279
5830
|
return { safe: true, absolutePath: resolvedTarget };
|
|
@@ -5291,15 +5842,15 @@ var init_Sandbox = __esm({
|
|
|
5291
5842
|
* isPathSafe (sync) is kept for cold/boot-time paths and existing tests.
|
|
5292
5843
|
*/
|
|
5293
5844
|
async isPathSafeAsync(targetPath) {
|
|
5294
|
-
const normalizedTarget =
|
|
5845
|
+
const normalizedTarget = isAbsolute14(targetPath) ? normalize13(targetPath) : resolve14(this.projectRoot, targetPath);
|
|
5295
5846
|
let resolvedTarget = normalizedTarget;
|
|
5296
5847
|
try {
|
|
5297
5848
|
resolvedTarget = await realpath2(normalizedTarget);
|
|
5298
5849
|
} catch {
|
|
5299
5850
|
resolvedTarget = normalizedTarget;
|
|
5300
5851
|
}
|
|
5301
|
-
const rel =
|
|
5302
|
-
const isOutside = rel.startsWith("..") ||
|
|
5852
|
+
const rel = relative14(this.projectRoot, resolvedTarget);
|
|
5853
|
+
const isOutside = rel.startsWith("..") || isAbsolute14(rel);
|
|
5303
5854
|
if (isOutside) {
|
|
5304
5855
|
if (this.permissions.trustedPaths.includes(resolvedTarget) || this.permissions.trustedPaths.includes(normalizedTarget)) {
|
|
5305
5856
|
return { safe: true, absolutePath: resolvedTarget };
|
|
@@ -5331,7 +5882,7 @@ var init_Sandbox = __esm({
|
|
|
5331
5882
|
}
|
|
5332
5883
|
const parsed = parse(commandLine);
|
|
5333
5884
|
const tokens = parsed.filter((t) => typeof t === "string");
|
|
5334
|
-
const
|
|
5885
|
+
const normalize14 = (raw) => {
|
|
5335
5886
|
let b = raw.toLowerCase();
|
|
5336
5887
|
const slash = Math.max(b.lastIndexOf("/"), b.lastIndexOf("\\"));
|
|
5337
5888
|
if (slash >= 0) b = b.slice(slash + 1);
|
|
@@ -5339,7 +5890,7 @@ var init_Sandbox = __esm({
|
|
|
5339
5890
|
return b;
|
|
5340
5891
|
};
|
|
5341
5892
|
for (const tok of tokens) {
|
|
5342
|
-
const b =
|
|
5893
|
+
const b = normalize14(tok);
|
|
5343
5894
|
if (_Sandbox.DANGEROUS_BINARIES.has(b)) {
|
|
5344
5895
|
return { status: "blocked", reason: `Binary '${b}' is explicitly forbidden.`, hash };
|
|
5345
5896
|
}
|
|
@@ -5347,7 +5898,7 @@ var init_Sandbox = __esm({
|
|
|
5347
5898
|
if (tokens.length === 0) {
|
|
5348
5899
|
return { status: "safe", hash };
|
|
5349
5900
|
}
|
|
5350
|
-
const binary =
|
|
5901
|
+
const binary = normalize14(tokens[0]);
|
|
5351
5902
|
const sub = tokens.length > 1 ? tokens[1].toLowerCase() : "";
|
|
5352
5903
|
if (binary === "git") {
|
|
5353
5904
|
const destructiveGit = /* @__PURE__ */ new Set(["push", "reset", "clean", "rebase", "merge", "force-push"]);
|
|
@@ -5446,7 +5997,7 @@ var init_Sandbox = __esm({
|
|
|
5446
5997
|
});
|
|
5447
5998
|
|
|
5448
5999
|
// ../core/src/Voice.ts
|
|
5449
|
-
import { spawn as
|
|
6000
|
+
import { spawn as spawn7 } from "child_process";
|
|
5450
6001
|
function buildTtsInvocation(text, rate) {
|
|
5451
6002
|
const psCommand = `
|
|
5452
6003
|
Add-Type -AssemblyName System.Speech;
|
|
@@ -5486,10 +6037,10 @@ var init_Voice = __esm({
|
|
|
5486
6037
|
const { command, args: args2, env } = buildTtsInvocation(text, rate);
|
|
5487
6038
|
try {
|
|
5488
6039
|
if (process.platform === "win32") {
|
|
5489
|
-
await new Promise((
|
|
6040
|
+
await new Promise((resolve21, reject) => {
|
|
5490
6041
|
try {
|
|
5491
|
-
const proc =
|
|
5492
|
-
proc.on("exit", () =>
|
|
6042
|
+
const proc = spawn7(command, args2, { env });
|
|
6043
|
+
proc.on("exit", () => resolve21());
|
|
5493
6044
|
proc.on("error", reject);
|
|
5494
6045
|
} catch (e) {
|
|
5495
6046
|
reject(e);
|
|
@@ -5586,7 +6137,7 @@ var init_ShadowService = __esm({
|
|
|
5586
6137
|
});
|
|
5587
6138
|
|
|
5588
6139
|
// ../core/src/Hooks.ts
|
|
5589
|
-
import {
|
|
6140
|
+
import { exec } from "child_process";
|
|
5590
6141
|
function matches(entry, ctx) {
|
|
5591
6142
|
if (!entry.matcher) return true;
|
|
5592
6143
|
let re;
|
|
@@ -5603,55 +6154,34 @@ function matches(entry, ctx) {
|
|
|
5603
6154
|
async function runOne(entry, ctx) {
|
|
5604
6155
|
const timeoutMs = entry.timeoutMs ?? DEFAULT_TIMEOUT_MS3;
|
|
5605
6156
|
const command = entry.command;
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
const child = spawn7(isWindows ? "cmd.exe" : "sh", isWindows ? ["/c", command] : ["-c", command], {
|
|
5609
|
-
cwd: ctx.cwd ?? process.cwd(),
|
|
5610
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
5611
|
-
});
|
|
5612
|
-
let stdout = "";
|
|
5613
|
-
let stderr = "";
|
|
6157
|
+
const env = { ...process.env, MSAPLING_HOOK_PAYLOAD: JSON.stringify({ ...ctx }) };
|
|
6158
|
+
return new Promise((resolve21) => {
|
|
5614
6159
|
let timedOut = false;
|
|
5615
6160
|
let settled = false;
|
|
5616
|
-
const finish = (exitCode) => {
|
|
6161
|
+
const finish = (exitCode, stdout, stderr) => {
|
|
5617
6162
|
if (settled) return;
|
|
5618
6163
|
settled = true;
|
|
5619
6164
|
clearTimeout(killer);
|
|
5620
6165
|
const blocked = !NON_BLOCKING_EVENTS.has(ctx.event) && !!entry.blocking && (exitCode === null || exitCode !== 0);
|
|
5621
|
-
|
|
6166
|
+
resolve21({ command, exitCode, stdout, stderr, timedOut, blocked });
|
|
5622
6167
|
};
|
|
6168
|
+
const child = exec(command, {
|
|
6169
|
+
cwd: ctx.cwd ?? process.cwd(),
|
|
6170
|
+
env,
|
|
6171
|
+
maxBuffer: MAX_OUTPUT_BYTES3
|
|
6172
|
+
}, (err, stdout, stderr) => {
|
|
6173
|
+
const rawCode = err ? err.code : 0;
|
|
6174
|
+
const exitCode = typeof rawCode === "number" ? rawCode : null;
|
|
6175
|
+
finish(exitCode, stdout.slice(0, MAX_OUTPUT_BYTES3), stderr.slice(0, MAX_OUTPUT_BYTES3));
|
|
6176
|
+
});
|
|
5623
6177
|
const killer = setTimeout(() => {
|
|
5624
6178
|
timedOut = true;
|
|
5625
6179
|
try {
|
|
5626
6180
|
child.kill("SIGKILL");
|
|
5627
6181
|
} catch {
|
|
5628
6182
|
}
|
|
5629
|
-
finish(null);
|
|
6183
|
+
finish(null, "", "");
|
|
5630
6184
|
}, timeoutMs);
|
|
5631
|
-
child.stdout.on("data", (b) => {
|
|
5632
|
-
if (stdout.length < MAX_OUTPUT_BYTES3) stdout += b.toString("utf8");
|
|
5633
|
-
});
|
|
5634
|
-
child.stderr.on("data", (b) => {
|
|
5635
|
-
if (stderr.length < MAX_OUTPUT_BYTES3) stderr += b.toString("utf8");
|
|
5636
|
-
});
|
|
5637
|
-
child.on("error", (e) => {
|
|
5638
|
-
stderr += `
|
|
5639
|
-
[hook spawn error] ${e.message}`;
|
|
5640
|
-
finish(null);
|
|
5641
|
-
});
|
|
5642
|
-
child.on("close", (code) => finish(code));
|
|
5643
|
-
try {
|
|
5644
|
-
child.stdin.write(JSON.stringify({ ...ctx }) + "\n");
|
|
5645
|
-
child.stdin.end();
|
|
5646
|
-
} catch (e) {
|
|
5647
|
-
stderr += `
|
|
5648
|
-
[hook stdin error] ${e?.message}`;
|
|
5649
|
-
try {
|
|
5650
|
-
child.kill();
|
|
5651
|
-
} catch {
|
|
5652
|
-
}
|
|
5653
|
-
finish(null);
|
|
5654
|
-
}
|
|
5655
6185
|
});
|
|
5656
6186
|
}
|
|
5657
6187
|
var NON_BLOCKING_EVENTS, DEFAULT_TIMEOUT_MS3, MAX_OUTPUT_BYTES3, HookRunner;
|
|
@@ -5869,9 +6399,9 @@ var init_specs = __esm({
|
|
|
5869
6399
|
|
|
5870
6400
|
// ../core/src/governor/ResourceGovernor.ts
|
|
5871
6401
|
import { freemem as freemem2 } from "os";
|
|
5872
|
-
import { homedir as
|
|
6402
|
+
import { homedir as homedir8 } from "os";
|
|
5873
6403
|
import { readFile as readFile9 } from "fs/promises";
|
|
5874
|
-
import { join as
|
|
6404
|
+
import { join as join15 } from "path";
|
|
5875
6405
|
function determineTier(specs) {
|
|
5876
6406
|
const memGB = specs.memory.totalGB;
|
|
5877
6407
|
const cores = specs.cpu.cores;
|
|
@@ -5886,7 +6416,7 @@ function recommendLimits(specs) {
|
|
|
5886
6416
|
}
|
|
5887
6417
|
async function readConfigOverrides() {
|
|
5888
6418
|
try {
|
|
5889
|
-
const configPath =
|
|
6419
|
+
const configPath = join15(homedir8(), ".msapling", "config.json");
|
|
5890
6420
|
const content = await readFile9(configPath, "utf-8");
|
|
5891
6421
|
const config = JSON.parse(content);
|
|
5892
6422
|
return config.limits ?? null;
|
|
@@ -5967,7 +6497,7 @@ var init_ResourceGovernor = __esm({
|
|
|
5967
6497
|
this.activeAgents++;
|
|
5968
6498
|
return;
|
|
5969
6499
|
}
|
|
5970
|
-
await new Promise((
|
|
6500
|
+
await new Promise((resolve21) => this.agentWaiters.push(resolve21));
|
|
5971
6501
|
this.activeAgents++;
|
|
5972
6502
|
}
|
|
5973
6503
|
/**
|
|
@@ -5986,7 +6516,7 @@ var init_ResourceGovernor = __esm({
|
|
|
5986
6516
|
this.activeTool++;
|
|
5987
6517
|
return;
|
|
5988
6518
|
}
|
|
5989
|
-
await new Promise((
|
|
6519
|
+
await new Promise((resolve21) => this.toolWaiters.push(resolve21));
|
|
5990
6520
|
this.activeTool++;
|
|
5991
6521
|
}
|
|
5992
6522
|
/**
|
|
@@ -6044,6 +6574,7 @@ var init_ToolExecutor = __esm({
|
|
|
6044
6574
|
init_WebFetchTool();
|
|
6045
6575
|
init_WebSearchTool();
|
|
6046
6576
|
init_BashTool();
|
|
6577
|
+
init_BackgroundShellTool();
|
|
6047
6578
|
init_NotebookReadTool();
|
|
6048
6579
|
init_NotebookEditTool();
|
|
6049
6580
|
init_MultiEditFileTool();
|
|
@@ -6056,9 +6587,14 @@ var init_ToolExecutor = __esm({
|
|
|
6056
6587
|
init_ShadowService();
|
|
6057
6588
|
init_Hooks();
|
|
6058
6589
|
init_ResourceGovernor();
|
|
6059
|
-
APPROVAL_GATED = /* @__PURE__ */ new Set(["run_command", "bash_command", "edit_file", "write_file", "patch_file", "multi_edit_file", "notebook_edit_cell", "move_file", "delete_file", "open_sub_shell"]);
|
|
6590
|
+
APPROVAL_GATED = /* @__PURE__ */ new Set(["run_command", "bash_command", "bash_background", "edit_file", "write_file", "patch_file", "multi_edit_file", "notebook_edit_cell", "move_file", "delete_file", "open_sub_shell"]);
|
|
6060
6591
|
ToolExecutor = class {
|
|
6061
6592
|
tools = /* @__PURE__ */ new Map();
|
|
6593
|
+
/** Backend client — retained so named-subagent dispatch can open its own
|
|
6594
|
+
* scoped stream (CLI-PARITY-P1-6). */
|
|
6595
|
+
client;
|
|
6596
|
+
/** Project root — retained for named-subagent dispatch context. */
|
|
6597
|
+
projectRoot;
|
|
6062
6598
|
sandbox;
|
|
6063
6599
|
mdrive;
|
|
6064
6600
|
voice;
|
|
@@ -6093,6 +6629,8 @@ var init_ToolExecutor = __esm({
|
|
|
6093
6629
|
*/
|
|
6094
6630
|
onModeChange = null;
|
|
6095
6631
|
constructor(client, projectRoot) {
|
|
6632
|
+
this.client = client;
|
|
6633
|
+
this.projectRoot = projectRoot;
|
|
6096
6634
|
this.sandbox = new Sandbox(projectRoot);
|
|
6097
6635
|
this.mdrive = new MDriveService(client);
|
|
6098
6636
|
this.voice = new VoiceService();
|
|
@@ -6121,11 +6659,20 @@ var init_ToolExecutor = __esm({
|
|
|
6121
6659
|
payload: info,
|
|
6122
6660
|
cwd: projectRoot
|
|
6123
6661
|
});
|
|
6124
|
-
}
|
|
6662
|
+
},
|
|
6663
|
+
// CLI-PARITY-P1-6: named-subagent runner. Runs a named agent with its own
|
|
6664
|
+
// system prompt + model + TOOL ALLOWLIST. Tools execute back through THIS
|
|
6665
|
+
// executor (so the existing permission/approval system gates writes), but
|
|
6666
|
+
// only the agent's allowlisted tools are advertised + permitted.
|
|
6667
|
+
runNamedAgent: (req) => this.runNamedSubagent(req)
|
|
6125
6668
|
}));
|
|
6126
6669
|
this.registerTool(new WebFetchTool());
|
|
6127
6670
|
this.registerTool(new WebSearchTool({ client }));
|
|
6128
6671
|
this.registerTool(new BashTool());
|
|
6672
|
+
this.registerTool(new BashBackgroundTool());
|
|
6673
|
+
this.registerTool(new ListBackgroundShellsTool());
|
|
6674
|
+
this.registerTool(new ReadBackgroundShellTool());
|
|
6675
|
+
this.registerTool(new KillBackgroundShellTool());
|
|
6129
6676
|
this.registerTool(new NotebookReadTool());
|
|
6130
6677
|
this.registerTool(new NotebookEditTool());
|
|
6131
6678
|
this.registerTool(new MultiEditFileTool());
|
|
@@ -6317,13 +6864,13 @@ ${blocker.stderr || "(empty)"}`,
|
|
|
6317
6864
|
return { content: `Security Block: ${check2.reason}`, isError: true };
|
|
6318
6865
|
}
|
|
6319
6866
|
}
|
|
6320
|
-
if (toolName === "run_command" || toolName === "bash_command") {
|
|
6867
|
+
if (toolName === "run_command" || toolName === "bash_command" || toolName === "bash_background") {
|
|
6321
6868
|
if (!args2.command) {
|
|
6322
6869
|
return { content: `Error: ${toolName} requires a command argument`, isError: true };
|
|
6323
6870
|
}
|
|
6324
6871
|
const analysis = this.sandbox.analyzeCommand(args2.command);
|
|
6325
6872
|
if (analysis.status === "blocked") {
|
|
6326
|
-
const staleCmdKey = toolName === "
|
|
6873
|
+
const staleCmdKey = toolName === "run_command" ? `run_command:${(args2.command || "").trim()}` : `${toolName}:${(args2.command || "").trim().replace(/\s+/g, " ")}${args2.cwd ? `:cwd=${args2.cwd}` : ""}`;
|
|
6327
6874
|
if (this.trustStore?.has(staleCmdKey)) {
|
|
6328
6875
|
this.trustStore.delete(staleCmdKey).catch(() => {
|
|
6329
6876
|
});
|
|
@@ -6338,10 +6885,10 @@ ${blocker.stderr || "(empty)"}`,
|
|
|
6338
6885
|
let cmdKey = "";
|
|
6339
6886
|
if (toolName === "run_command") {
|
|
6340
6887
|
cmdKey = `run_command:${(args2.command || "").trim()}`;
|
|
6341
|
-
} else if (toolName === "bash_command") {
|
|
6888
|
+
} else if (toolName === "bash_command" || toolName === "bash_background") {
|
|
6342
6889
|
const normalizedCmd = (args2.command || "").trim().replace(/\s+/g, " ");
|
|
6343
6890
|
const cwdSuffix = args2.cwd ? `:cwd=${args2.cwd}` : "";
|
|
6344
|
-
cmdKey =
|
|
6891
|
+
cmdKey = `${toolName}:${normalizedCmd}${cwdSuffix}`;
|
|
6345
6892
|
} else {
|
|
6346
6893
|
cmdKey = `${toolName}:${(args2.path || args2.instruction || "").trim()}`;
|
|
6347
6894
|
}
|
|
@@ -6365,7 +6912,7 @@ ${blocker.stderr || "(empty)"}`,
|
|
|
6365
6912
|
}
|
|
6366
6913
|
}
|
|
6367
6914
|
}
|
|
6368
|
-
if (toolName === "run_command" || toolName === "bash_command" || toolName === "edit_file" || toolName === "write_file" || toolName === "patch_file" || toolName === "multi_edit_file" || toolName === "notebook_edit_cell" || toolName === "move_file" || toolName === "delete_file") {
|
|
6915
|
+
if (toolName === "run_command" || toolName === "bash_command" || toolName === "bash_background" || toolName === "edit_file" || toolName === "write_file" || toolName === "patch_file" || toolName === "multi_edit_file" || toolName === "notebook_edit_cell" || toolName === "move_file" || toolName === "delete_file") {
|
|
6369
6916
|
const audit = await this.shadow.verifyAction(
|
|
6370
6917
|
JSON.stringify({ tool: toolName, args: args2 }),
|
|
6371
6918
|
`Execution context: ${projectRoot}`
|
|
@@ -6426,6 +6973,80 @@ Please approve the diff in the UI to sync this change locally.`
|
|
|
6426
6973
|
}));
|
|
6427
6974
|
return [...builtin, ...mcp];
|
|
6428
6975
|
}
|
|
6976
|
+
/**
|
|
6977
|
+
* CLI-PARITY-P1-6: tool schemas filtered to a named agent's allowlist.
|
|
6978
|
+
*
|
|
6979
|
+
* - `undefined` allowlist → inherit the FULL builtin+MCP set (getToolSchemas).
|
|
6980
|
+
* - `[]` → no tools (read/reason-only agent).
|
|
6981
|
+
* - `['read_file', …]` → ONLY those tools (silently drops names that
|
|
6982
|
+
* don't resolve to a registered tool).
|
|
6983
|
+
*
|
|
6984
|
+
* `dispatch_agent` is always excluded from a subagent's view so a named agent
|
|
6985
|
+
* cannot recursively spawn further named agents (avoids unbounded fan-out).
|
|
6986
|
+
*/
|
|
6987
|
+
getToolSchemasForAllowlist(allow) {
|
|
6988
|
+
const all = this.getToolSchemas();
|
|
6989
|
+
if (allow === void 0) {
|
|
6990
|
+
return all.filter((s) => s.name !== "dispatch_agent");
|
|
6991
|
+
}
|
|
6992
|
+
const allowSet = new Set(allow);
|
|
6993
|
+
return all.filter((s) => s.name !== "dispatch_agent" && allowSet.has(s.name));
|
|
6994
|
+
}
|
|
6995
|
+
/**
|
|
6996
|
+
* CLI-PARITY-P1-6: run a NAMED subagent to completion.
|
|
6997
|
+
*
|
|
6998
|
+
* Drives a bounded agentic loop against the backend using the agent's system
|
|
6999
|
+
* prompt + model, advertising ONLY the agent's allowlisted tools. Tool calls
|
|
7000
|
+
* execute back through `this.execute(...)`, so the SAME permission/approval +
|
|
7001
|
+
* shadow-audit gates apply — the allowlist narrows WHICH tools the agent may
|
|
7002
|
+
* request; the permission system still governs WHETHER a given write runs.
|
|
7003
|
+
*
|
|
7004
|
+
* This is the mechanism that closes the "subagents are read-only" gap: a named
|
|
7005
|
+
* agent whose frontmatter grants `write_file`/`edit_file`/`bash_command` can
|
|
7006
|
+
* use them (subject to approval), whereas the anonymous dispatch path cannot.
|
|
7007
|
+
*/
|
|
7008
|
+
async runNamedSubagent(req) {
|
|
7009
|
+
const { agent, prompt: prompt4, model, chatId, onContent } = req;
|
|
7010
|
+
const schemas = this.getToolSchemasForAllowlist(agent.tools);
|
|
7011
|
+
const framedPrompt = `[You are the "${agent.name}" subagent. Operate strictly within this role.]
|
|
7012
|
+
${agent.systemPrompt}
|
|
7013
|
+
|
|
7014
|
+
---
|
|
7015
|
+
|
|
7016
|
+
${prompt4}`;
|
|
7017
|
+
const MAX_SUBAGENT_ROUNDS = 12;
|
|
7018
|
+
const queue = [framedPrompt];
|
|
7019
|
+
let rounds = 0;
|
|
7020
|
+
while (queue.length > 0 && rounds < MAX_SUBAGENT_ROUNDS) {
|
|
7021
|
+
const current = queue.shift();
|
|
7022
|
+
rounds++;
|
|
7023
|
+
const stream = this.client.streamChat({
|
|
7024
|
+
...chatId ? { chat_id: chatId } : {},
|
|
7025
|
+
prompt: current,
|
|
7026
|
+
model,
|
|
7027
|
+
tools: schemas,
|
|
7028
|
+
project_root: this.projectRoot,
|
|
7029
|
+
mode: this.mode
|
|
7030
|
+
});
|
|
7031
|
+
for await (const chunk of stream) {
|
|
7032
|
+
if (chunk.content) onContent(chunk.content);
|
|
7033
|
+
if (chunk.tool_use) {
|
|
7034
|
+
const name = chunk.tool_use.name;
|
|
7035
|
+
if (agent.tools !== void 0 && !agent.tools.includes(name)) {
|
|
7036
|
+
queue.push(
|
|
7037
|
+
`[TOOL_RESULT]: ${JSON.stringify({
|
|
7038
|
+
content: `Tool "${name}" is not in the "${agent.name}" agent's allowlist; refused.`,
|
|
7039
|
+
isError: true
|
|
7040
|
+
})}`
|
|
7041
|
+
);
|
|
7042
|
+
continue;
|
|
7043
|
+
}
|
|
7044
|
+
const result = await this.execute(name, chunk.tool_use.args, this.projectRoot);
|
|
7045
|
+
queue.push(`[TOOL_RESULT]: ${JSON.stringify(result)}`);
|
|
7046
|
+
}
|
|
7047
|
+
}
|
|
7048
|
+
}
|
|
7049
|
+
}
|
|
6429
7050
|
};
|
|
6430
7051
|
}
|
|
6431
7052
|
});
|
|
@@ -6471,8 +7092,8 @@ var init_Safety = __esm({
|
|
|
6471
7092
|
});
|
|
6472
7093
|
|
|
6473
7094
|
// ../core/src/ProjectConfig.ts
|
|
6474
|
-
import { homedir as
|
|
6475
|
-
import { join as
|
|
7095
|
+
import { homedir as homedir9 } from "os";
|
|
7096
|
+
import { join as join16, dirname as dirname3, parse as parsePath } from "path";
|
|
6476
7097
|
import { existsSync as existsSync11 } from "fs";
|
|
6477
7098
|
import { readFile as readFile11 } from "fs/promises";
|
|
6478
7099
|
async function readIfExists(path2) {
|
|
@@ -6486,7 +7107,7 @@ async function readIfExists(path2) {
|
|
|
6486
7107
|
}
|
|
6487
7108
|
async function findInDir(dir) {
|
|
6488
7109
|
for (const filename of FILENAMES) {
|
|
6489
|
-
const path2 =
|
|
7110
|
+
const path2 = join16(dir, filename);
|
|
6490
7111
|
const content = await readIfExists(path2);
|
|
6491
7112
|
if (content !== null) {
|
|
6492
7113
|
return { path: path2, filename, content };
|
|
@@ -6509,9 +7130,9 @@ async function findProjectConfig(start) {
|
|
|
6509
7130
|
return null;
|
|
6510
7131
|
}
|
|
6511
7132
|
async function findUserConfig() {
|
|
6512
|
-
const home =
|
|
7133
|
+
const home = homedir9();
|
|
6513
7134
|
if (!home) return null;
|
|
6514
|
-
const userDir =
|
|
7135
|
+
const userDir = join16(home, ".msapling");
|
|
6515
7136
|
return findInDir(userDir);
|
|
6516
7137
|
}
|
|
6517
7138
|
function buildCombined(user, project) {
|
|
@@ -7119,8 +7740,8 @@ var init_Mutex = __esm({
|
|
|
7119
7740
|
*/
|
|
7120
7741
|
acquire() {
|
|
7121
7742
|
let release3;
|
|
7122
|
-
const next = new Promise((
|
|
7123
|
-
release3 =
|
|
7743
|
+
const next = new Promise((resolve21) => {
|
|
7744
|
+
release3 = resolve21;
|
|
7124
7745
|
});
|
|
7125
7746
|
const entry = this._queue.then(() => release3);
|
|
7126
7747
|
this._queue = this._queue.then(() => next);
|
|
@@ -7158,8 +7779,8 @@ var init_lockfile = __esm({
|
|
|
7158
7779
|
});
|
|
7159
7780
|
|
|
7160
7781
|
// ../core/src/TrustStore.ts
|
|
7161
|
-
import { join as
|
|
7162
|
-
import { homedir as
|
|
7782
|
+
import { join as join17, dirname as dirname4 } from "path";
|
|
7783
|
+
import { homedir as homedir10, platform as platform3 } from "os";
|
|
7163
7784
|
import { existsSync as existsSync12, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
7164
7785
|
import { readFile as readFile12, writeFile as writeFile5, rename as rename2, chmod } from "fs/promises";
|
|
7165
7786
|
import { randomBytes as randomBytes9 } from "crypto";
|
|
@@ -7170,7 +7791,7 @@ var init_TrustStore = __esm({
|
|
|
7170
7791
|
init_esm_shims();
|
|
7171
7792
|
init_Mutex();
|
|
7172
7793
|
init_lockfile();
|
|
7173
|
-
USER_SETTINGS_PATH =
|
|
7794
|
+
USER_SETTINGS_PATH = join17(homedir10(), ".msapling", "settings.json");
|
|
7174
7795
|
TrustStore = class {
|
|
7175
7796
|
/** Current in-memory set of trusted `tool:command` keys. */
|
|
7176
7797
|
trusted = /* @__PURE__ */ new Set();
|
|
@@ -7327,9 +7948,118 @@ var init_TrustStore = __esm({
|
|
|
7327
7948
|
}
|
|
7328
7949
|
});
|
|
7329
7950
|
|
|
7951
|
+
// ../core/src/BackupIndex.ts
|
|
7952
|
+
import { join as join18 } from "path";
|
|
7953
|
+
import { homedir as homedir11 } from "os";
|
|
7954
|
+
import {
|
|
7955
|
+
readFileSync as readFileSync3,
|
|
7956
|
+
appendFileSync as appendFileSync2,
|
|
7957
|
+
existsSync as existsSync13,
|
|
7958
|
+
mkdirSync as mkdirSync3,
|
|
7959
|
+
copyFileSync
|
|
7960
|
+
} from "fs";
|
|
7961
|
+
import { randomBytes as randomBytes10 } from "crypto";
|
|
7962
|
+
function _setBackupDirOverride(dir) {
|
|
7963
|
+
_backupDirOverride = dir;
|
|
7964
|
+
}
|
|
7965
|
+
function getBackupDir() {
|
|
7966
|
+
return _backupDirOverride ?? join18(homedir11(), ".msapling", "backups");
|
|
7967
|
+
}
|
|
7968
|
+
function getManifestPath() {
|
|
7969
|
+
return join18(getBackupDir(), "manifest.jsonl");
|
|
7970
|
+
}
|
|
7971
|
+
function ensureBackupDir() {
|
|
7972
|
+
const dir = getBackupDir();
|
|
7973
|
+
if (!existsSync13(dir)) {
|
|
7974
|
+
mkdirSync3(dir, { recursive: true });
|
|
7975
|
+
}
|
|
7976
|
+
}
|
|
7977
|
+
function openTurn(label) {
|
|
7978
|
+
ensureBackupDir();
|
|
7979
|
+
_turnCounter += 1;
|
|
7980
|
+
const turnId = `turn-${Date.now()}-${randomBytes10(4).toString("hex")}`;
|
|
7981
|
+
const entry = {
|
|
7982
|
+
turnId,
|
|
7983
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7984
|
+
label: label ?? `Turn ${_turnCounter}`,
|
|
7985
|
+
backups: []
|
|
7986
|
+
};
|
|
7987
|
+
_openTurns.set(turnId, entry);
|
|
7988
|
+
return turnId;
|
|
7989
|
+
}
|
|
7990
|
+
function recordBackup(turnId, originalPath, backupPath) {
|
|
7991
|
+
const entry = _openTurns.get(turnId);
|
|
7992
|
+
if (!entry) return;
|
|
7993
|
+
entry.backups.push({ originalPath, backupPath });
|
|
7994
|
+
}
|
|
7995
|
+
function closeTurn(turnId) {
|
|
7996
|
+
const entry = _openTurns.get(turnId);
|
|
7997
|
+
if (!entry) return;
|
|
7998
|
+
_openTurns.delete(turnId);
|
|
7999
|
+
entry.closedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8000
|
+
try {
|
|
8001
|
+
ensureBackupDir();
|
|
8002
|
+
appendFileSync2(getManifestPath(), JSON.stringify(entry) + "\n", "utf8");
|
|
8003
|
+
} catch {
|
|
8004
|
+
}
|
|
8005
|
+
}
|
|
8006
|
+
function listCheckpoints() {
|
|
8007
|
+
const mp = getManifestPath();
|
|
8008
|
+
if (!existsSync13(mp)) return [];
|
|
8009
|
+
let raw;
|
|
8010
|
+
try {
|
|
8011
|
+
raw = readFileSync3(mp, "utf8");
|
|
8012
|
+
} catch {
|
|
8013
|
+
return [];
|
|
8014
|
+
}
|
|
8015
|
+
const entries = [];
|
|
8016
|
+
for (const line of raw.split("\n")) {
|
|
8017
|
+
const trimmed = line.trim();
|
|
8018
|
+
if (!trimmed) continue;
|
|
8019
|
+
try {
|
|
8020
|
+
entries.push(JSON.parse(trimmed));
|
|
8021
|
+
} catch {
|
|
8022
|
+
}
|
|
8023
|
+
}
|
|
8024
|
+
return entries.reverse().slice(0, MAX_CHECKPOINTS);
|
|
8025
|
+
}
|
|
8026
|
+
function restoreCheckpoint(entry) {
|
|
8027
|
+
const results = [];
|
|
8028
|
+
for (const rec of entry.backups) {
|
|
8029
|
+
if (!existsSync13(rec.backupPath)) {
|
|
8030
|
+
results.push(`Skip (backup file missing): ${rec.originalPath}`);
|
|
8031
|
+
continue;
|
|
8032
|
+
}
|
|
8033
|
+
try {
|
|
8034
|
+
copyFileSync(rec.backupPath, rec.originalPath);
|
|
8035
|
+
results.push(`Restored: ${rec.originalPath}`);
|
|
8036
|
+
} catch (e) {
|
|
8037
|
+
results.push(`Error restoring ${rec.originalPath}: ${e.message}`);
|
|
8038
|
+
}
|
|
8039
|
+
}
|
|
8040
|
+
return results;
|
|
8041
|
+
}
|
|
8042
|
+
function manifestPath() {
|
|
8043
|
+
return getManifestPath();
|
|
8044
|
+
}
|
|
8045
|
+
function backupDir() {
|
|
8046
|
+
return getBackupDir();
|
|
8047
|
+
}
|
|
8048
|
+
var MAX_CHECKPOINTS, _backupDirOverride, _openTurns, _turnCounter;
|
|
8049
|
+
var init_BackupIndex = __esm({
|
|
8050
|
+
"../core/src/BackupIndex.ts"() {
|
|
8051
|
+
"use strict";
|
|
8052
|
+
init_esm_shims();
|
|
8053
|
+
MAX_CHECKPOINTS = 50;
|
|
8054
|
+
_backupDirOverride = null;
|
|
8055
|
+
_openTurns = /* @__PURE__ */ new Map();
|
|
8056
|
+
_turnCounter = 0;
|
|
8057
|
+
}
|
|
8058
|
+
});
|
|
8059
|
+
|
|
7330
8060
|
// ../core/src/Storage/vault.ts
|
|
7331
|
-
import { join as
|
|
7332
|
-
import { chmodSync, existsSync as
|
|
8061
|
+
import { join as join19 } from "path";
|
|
8062
|
+
import { chmodSync, existsSync as existsSync14, renameSync as renameSync2, unlinkSync } from "fs";
|
|
7333
8063
|
import { writeFile as writeFile6, readFile as readFile13 } from "fs/promises";
|
|
7334
8064
|
import { createHash as createHash4 } from "crypto";
|
|
7335
8065
|
async function getKeytar() {
|
|
@@ -7343,7 +8073,7 @@ async function getKeytar() {
|
|
|
7343
8073
|
return _keytar;
|
|
7344
8074
|
}
|
|
7345
8075
|
async function saveToken(baseDir, token) {
|
|
7346
|
-
const filePath =
|
|
8076
|
+
const filePath = join19(baseDir, "vault", "token");
|
|
7347
8077
|
try {
|
|
7348
8078
|
const kt = await getKeytar();
|
|
7349
8079
|
if (!kt) throw new Error("keyring not loadable");
|
|
@@ -7352,12 +8082,12 @@ async function saveToken(baseDir, token) {
|
|
|
7352
8082
|
throw new KeychainUnavailableError(e instanceof Error ? e.message : String(e));
|
|
7353
8083
|
}
|
|
7354
8084
|
try {
|
|
7355
|
-
if (
|
|
8085
|
+
if (existsSync14(filePath)) unlinkSync(filePath);
|
|
7356
8086
|
} catch {
|
|
7357
8087
|
}
|
|
7358
8088
|
}
|
|
7359
8089
|
async function loadToken(baseDir) {
|
|
7360
|
-
const filePath =
|
|
8090
|
+
const filePath = join19(baseDir, "vault", "token");
|
|
7361
8091
|
let kt = null;
|
|
7362
8092
|
try {
|
|
7363
8093
|
kt = await getKeytar();
|
|
@@ -7368,7 +8098,7 @@ async function loadToken(baseDir) {
|
|
|
7368
8098
|
kt = null;
|
|
7369
8099
|
console.debug(`Keychain unavailable (${e instanceof Error ? e.message : String(e)})`);
|
|
7370
8100
|
}
|
|
7371
|
-
if (
|
|
8101
|
+
if (existsSync14(filePath)) {
|
|
7372
8102
|
const legacyToken = (await readFile13(filePath, "utf8")).trim();
|
|
7373
8103
|
if (!legacyToken) return null;
|
|
7374
8104
|
if (kt) {
|
|
@@ -7387,7 +8117,7 @@ async function loadToken(baseDir) {
|
|
|
7387
8117
|
return null;
|
|
7388
8118
|
}
|
|
7389
8119
|
async function clearToken(baseDir) {
|
|
7390
|
-
const filePath =
|
|
8120
|
+
const filePath = join19(baseDir, "vault", "token");
|
|
7391
8121
|
try {
|
|
7392
8122
|
const kt = await getKeytar();
|
|
7393
8123
|
if (kt) await kt.deletePassword(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT);
|
|
@@ -7395,7 +8125,7 @@ async function clearToken(baseDir) {
|
|
|
7395
8125
|
console.debug(`Keychain unavailable for deletion (${e instanceof Error ? e.message : String(e)})`);
|
|
7396
8126
|
}
|
|
7397
8127
|
try {
|
|
7398
|
-
if (
|
|
8128
|
+
if (existsSync14(filePath)) {
|
|
7399
8129
|
const fs3 = await import("fs/promises");
|
|
7400
8130
|
await fs3.unlink(filePath);
|
|
7401
8131
|
}
|
|
@@ -7417,8 +8147,8 @@ async function getOrCreateJournalKey() {
|
|
|
7417
8147
|
const buf = Buffer.from(existing, "base64");
|
|
7418
8148
|
if (buf.length === 32) return buf;
|
|
7419
8149
|
}
|
|
7420
|
-
const { randomBytes:
|
|
7421
|
-
const key =
|
|
8150
|
+
const { randomBytes: randomBytes16 } = await import("crypto");
|
|
8151
|
+
const key = randomBytes16(32);
|
|
7422
8152
|
await kt.setPassword(KEYCHAIN_SERVICE, KEYCHAIN_JOURNAL_ACCOUNT, key.toString("base64"));
|
|
7423
8153
|
return key;
|
|
7424
8154
|
} catch (e) {
|
|
@@ -7428,8 +8158,8 @@ async function getOrCreateJournalKey() {
|
|
|
7428
8158
|
}
|
|
7429
8159
|
async function writeVaultRef(baseDir, label, value) {
|
|
7430
8160
|
const hash = createHash4("sha256").update(value, "utf8").digest("hex");
|
|
7431
|
-
const objectPath =
|
|
7432
|
-
const refPath =
|
|
8161
|
+
const objectPath = join19(baseDir, "vault", "objects", hash);
|
|
8162
|
+
const refPath = join19(baseDir, "vault", "refs", label);
|
|
7433
8163
|
const refTmp = `${refPath}.tmp`;
|
|
7434
8164
|
await writeFile6(objectPath, value, "utf8");
|
|
7435
8165
|
if (process.platform !== "win32") {
|
|
@@ -7440,11 +8170,11 @@ async function writeVaultRef(baseDir, label, value) {
|
|
|
7440
8170
|
return hash;
|
|
7441
8171
|
}
|
|
7442
8172
|
async function readVaultRef(baseDir, label) {
|
|
7443
|
-
const refPath =
|
|
7444
|
-
if (!
|
|
8173
|
+
const refPath = join19(baseDir, "vault", "refs", label);
|
|
8174
|
+
if (!existsSync14(refPath)) return null;
|
|
7445
8175
|
const hash = (await readFile13(refPath, "utf8")).trim();
|
|
7446
|
-
const objectPath =
|
|
7447
|
-
if (!
|
|
8176
|
+
const objectPath = join19(baseDir, "vault", "objects", hash);
|
|
8177
|
+
if (!existsSync14(objectPath)) return null;
|
|
7448
8178
|
return readFile13(objectPath, "utf8");
|
|
7449
8179
|
}
|
|
7450
8180
|
var _keytar, KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT, KeychainUnavailableError, KEYCHAIN_JOURNAL_ACCOUNT;
|
|
@@ -7468,20 +8198,20 @@ var init_vault = __esm({
|
|
|
7468
8198
|
});
|
|
7469
8199
|
|
|
7470
8200
|
// ../core/src/Storage/recipes.ts
|
|
7471
|
-
import { join as
|
|
7472
|
-
import { existsSync as
|
|
8201
|
+
import { join as join20 } from "path";
|
|
8202
|
+
import { existsSync as existsSync15, renameSync as renameSync3 } from "fs";
|
|
7473
8203
|
import { writeFile as writeFile7, readFile as readFile14 } from "fs/promises";
|
|
7474
8204
|
import { createHash as createHash5 } from "crypto";
|
|
7475
8205
|
async function registerRecipe(baseDir, name, content) {
|
|
7476
8206
|
const hash = createHash5("sha256").update(content, "utf8").digest("hex");
|
|
7477
|
-
const objectPath =
|
|
7478
|
-
const indexPath =
|
|
8207
|
+
const objectPath = join20(baseDir, "cache", "recipes", "objects", hash);
|
|
8208
|
+
const indexPath = join20(baseDir, "cache", "recipes", "index.json");
|
|
7479
8209
|
const indexTmp = `${indexPath}.tmp`;
|
|
7480
|
-
if (!
|
|
8210
|
+
if (!existsSync15(objectPath)) {
|
|
7481
8211
|
await writeFile7(objectPath, content, "utf8");
|
|
7482
8212
|
}
|
|
7483
8213
|
let index = {};
|
|
7484
|
-
if (
|
|
8214
|
+
if (existsSync15(indexPath)) {
|
|
7485
8215
|
try {
|
|
7486
8216
|
index = JSON.parse(await readFile14(indexPath, "utf8"));
|
|
7487
8217
|
} catch {
|
|
@@ -7494,15 +8224,15 @@ async function registerRecipe(baseDir, name, content) {
|
|
|
7494
8224
|
return hash;
|
|
7495
8225
|
}
|
|
7496
8226
|
async function resolveRecipe(baseDir, nameOrRef) {
|
|
7497
|
-
const indexPath =
|
|
8227
|
+
const indexPath = join20(baseDir, "cache", "recipes", "index.json");
|
|
7498
8228
|
const atIdx = nameOrRef.indexOf("@");
|
|
7499
8229
|
if (atIdx !== -1) {
|
|
7500
8230
|
const hash2 = nameOrRef.slice(atIdx + 1);
|
|
7501
|
-
const objectPath2 =
|
|
7502
|
-
if (!
|
|
8231
|
+
const objectPath2 = join20(baseDir, "cache", "recipes", "objects", hash2);
|
|
8232
|
+
if (!existsSync15(objectPath2)) return null;
|
|
7503
8233
|
return { hash: hash2, content: await readFile14(objectPath2, "utf8") };
|
|
7504
8234
|
}
|
|
7505
|
-
if (!
|
|
8235
|
+
if (!existsSync15(indexPath)) return null;
|
|
7506
8236
|
let index;
|
|
7507
8237
|
try {
|
|
7508
8238
|
index = JSON.parse(await readFile14(indexPath, "utf8"));
|
|
@@ -7511,8 +8241,8 @@ async function resolveRecipe(baseDir, nameOrRef) {
|
|
|
7511
8241
|
}
|
|
7512
8242
|
const hash = index[nameOrRef];
|
|
7513
8243
|
if (!hash) return null;
|
|
7514
|
-
const objectPath =
|
|
7515
|
-
if (!
|
|
8244
|
+
const objectPath = join20(baseDir, "cache", "recipes", "objects", hash);
|
|
8245
|
+
if (!existsSync15(objectPath)) return null;
|
|
7516
8246
|
return { hash, content: await readFile14(objectPath, "utf8") };
|
|
7517
8247
|
}
|
|
7518
8248
|
var init_recipes = __esm({
|
|
@@ -7523,25 +8253,25 @@ var init_recipes = __esm({
|
|
|
7523
8253
|
});
|
|
7524
8254
|
|
|
7525
8255
|
// ../core/src/Storage/history.ts
|
|
7526
|
-
import { join as
|
|
7527
|
-
import { existsSync as
|
|
8256
|
+
import { join as join21, basename } from "path";
|
|
8257
|
+
import { existsSync as existsSync16, renameSync as renameSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
7528
8258
|
import { writeFile as writeFile8, readFile as readFile15, appendFile } from "fs/promises";
|
|
7529
|
-
import { createHash as createHash6, randomBytes as
|
|
8259
|
+
import { createHash as createHash6, randomBytes as randomBytes11 } from "crypto";
|
|
7530
8260
|
function hashLine(line) {
|
|
7531
8261
|
return createHash6("sha256").update(line, "utf8").digest("hex");
|
|
7532
8262
|
}
|
|
7533
8263
|
async function appendHistoryEntry(baseDir, historyMutex, content) {
|
|
7534
|
-
const path2 =
|
|
8264
|
+
const path2 = join21(baseDir, "history", "shell_history.jsonl");
|
|
7535
8265
|
return historyMutex.run(async () => {
|
|
7536
8266
|
let release3 = null;
|
|
7537
8267
|
try {
|
|
7538
|
-
if (!
|
|
8268
|
+
if (!existsSync16(path2)) {
|
|
7539
8269
|
await writeFile8(path2, "", "utf8");
|
|
7540
8270
|
}
|
|
7541
8271
|
release3 = await lock(path2, { retries: 5, retryWait: 50 });
|
|
7542
8272
|
let prevHash = null;
|
|
7543
8273
|
let seq = 1;
|
|
7544
|
-
if (
|
|
8274
|
+
if (existsSync16(path2)) {
|
|
7545
8275
|
const raw = (await readFile15(path2, "utf8")).trimEnd();
|
|
7546
8276
|
if (raw.length > 0) {
|
|
7547
8277
|
const lines = raw.split("\n");
|
|
@@ -7574,8 +8304,8 @@ async function appendHistoryEntry(baseDir, historyMutex, content) {
|
|
|
7574
8304
|
});
|
|
7575
8305
|
}
|
|
7576
8306
|
async function loadHistoryEntries(baseDir) {
|
|
7577
|
-
const path2 =
|
|
7578
|
-
if (!
|
|
8307
|
+
const path2 = join21(baseDir, "history", "shell_history.jsonl");
|
|
8308
|
+
if (!existsSync16(path2)) return [];
|
|
7579
8309
|
const raw = await readFile15(path2, "utf8");
|
|
7580
8310
|
const entries = [];
|
|
7581
8311
|
for (const line of raw.split("\n")) {
|
|
@@ -7607,10 +8337,10 @@ async function verifyHistory(baseDir) {
|
|
|
7607
8337
|
return breaks.length === 0 ? { ok: true } : { ok: false, breaks };
|
|
7608
8338
|
}
|
|
7609
8339
|
async function saveHistory(baseDir, historyMutex, history) {
|
|
7610
|
-
const path2 =
|
|
8340
|
+
const path2 = join21(baseDir, "history", "shell_history.json");
|
|
7611
8341
|
let release3;
|
|
7612
8342
|
try {
|
|
7613
|
-
if (!
|
|
8343
|
+
if (!existsSync16(path2)) writeFileSync3(path2, "[]", "utf8");
|
|
7614
8344
|
release3 = await lock(path2, { realpath: false, retries: 5, retryWait: 50 });
|
|
7615
8345
|
await historyMutex.run(async () => {
|
|
7616
8346
|
const tmpPath = `${path2}.tmp`;
|
|
@@ -7620,7 +8350,7 @@ async function saveHistory(baseDir, historyMutex, history) {
|
|
|
7620
8350
|
renameSync4(tmpPath, path2);
|
|
7621
8351
|
} catch (e) {
|
|
7622
8352
|
try {
|
|
7623
|
-
if (
|
|
8353
|
+
if (existsSync16(tmpPath)) {
|
|
7624
8354
|
const fs3 = await import("fs/promises");
|
|
7625
8355
|
await fs3.unlink(tmpPath);
|
|
7626
8356
|
}
|
|
@@ -7640,21 +8370,21 @@ async function saveHistory(baseDir, historyMutex, history) {
|
|
|
7640
8370
|
}
|
|
7641
8371
|
}
|
|
7642
8372
|
async function loadHistory(baseDir, historyMutex) {
|
|
7643
|
-
const path2 =
|
|
7644
|
-
if (!
|
|
8373
|
+
const path2 = join21(baseDir, "history", "shell_history.json");
|
|
8374
|
+
if (!existsSync16(path2)) return [];
|
|
7645
8375
|
let release3;
|
|
7646
8376
|
try {
|
|
7647
8377
|
release3 = await lock(path2, { realpath: false, retries: 5, retryWait: 50 });
|
|
7648
8378
|
return historyMutex.run(async () => {
|
|
7649
|
-
if (
|
|
8379
|
+
if (existsSync16(path2)) {
|
|
7650
8380
|
const text = await readFile15(path2, "utf8");
|
|
7651
8381
|
try {
|
|
7652
8382
|
return JSON.parse(text);
|
|
7653
8383
|
} catch (parseErr) {
|
|
7654
8384
|
const filename = basename(path2) || "shell_history.json";
|
|
7655
8385
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
7656
|
-
const suffix =
|
|
7657
|
-
const corruptBackupPath =
|
|
8386
|
+
const suffix = randomBytes11(4).toString("hex");
|
|
8387
|
+
const corruptBackupPath = join21(
|
|
7658
8388
|
baseDir,
|
|
7659
8389
|
"history",
|
|
7660
8390
|
`${filename}.corrupt.${stamp}-${suffix}.bak`
|
|
@@ -7689,15 +8419,15 @@ var init_history = __esm({
|
|
|
7689
8419
|
});
|
|
7690
8420
|
|
|
7691
8421
|
// ../core/src/Storage/permissions.ts
|
|
7692
|
-
import { join as
|
|
7693
|
-
import { existsSync as
|
|
8422
|
+
import { join as join22 } from "path";
|
|
8423
|
+
import { existsSync as existsSync17, renameSync as renameSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
7694
8424
|
import { writeFile as writeFile9, readFile as readFile16 } from "fs/promises";
|
|
7695
|
-
import { randomBytes as
|
|
8425
|
+
import { randomBytes as randomBytes12 } from "crypto";
|
|
7696
8426
|
async function savePermissions(baseDir, permissionsMutex, permissions) {
|
|
7697
|
-
const path2 =
|
|
8427
|
+
const path2 = join22(baseDir, "vault", "permissions.json");
|
|
7698
8428
|
let release3;
|
|
7699
8429
|
try {
|
|
7700
|
-
if (!
|
|
8430
|
+
if (!existsSync17(path2)) writeFileSync4(path2, "{}", "utf8");
|
|
7701
8431
|
release3 = await lock(path2, { realpath: false, retries: 5, retryWait: 50 });
|
|
7702
8432
|
await permissionsMutex.run(async () => {
|
|
7703
8433
|
const tmpPath = `${path2}.tmp`;
|
|
@@ -7707,7 +8437,7 @@ async function savePermissions(baseDir, permissionsMutex, permissions) {
|
|
|
7707
8437
|
renameSync5(tmpPath, path2);
|
|
7708
8438
|
} catch (e) {
|
|
7709
8439
|
try {
|
|
7710
|
-
if (
|
|
8440
|
+
if (existsSync17(tmpPath)) {
|
|
7711
8441
|
const fs3 = await import("fs/promises");
|
|
7712
8442
|
await fs3.unlink(tmpPath);
|
|
7713
8443
|
}
|
|
@@ -7727,21 +8457,21 @@ async function savePermissions(baseDir, permissionsMutex, permissions) {
|
|
|
7727
8457
|
}
|
|
7728
8458
|
}
|
|
7729
8459
|
async function loadPermissions(baseDir, permissionsMutex) {
|
|
7730
|
-
const path2 =
|
|
7731
|
-
if (!
|
|
8460
|
+
const path2 = join22(baseDir, "vault", "permissions.json");
|
|
8461
|
+
if (!existsSync17(path2)) return { trustedCommands: [], trustedPaths: [] };
|
|
7732
8462
|
let release3;
|
|
7733
8463
|
try {
|
|
7734
8464
|
release3 = await lock(path2, { realpath: false, retries: 5, retryWait: 50 });
|
|
7735
8465
|
return permissionsMutex.run(async () => {
|
|
7736
|
-
if (
|
|
8466
|
+
if (existsSync17(path2)) {
|
|
7737
8467
|
const text = await readFile16(path2, "utf8");
|
|
7738
8468
|
try {
|
|
7739
8469
|
return JSON.parse(text);
|
|
7740
8470
|
} catch (parseErr) {
|
|
7741
8471
|
const filename = path2.split("/").pop() || "permissions.json";
|
|
7742
8472
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
7743
|
-
const suffix =
|
|
7744
|
-
const corruptBackupPath =
|
|
8473
|
+
const suffix = randomBytes12(4).toString("hex");
|
|
8474
|
+
const corruptBackupPath = join22(
|
|
7745
8475
|
baseDir,
|
|
7746
8476
|
"vault",
|
|
7747
8477
|
`${filename}.corrupt.${stamp}-${suffix}.bak`
|
|
@@ -7776,11 +8506,11 @@ var init_permissions = __esm({
|
|
|
7776
8506
|
});
|
|
7777
8507
|
|
|
7778
8508
|
// ../core/src/Storage.ts
|
|
7779
|
-
import { join as
|
|
7780
|
-
import { homedir as
|
|
8509
|
+
import { join as join23 } from "path";
|
|
8510
|
+
import { homedir as homedir12 } from "os";
|
|
7781
8511
|
import { chmodSync as chmodSync2 } from "fs";
|
|
7782
8512
|
import { mkdir as mkdir6, writeFile as writeFile10 } from "fs/promises";
|
|
7783
|
-
import { randomBytes as
|
|
8513
|
+
import { randomBytes as randomBytes13 } from "crypto";
|
|
7784
8514
|
var StorageManager;
|
|
7785
8515
|
var init_Storage = __esm({
|
|
7786
8516
|
"../core/src/Storage.ts"() {
|
|
@@ -7807,7 +8537,7 @@ var init_Storage = __esm({
|
|
|
7807
8537
|
*/
|
|
7808
8538
|
_ready;
|
|
7809
8539
|
constructor() {
|
|
7810
|
-
this.baseDir =
|
|
8540
|
+
this.baseDir = join23(homedir12(), ".msapling");
|
|
7811
8541
|
this._ready = this.ensureDirs();
|
|
7812
8542
|
}
|
|
7813
8543
|
async ensureDirs() {
|
|
@@ -7824,7 +8554,7 @@ var init_Storage = __esm({
|
|
|
7824
8554
|
"cache/recipes/objects"
|
|
7825
8555
|
];
|
|
7826
8556
|
for (const sub of subdirs) {
|
|
7827
|
-
await mkdir6(
|
|
8557
|
+
await mkdir6(join23(this.baseDir, sub), { recursive: true });
|
|
7828
8558
|
}
|
|
7829
8559
|
if (process.platform !== "win32") {
|
|
7830
8560
|
chmodSync2(this.baseDir, 448);
|
|
@@ -7901,8 +8631,8 @@ var init_Storage = __esm({
|
|
|
7901
8631
|
await this._ready;
|
|
7902
8632
|
const filename = filePath.split("/").pop() || "file";
|
|
7903
8633
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
7904
|
-
const suffix =
|
|
7905
|
-
const backupPath =
|
|
8634
|
+
const suffix = randomBytes13(4).toString("hex");
|
|
8635
|
+
const backupPath = join23(this.baseDir, "backups", `${filename}.backup-${stamp}-${suffix}.bak`);
|
|
7906
8636
|
await writeFile10(backupPath, content, "utf8");
|
|
7907
8637
|
if (process.platform !== "win32") {
|
|
7908
8638
|
chmodSync2(backupPath, 384);
|
|
@@ -7933,15 +8663,15 @@ var init_journalCrypto = __esm({
|
|
|
7933
8663
|
});
|
|
7934
8664
|
|
|
7935
8665
|
// ../core/src/Settings.ts
|
|
7936
|
-
import { homedir as
|
|
7937
|
-
import { join as
|
|
7938
|
-
import { existsSync as
|
|
8666
|
+
import { homedir as homedir13 } from "os";
|
|
8667
|
+
import { join as join24 } from "path";
|
|
8668
|
+
import { existsSync as existsSync18 } from "fs";
|
|
7939
8669
|
import * as fs from "fs";
|
|
7940
8670
|
import { readFile as readFile17 } from "fs/promises";
|
|
7941
|
-
import { randomBytes as
|
|
8671
|
+
import { randomBytes as randomBytes14 } from "crypto";
|
|
7942
8672
|
function backupStaleFile(p) {
|
|
7943
8673
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
7944
|
-
const suffix =
|
|
8674
|
+
const suffix = randomBytes14(4).toString("hex");
|
|
7945
8675
|
fs.renameSync(p, `${p}.broken-${stamp}-${suffix}`);
|
|
7946
8676
|
}
|
|
7947
8677
|
function ensureConfigDir(p) {
|
|
@@ -7964,7 +8694,7 @@ function ensureConfigDir(p) {
|
|
|
7964
8694
|
}
|
|
7965
8695
|
async function readJson(path2) {
|
|
7966
8696
|
try {
|
|
7967
|
-
if (!
|
|
8697
|
+
if (!existsSync18(path2)) return null;
|
|
7968
8698
|
const text = await readFile17(path2, "utf8");
|
|
7969
8699
|
if (!text.trim()) return null;
|
|
7970
8700
|
return JSON.parse(text);
|
|
@@ -8019,9 +8749,9 @@ function mergeSettings(base, override) {
|
|
|
8019
8749
|
}
|
|
8020
8750
|
async function loadSettings(cwd = process.cwd(), env = process.env, warn) {
|
|
8021
8751
|
const sources = [];
|
|
8022
|
-
const home = env.HOME || env.USERPROFILE || process.env.HOME || process.env.USERPROFILE ||
|
|
8023
|
-
const userPath =
|
|
8024
|
-
const projectPath =
|
|
8752
|
+
const home = env.HOME || env.USERPROFILE || process.env.HOME || process.env.USERPROFILE || homedir13() || ".";
|
|
8753
|
+
const userPath = join24(home, ".msapling", "settings.json");
|
|
8754
|
+
const projectPath = join24(cwd, ".msapling", "settings.json");
|
|
8025
8755
|
const [user, project] = await Promise.all([readJson(userPath), readJson(projectPath)]);
|
|
8026
8756
|
if (user) sources.push(userPath);
|
|
8027
8757
|
if (project) sources.push(projectPath);
|
|
@@ -8074,7 +8804,7 @@ var init_Settings = __esm({
|
|
|
8074
8804
|
});
|
|
8075
8805
|
|
|
8076
8806
|
// ../core/src/mcp/client.ts
|
|
8077
|
-
import { spawn as
|
|
8807
|
+
import { spawn as spawn9 } from "child_process";
|
|
8078
8808
|
var PROTOCOL_VERSION, CLIENT_INFO, MCPClientError, MCPClient, MCPRegistry;
|
|
8079
8809
|
var init_client = __esm({
|
|
8080
8810
|
"../core/src/mcp/client.ts"() {
|
|
@@ -8110,7 +8840,7 @@ var init_client = __esm({
|
|
|
8110
8840
|
if (this.proc) return;
|
|
8111
8841
|
const env = { ...process.env, ...this.config.env ?? {} };
|
|
8112
8842
|
try {
|
|
8113
|
-
this.proc =
|
|
8843
|
+
this.proc = spawn9(this.config.command, this.config.args ?? [], {
|
|
8114
8844
|
stdio: ["pipe", "pipe", "pipe"],
|
|
8115
8845
|
env
|
|
8116
8846
|
});
|
|
@@ -8184,7 +8914,7 @@ var init_client = __esm({
|
|
|
8184
8914
|
if (!this.proc) throw new MCPClientError(`MCP server "${this.name}" not started`);
|
|
8185
8915
|
const id = this.nextId++;
|
|
8186
8916
|
const frame = { jsonrpc: "2.0", id, method, params };
|
|
8187
|
-
return new Promise((
|
|
8917
|
+
return new Promise((resolve21, reject) => {
|
|
8188
8918
|
const timer = setTimeout(() => {
|
|
8189
8919
|
this.pending.delete(id);
|
|
8190
8920
|
reject(new MCPClientError(`MCP request ${method} timed out after ${timeoutMs}ms`));
|
|
@@ -8192,7 +8922,7 @@ var init_client = __esm({
|
|
|
8192
8922
|
this.pending.set(id, {
|
|
8193
8923
|
resolve: (v) => {
|
|
8194
8924
|
clearTimeout(timer);
|
|
8195
|
-
|
|
8925
|
+
resolve21(v);
|
|
8196
8926
|
},
|
|
8197
8927
|
reject: (e) => {
|
|
8198
8928
|
clearTimeout(timer);
|
|
@@ -8219,7 +8949,7 @@ var init_client = __esm({
|
|
|
8219
8949
|
if (!this.proc?.stdout) return;
|
|
8220
8950
|
const stdout = this.proc.stdout;
|
|
8221
8951
|
const decoder = new TextDecoder();
|
|
8222
|
-
return new Promise((
|
|
8952
|
+
return new Promise((resolve21) => {
|
|
8223
8953
|
stdout.on("data", (chunk) => {
|
|
8224
8954
|
this.buffer += decoder.decode(chunk, { stream: true });
|
|
8225
8955
|
let idx;
|
|
@@ -8230,8 +8960,8 @@ var init_client = __esm({
|
|
|
8230
8960
|
this.handleFrame(line);
|
|
8231
8961
|
}
|
|
8232
8962
|
});
|
|
8233
|
-
stdout.on("end", () =>
|
|
8234
|
-
stdout.on("error", () =>
|
|
8963
|
+
stdout.on("end", () => resolve21());
|
|
8964
|
+
stdout.on("error", () => resolve21());
|
|
8235
8965
|
});
|
|
8236
8966
|
}
|
|
8237
8967
|
handleFrame(line) {
|
|
@@ -8371,6 +9101,8 @@ __export(src_exports2, {
|
|
|
8371
9101
|
APPROVAL_GATED: () => APPROVAL_GATED,
|
|
8372
9102
|
Agent: () => Agent,
|
|
8373
9103
|
AsyncMutex: () => AsyncMutex,
|
|
9104
|
+
BackgroundShellRegistry: () => BackgroundShellRegistry,
|
|
9105
|
+
BashBackgroundTool: () => BashBackgroundTool,
|
|
8374
9106
|
BashTool: () => BashTool,
|
|
8375
9107
|
ContextBudget: () => ContextBudget,
|
|
8376
9108
|
DEFAULT_SETTINGS: () => DEFAULT_SETTINGS,
|
|
@@ -8382,6 +9114,8 @@ __export(src_exports2, {
|
|
|
8382
9114
|
GrepSearchTool: () => GrepSearchTool,
|
|
8383
9115
|
HookRunner: () => HookRunner,
|
|
8384
9116
|
KeychainUnavailableError: () => KeychainUnavailableError,
|
|
9117
|
+
KillBackgroundShellTool: () => KillBackgroundShellTool,
|
|
9118
|
+
ListBackgroundShellsTool: () => ListBackgroundShellsTool,
|
|
8385
9119
|
ListDirectoryTool: () => ListDirectoryTool,
|
|
8386
9120
|
MAX_PLAN_CHARS: () => MAX_PLAN_CHARS,
|
|
8387
9121
|
MCPClient: () => MCPClient,
|
|
@@ -8392,8 +9126,10 @@ __export(src_exports2, {
|
|
|
8392
9126
|
NotebookEditTool: () => NotebookEditTool,
|
|
8393
9127
|
NotebookReadTool: () => NotebookReadTool,
|
|
8394
9128
|
PatchFileTool: () => PatchFileTool,
|
|
9129
|
+
ReadBackgroundShellTool: () => ReadBackgroundShellTool,
|
|
8395
9130
|
StorageManager: () => StorageManager,
|
|
8396
9131
|
SwarmManager: () => SwarmManager,
|
|
9132
|
+
TOOL_NAME_ALIASES: () => TOOL_NAME_ALIASES,
|
|
8397
9133
|
TodoReadTool: () => TodoReadTool,
|
|
8398
9134
|
TodoStore: () => TodoStore,
|
|
8399
9135
|
TodoWriteTool: () => TodoWriteTool,
|
|
@@ -8402,16 +9138,31 @@ __export(src_exports2, {
|
|
|
8402
9138
|
WebFetchTool: () => WebFetchTool,
|
|
8403
9139
|
WebSearchTool: () => WebSearchTool,
|
|
8404
9140
|
WriteFileTool: () => WriteFileTool,
|
|
9141
|
+
_setBackupDirOverride: () => _setBackupDirOverride,
|
|
9142
|
+
backupDir: () => backupDir,
|
|
8405
9143
|
buildCompactionPrompt: () => buildCompactionPrompt,
|
|
8406
9144
|
buildHwContext: () => buildHwContext,
|
|
9145
|
+
closeTurn: () => closeTurn,
|
|
9146
|
+
discoverAgentFiles: () => discoverAgentFiles,
|
|
8407
9147
|
ensureConfigDir: () => ensureConfigDir,
|
|
9148
|
+
findNamedAgent: () => findNamedAgent,
|
|
9149
|
+
formatBackgroundShells: () => formatBackgroundShells,
|
|
8408
9150
|
formatCell: () => formatCell,
|
|
8409
9151
|
formatNotebookHeader: () => formatNotebookHeader,
|
|
8410
9152
|
formatTodos: () => formatTodos,
|
|
8411
9153
|
getOrCreateJournalKey: () => getOrCreateJournalKey,
|
|
8412
9154
|
initJournalEncryption: () => initJournalEncryption,
|
|
9155
|
+
listCheckpoints: () => listCheckpoints,
|
|
9156
|
+
loadNamedAgents: () => loadNamedAgents,
|
|
8413
9157
|
loadProjectConfig: () => loadProjectConfig,
|
|
8414
9158
|
loadSettings: () => loadSettings,
|
|
9159
|
+
manifestPath: () => manifestPath,
|
|
9160
|
+
normalizeToolName: () => normalizeToolName,
|
|
9161
|
+
openTurn: () => openTurn,
|
|
9162
|
+
parseAgentFile: () => parseAgentFile,
|
|
9163
|
+
parseToolsValue: () => parseToolsValue,
|
|
9164
|
+
recordBackup: () => recordBackup,
|
|
9165
|
+
restoreCheckpoint: () => restoreCheckpoint,
|
|
8415
9166
|
takeSnapshot: () => takeSnapshot
|
|
8416
9167
|
});
|
|
8417
9168
|
var init_src3 = __esm({
|
|
@@ -8420,9 +9171,11 @@ var init_src3 = __esm({
|
|
|
8420
9171
|
init_esm_shims();
|
|
8421
9172
|
init_ToolExecutor();
|
|
8422
9173
|
init_Agent();
|
|
9174
|
+
init_namedAgents();
|
|
8423
9175
|
init_HardwareMonitor();
|
|
8424
9176
|
init_TrustStore();
|
|
8425
9177
|
init_ContextBudget();
|
|
9178
|
+
init_BackupIndex();
|
|
8426
9179
|
init_Storage();
|
|
8427
9180
|
init_Storage();
|
|
8428
9181
|
init_journalCrypto();
|
|
@@ -8442,6 +9195,7 @@ var init_src3 = __esm({
|
|
|
8442
9195
|
init_WebSearchTool();
|
|
8443
9196
|
init_PlanModeTools();
|
|
8444
9197
|
init_BashTool();
|
|
9198
|
+
init_BackgroundShellTool();
|
|
8445
9199
|
init_NotebookReadTool();
|
|
8446
9200
|
init_NotebookEditTool();
|
|
8447
9201
|
init_MultiEditFileTool();
|
|
@@ -8466,7 +9220,7 @@ function setRawModeGuarded(stdin, mode) {
|
|
|
8466
9220
|
}
|
|
8467
9221
|
}
|
|
8468
9222
|
async function promptPassword(prompt4) {
|
|
8469
|
-
return new Promise((
|
|
9223
|
+
return new Promise((resolve21) => {
|
|
8470
9224
|
const stdin = process.stdin;
|
|
8471
9225
|
const stdout = process.stdout;
|
|
8472
9226
|
stdout.write(prompt4);
|
|
@@ -8480,7 +9234,7 @@ async function promptPassword(prompt4) {
|
|
|
8480
9234
|
setRawModeGuarded(stdin, wasRaw);
|
|
8481
9235
|
stdin.removeListener("data", onData);
|
|
8482
9236
|
stdout.write("\n");
|
|
8483
|
-
|
|
9237
|
+
resolve21(value);
|
|
8484
9238
|
};
|
|
8485
9239
|
const onData = (chunk) => {
|
|
8486
9240
|
try {
|
|
@@ -8582,7 +9336,7 @@ async function loginWithGithubDevice(context) {
|
|
|
8582
9336
|
const deadline = Date.now() + expires_in * 1e3;
|
|
8583
9337
|
let githubToken = null;
|
|
8584
9338
|
while (Date.now() < deadline) {
|
|
8585
|
-
await new Promise((
|
|
9339
|
+
await new Promise((resolve21) => setTimeout(resolve21, pollMs));
|
|
8586
9340
|
let tokenResp;
|
|
8587
9341
|
try {
|
|
8588
9342
|
tokenResp = await fetch(GITHUB_TOKEN_URL, {
|
|
@@ -8606,7 +9360,7 @@ async function loginWithGithubDevice(context) {
|
|
|
8606
9360
|
if (tokenData.error === "authorization_pending") continue;
|
|
8607
9361
|
if (tokenData.error === "slow_down") {
|
|
8608
9362
|
pollMs += 5e3;
|
|
8609
|
-
await new Promise((
|
|
9363
|
+
await new Promise((resolve21) => setTimeout(resolve21, 5e3));
|
|
8610
9364
|
continue;
|
|
8611
9365
|
}
|
|
8612
9366
|
context.addMessage("system", `GitHub auth error: ${tokenData.error_description || tokenData.error}`);
|
|
@@ -8951,9 +9705,9 @@ var init_unlock = __esm({
|
|
|
8951
9705
|
});
|
|
8952
9706
|
|
|
8953
9707
|
// src/commands/doctor.ts
|
|
8954
|
-
import { homedir as
|
|
8955
|
-
import { join as
|
|
8956
|
-
import { existsSync as
|
|
9708
|
+
import { homedir as homedir14 } from "os";
|
|
9709
|
+
import { join as join25 } from "path";
|
|
9710
|
+
import { existsSync as existsSync20 } from "fs";
|
|
8957
9711
|
import { readFile as readFile18 } from "fs/promises";
|
|
8958
9712
|
async function checkApiHealth(client) {
|
|
8959
9713
|
try {
|
|
@@ -8979,9 +9733,9 @@ async function checkAuthStatus(client) {
|
|
|
8979
9733
|
}
|
|
8980
9734
|
}
|
|
8981
9735
|
async function checkSettingsFile() {
|
|
8982
|
-
const settingsPath =
|
|
9736
|
+
const settingsPath = join25(homedir14(), ".msapling", "settings.json");
|
|
8983
9737
|
try {
|
|
8984
|
-
if (!
|
|
9738
|
+
if (!existsSync20(settingsPath)) {
|
|
8985
9739
|
return { ok: false, message: `Not found: ${settingsPath}` };
|
|
8986
9740
|
}
|
|
8987
9741
|
const text = await readFile18(settingsPath, "utf8");
|
|
@@ -9656,7 +10410,7 @@ function setRawModeGuarded2(stdin, mode) {
|
|
|
9656
10410
|
}
|
|
9657
10411
|
}
|
|
9658
10412
|
async function promptSecret(prompt4) {
|
|
9659
|
-
return new Promise((
|
|
10413
|
+
return new Promise((resolve21) => {
|
|
9660
10414
|
const stdin = process.stdin;
|
|
9661
10415
|
const stdout = process.stdout;
|
|
9662
10416
|
stdout.write(prompt4);
|
|
@@ -9669,12 +10423,12 @@ async function promptSecret(prompt4) {
|
|
|
9669
10423
|
setRawModeGuarded2(stdin, wasRaw);
|
|
9670
10424
|
stdin.removeListener("data", onData);
|
|
9671
10425
|
stdout.write("\n");
|
|
9672
|
-
|
|
10426
|
+
resolve21(secret);
|
|
9673
10427
|
} else if (char === "") {
|
|
9674
10428
|
setRawModeGuarded2(stdin, wasRaw);
|
|
9675
10429
|
stdin.removeListener("data", onData);
|
|
9676
10430
|
stdout.write("\n");
|
|
9677
|
-
|
|
10431
|
+
resolve21("");
|
|
9678
10432
|
} else if (char === "\x7F" || char === "\b") {
|
|
9679
10433
|
secret = secret.slice(0, -1);
|
|
9680
10434
|
} else if (char >= " " && char <= "~") {
|
|
@@ -9834,8 +10588,8 @@ var init_memories = __esm({
|
|
|
9834
10588
|
|
|
9835
10589
|
// src/commands/mdrive.ts
|
|
9836
10590
|
import { readFile as readFile19, writeFile as writeFile11 } from "fs/promises";
|
|
9837
|
-
import { existsSync as
|
|
9838
|
-
import { basename as basename2, resolve as
|
|
10591
|
+
import { existsSync as existsSync21 } from "fs";
|
|
10592
|
+
import { basename as basename2, resolve as resolve15, relative as relative15, isAbsolute as isAbsolute15, sep as sep3 } from "path";
|
|
9839
10593
|
function formatBytes(b) {
|
|
9840
10594
|
if (!b) return "0";
|
|
9841
10595
|
if (b < 1024) return `${b}`;
|
|
@@ -9843,10 +10597,10 @@ function formatBytes(b) {
|
|
|
9843
10597
|
return `${(b / 1024 / 1024).toFixed(1)}M`;
|
|
9844
10598
|
}
|
|
9845
10599
|
function containLocalPath(targetPath, root = process.cwd()) {
|
|
9846
|
-
const resolvedRoot =
|
|
9847
|
-
const resolved =
|
|
9848
|
-
const rel =
|
|
9849
|
-
if (rel === ".." || rel.startsWith(`..${sep3}`) || rel.startsWith("../") ||
|
|
10600
|
+
const resolvedRoot = resolve15(root);
|
|
10601
|
+
const resolved = isAbsolute15(targetPath) ? resolve15(targetPath) : resolve15(resolvedRoot, targetPath);
|
|
10602
|
+
const rel = relative15(resolvedRoot, resolved);
|
|
10603
|
+
if (rel === ".." || rel.startsWith(`..${sep3}`) || rel.startsWith("../") || isAbsolute15(rel)) {
|
|
9850
10604
|
return null;
|
|
9851
10605
|
}
|
|
9852
10606
|
return resolved;
|
|
@@ -9927,7 +10681,7 @@ var init_mdrive = __esm({
|
|
|
9927
10681
|
context.addMessage("error", `Refusing to read local file outside the working directory: ${local}`);
|
|
9928
10682
|
return;
|
|
9929
10683
|
}
|
|
9930
|
-
if (!
|
|
10684
|
+
if (!existsSync21(absLocal)) {
|
|
9931
10685
|
context.addMessage("error", `Local file not found: ${absLocal}`);
|
|
9932
10686
|
return;
|
|
9933
10687
|
}
|
|
@@ -10048,14 +10802,14 @@ var init_clear = __esm({
|
|
|
10048
10802
|
});
|
|
10049
10803
|
|
|
10050
10804
|
// src/commands/mode.ts
|
|
10051
|
-
import { homedir as
|
|
10052
|
-
import { join as
|
|
10053
|
-
import { existsSync as
|
|
10805
|
+
import { homedir as homedir15 } from "os";
|
|
10806
|
+
import { join as join26 } from "path";
|
|
10807
|
+
import { existsSync as existsSync22 } from "fs";
|
|
10054
10808
|
import { readFile as readFile20, writeFile as writeFile12, mkdir as mkdir7 } from "fs/promises";
|
|
10055
10809
|
async function persistApprovalMode(mode, ttlMs) {
|
|
10056
10810
|
try {
|
|
10057
10811
|
let existing = {};
|
|
10058
|
-
if (
|
|
10812
|
+
if (existsSync22(SETTINGS_PATH)) {
|
|
10059
10813
|
const text = await readFile20(SETTINGS_PATH, "utf8");
|
|
10060
10814
|
if (text.trim()) {
|
|
10061
10815
|
existing = JSON.parse(text);
|
|
@@ -10067,8 +10821,8 @@ async function persistApprovalMode(mode, ttlMs) {
|
|
|
10067
10821
|
...ttlMs && { ttlMs }
|
|
10068
10822
|
};
|
|
10069
10823
|
existing.approvalMode = entry;
|
|
10070
|
-
const settingsDir =
|
|
10071
|
-
if (!
|
|
10824
|
+
const settingsDir = join26(homedir15(), ".msapling");
|
|
10825
|
+
if (!existsSync22(settingsDir)) {
|
|
10072
10826
|
await mkdir7(settingsDir, { recursive: true });
|
|
10073
10827
|
}
|
|
10074
10828
|
await writeFile12(SETTINGS_PATH, JSON.stringify(existing, null, 2), "utf8");
|
|
@@ -10080,7 +10834,7 @@ var init_mode = __esm({
|
|
|
10080
10834
|
"src/commands/mode.ts"() {
|
|
10081
10835
|
"use strict";
|
|
10082
10836
|
init_esm_shims();
|
|
10083
|
-
SETTINGS_PATH =
|
|
10837
|
+
SETTINGS_PATH = join26(homedir15(), ".msapling", "settings.json");
|
|
10084
10838
|
modeCommand = {
|
|
10085
10839
|
name: "mode",
|
|
10086
10840
|
args: "[default|plan|acceptEdits|bypassPermissions] [...options]",
|
|
@@ -10502,8 +11256,8 @@ var init_compact = __esm({
|
|
|
10502
11256
|
});
|
|
10503
11257
|
|
|
10504
11258
|
// src/commands/init.ts
|
|
10505
|
-
import { join as
|
|
10506
|
-
import { existsSync as
|
|
11259
|
+
import { join as join27 } from "path";
|
|
11260
|
+
import { existsSync as existsSync23 } from "fs";
|
|
10507
11261
|
import { writeFile as writeFile13 } from "fs/promises";
|
|
10508
11262
|
var initCommand;
|
|
10509
11263
|
var init_init = __esm({
|
|
@@ -10517,8 +11271,8 @@ var init_init = __esm({
|
|
|
10517
11271
|
handler: async (args2, context) => {
|
|
10518
11272
|
try {
|
|
10519
11273
|
const cwd = process.cwd();
|
|
10520
|
-
const path2 =
|
|
10521
|
-
if (
|
|
11274
|
+
const path2 = join27(cwd, "MSAPLING.md");
|
|
11275
|
+
if (existsSync23(path2)) {
|
|
10522
11276
|
context.addMessage("error", "MSAPLING.md already exists in current directory.");
|
|
10523
11277
|
return;
|
|
10524
11278
|
}
|
|
@@ -10544,7 +11298,7 @@ var init_init = __esm({
|
|
|
10544
11298
|
});
|
|
10545
11299
|
|
|
10546
11300
|
// src/commands/review.ts
|
|
10547
|
-
import { existsSync as
|
|
11301
|
+
import { existsSync as existsSync24 } from "fs";
|
|
10548
11302
|
import { readFile as readFile21 } from "fs/promises";
|
|
10549
11303
|
var reviewCommand;
|
|
10550
11304
|
var init_review = __esm({
|
|
@@ -10564,7 +11318,7 @@ var init_review = __esm({
|
|
|
10564
11318
|
}
|
|
10565
11319
|
let content = "";
|
|
10566
11320
|
try {
|
|
10567
|
-
if (
|
|
11321
|
+
if (existsSync24(target)) {
|
|
10568
11322
|
content = await readFile21(target, "utf8");
|
|
10569
11323
|
} else {
|
|
10570
11324
|
content = `Review target: ${target}`;
|
|
@@ -10658,15 +11412,15 @@ var init_swarm = __esm({
|
|
|
10658
11412
|
|
|
10659
11413
|
// src/commands/recipe.ts
|
|
10660
11414
|
import { parse as parseYaml } from "yaml";
|
|
10661
|
-
import { existsSync as
|
|
11415
|
+
import { existsSync as existsSync25 } from "fs";
|
|
10662
11416
|
import { readFile as readFile22 } from "fs/promises";
|
|
10663
|
-
import { join as
|
|
11417
|
+
import { join as join28 } from "path";
|
|
10664
11418
|
function findRecipe(name, cwd) {
|
|
10665
11419
|
for (const dir of RECIPE_DIRS) {
|
|
10666
11420
|
for (const suffix of NAME_SUFFIXES) {
|
|
10667
11421
|
for (const ext of FILE_EXTS) {
|
|
10668
|
-
const p =
|
|
10669
|
-
if (
|
|
11422
|
+
const p = join28(cwd, dir, `${name}${suffix}${ext}`);
|
|
11423
|
+
if (existsSync25(p)) return p;
|
|
10670
11424
|
}
|
|
10671
11425
|
}
|
|
10672
11426
|
}
|
|
@@ -10779,13 +11533,13 @@ ${rendered}` : rendered;
|
|
|
10779
11533
|
});
|
|
10780
11534
|
|
|
10781
11535
|
// src/commands/skill.ts
|
|
10782
|
-
import { existsSync as
|
|
11536
|
+
import { existsSync as existsSync26, readdirSync as readdirSync3, statSync as statSync7 } from "fs";
|
|
10783
11537
|
import { readFile as readFile23 } from "fs/promises";
|
|
10784
|
-
import { join as
|
|
11538
|
+
import { join as join29, resolve as resolve16 } from "path";
|
|
10785
11539
|
function findSkillsRoot(cwd) {
|
|
10786
11540
|
for (const candidate of SKILLS_DIRS) {
|
|
10787
|
-
const full =
|
|
10788
|
-
if (
|
|
11541
|
+
const full = resolve16(cwd, candidate);
|
|
11542
|
+
if (existsSync26(full) && statSync7(full).isDirectory()) return full;
|
|
10789
11543
|
}
|
|
10790
11544
|
return null;
|
|
10791
11545
|
}
|
|
@@ -10793,28 +11547,28 @@ function listAllSkills(root) {
|
|
|
10793
11547
|
const out = [];
|
|
10794
11548
|
let domains;
|
|
10795
11549
|
try {
|
|
10796
|
-
domains =
|
|
11550
|
+
domains = readdirSync3(root);
|
|
10797
11551
|
} catch {
|
|
10798
11552
|
return out;
|
|
10799
11553
|
}
|
|
10800
11554
|
for (const domain of domains) {
|
|
10801
|
-
const dir =
|
|
11555
|
+
const dir = join29(root, domain);
|
|
10802
11556
|
let s;
|
|
10803
11557
|
try {
|
|
10804
|
-
s =
|
|
11558
|
+
s = statSync7(dir);
|
|
10805
11559
|
} catch {
|
|
10806
11560
|
continue;
|
|
10807
11561
|
}
|
|
10808
11562
|
if (!s.isDirectory()) continue;
|
|
10809
11563
|
let files;
|
|
10810
11564
|
try {
|
|
10811
|
-
files =
|
|
11565
|
+
files = readdirSync3(dir);
|
|
10812
11566
|
} catch {
|
|
10813
11567
|
continue;
|
|
10814
11568
|
}
|
|
10815
11569
|
for (const f of files) {
|
|
10816
11570
|
if (!f.endsWith(".md")) continue;
|
|
10817
|
-
out.push({ domain, name: f.slice(0, -3), path:
|
|
11571
|
+
out.push({ domain, name: f.slice(0, -3), path: join29(dir, f) });
|
|
10818
11572
|
}
|
|
10819
11573
|
}
|
|
10820
11574
|
return out.sort(
|
|
@@ -10903,9 +11657,9 @@ ${prompt4}`;
|
|
|
10903
11657
|
});
|
|
10904
11658
|
|
|
10905
11659
|
// src/commands/benchmark.ts
|
|
10906
|
-
import { homedir as
|
|
10907
|
-
import { join as
|
|
10908
|
-
import { mkdirSync as
|
|
11660
|
+
import { homedir as homedir16 } from "os";
|
|
11661
|
+
import { join as join30 } from "path";
|
|
11662
|
+
import { mkdirSync as mkdirSync5 } from "fs";
|
|
10909
11663
|
import * as fs2 from "fs";
|
|
10910
11664
|
function parseArgs(args2) {
|
|
10911
11665
|
let models = null;
|
|
@@ -11024,10 +11778,10 @@ HW at start: ${hw.cores}-core ${hw.platform} | CPU ${hw.cpuPct}% | RAM ${hw.ramP
|
|
|
11024
11778
|
`[HW at run time: CPU ${hwAtEnd.cpuPct}% / RAM ${hwAtEnd.ramPct}% | ${hw.ramGiB} GiB RAM, ${hw.cores} cores]`
|
|
11025
11779
|
);
|
|
11026
11780
|
try {
|
|
11027
|
-
const dir =
|
|
11028
|
-
|
|
11781
|
+
const dir = join30(homedir16(), ".msapling", "benchmarks");
|
|
11782
|
+
mkdirSync5(dir, { recursive: true });
|
|
11029
11783
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 16);
|
|
11030
|
-
const file =
|
|
11784
|
+
const file = join30(dir, `${ts}.json`);
|
|
11031
11785
|
const run = {
|
|
11032
11786
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11033
11787
|
rounds,
|
|
@@ -11273,22 +12027,22 @@ var init_theme = __esm({
|
|
|
11273
12027
|
});
|
|
11274
12028
|
|
|
11275
12029
|
// src/commands/theme.ts
|
|
11276
|
-
import { join as
|
|
11277
|
-
import { homedir as
|
|
11278
|
-
import { existsSync as
|
|
12030
|
+
import { join as join31 } from "path";
|
|
12031
|
+
import { homedir as homedir17 } from "os";
|
|
12032
|
+
import { existsSync as existsSync27 } from "fs";
|
|
11279
12033
|
import { readFile as readFile24, writeFile as writeFile14 } from "fs/promises";
|
|
11280
12034
|
async function persistTheme(storage, themeName) {
|
|
11281
|
-
const settingsPath =
|
|
12035
|
+
const settingsPath = join31(homedir17(), ".msapling", "settings.json");
|
|
11282
12036
|
let existing = {};
|
|
11283
12037
|
try {
|
|
11284
|
-
if (
|
|
12038
|
+
if (existsSync27(settingsPath)) {
|
|
11285
12039
|
const text = await readFile24(settingsPath, "utf8");
|
|
11286
12040
|
if (text.trim()) existing = JSON.parse(text);
|
|
11287
12041
|
}
|
|
11288
12042
|
} catch {
|
|
11289
12043
|
}
|
|
11290
12044
|
existing["theme"] = themeName;
|
|
11291
|
-
ensureConfigDir(
|
|
12045
|
+
ensureConfigDir(join31(homedir17(), ".msapling"));
|
|
11292
12046
|
await writeFile14(settingsPath, JSON.stringify(existing, null, 2), "utf8");
|
|
11293
12047
|
}
|
|
11294
12048
|
var VALID_THEMES, themeCommand;
|
|
@@ -11360,7 +12114,7 @@ var init_version = __esm({
|
|
|
11360
12114
|
description: "Show version information for CLI and core packages",
|
|
11361
12115
|
category: "debug",
|
|
11362
12116
|
handler: async (_args, context) => {
|
|
11363
|
-
const cliVersion = true ? "2.3.6-beta.
|
|
12117
|
+
const cliVersion = true ? "2.3.6-beta.47" : "(dev)";
|
|
11364
12118
|
const coreVersion = true ? "2.3.6-beta.43" : "(dev)";
|
|
11365
12119
|
const runtime = process.version;
|
|
11366
12120
|
context.addMessage("system", "MSapling Version Info");
|
|
@@ -11369,7 +12123,7 @@ var init_version = __esm({
|
|
|
11369
12123
|
context.addMessage("system", row2("Core (@msapling/core)", coreVersion));
|
|
11370
12124
|
context.addMessage("system", row2("Runtime (Node/Bun)", runtime));
|
|
11371
12125
|
try {
|
|
11372
|
-
const ts = "2026-06-
|
|
12126
|
+
const ts = "2026-06-20T08:11:43.445Z";
|
|
11373
12127
|
if (ts && ts !== "__BUILD_TIMESTAMP__") {
|
|
11374
12128
|
context.addMessage("system", row2("Build Timestamp", ts));
|
|
11375
12129
|
}
|
|
@@ -11382,14 +12136,14 @@ var init_version = __esm({
|
|
|
11382
12136
|
});
|
|
11383
12137
|
|
|
11384
12138
|
// src/commands/feedback.ts
|
|
11385
|
-
import { join as
|
|
11386
|
-
import { existsSync as
|
|
12139
|
+
import { join as join32 } from "path";
|
|
12140
|
+
import { existsSync as existsSync28 } from "fs";
|
|
11387
12141
|
import { readFile as readFile25 } from "fs/promises";
|
|
11388
12142
|
async function readCliVersion() {
|
|
11389
12143
|
try {
|
|
11390
12144
|
const baseDir = typeof __dirname !== "undefined" ? __dirname : process.cwd();
|
|
11391
|
-
const pkgPath =
|
|
11392
|
-
if (!
|
|
12145
|
+
const pkgPath = join32(baseDir, "..", "..", "package.json");
|
|
12146
|
+
if (!existsSync28(pkgPath)) return "unknown";
|
|
11393
12147
|
const text = await readFile25(pkgPath, "utf8");
|
|
11394
12148
|
const json = JSON.parse(text);
|
|
11395
12149
|
return json.version ?? "unknown";
|
|
@@ -11430,8 +12184,8 @@ var init_feedback = __esm({
|
|
|
11430
12184
|
});
|
|
11431
12185
|
|
|
11432
12186
|
// src/commands/export.ts
|
|
11433
|
-
import { homedir as
|
|
11434
|
-
import { join as
|
|
12187
|
+
import { homedir as homedir18 } from "os";
|
|
12188
|
+
import { join as join33 } from "path";
|
|
11435
12189
|
import { writeFile as writeFile15, mkdir as mkdir8 } from "fs/promises";
|
|
11436
12190
|
function formatTimestamp(date) {
|
|
11437
12191
|
return date.toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19);
|
|
@@ -11481,10 +12235,10 @@ var init_export = __esm({
|
|
|
11481
12235
|
let outputPath;
|
|
11482
12236
|
let content;
|
|
11483
12237
|
if (arg === "" || arg === "json") {
|
|
11484
|
-
outputPath =
|
|
12238
|
+
outputPath = join33(homedir18(), `msapling-export-${timestamp}.json`);
|
|
11485
12239
|
content = buildJsonExport(history);
|
|
11486
12240
|
} else if (arg === "markdown" || arg === "md") {
|
|
11487
|
-
outputPath =
|
|
12241
|
+
outputPath = join33(homedir18(), `msapling-export-${timestamp}.md`);
|
|
11488
12242
|
content = buildMarkdownExport(history);
|
|
11489
12243
|
} else {
|
|
11490
12244
|
outputPath = arg;
|
|
@@ -11496,7 +12250,7 @@ var init_export = __esm({
|
|
|
11496
12250
|
}
|
|
11497
12251
|
}
|
|
11498
12252
|
try {
|
|
11499
|
-
const dir =
|
|
12253
|
+
const dir = join33(outputPath, "..");
|
|
11500
12254
|
await mkdir8(dir, { recursive: true });
|
|
11501
12255
|
await writeFile15(outputPath, content, "utf8");
|
|
11502
12256
|
context.addMessage("system", `Exported to: ${outputPath}`);
|
|
@@ -11691,16 +12445,16 @@ var init_plan = __esm({
|
|
|
11691
12445
|
});
|
|
11692
12446
|
|
|
11693
12447
|
// src/commands/note.ts
|
|
11694
|
-
import { homedir as
|
|
11695
|
-
import { join as
|
|
11696
|
-
import { existsSync as
|
|
12448
|
+
import { homedir as homedir19 } from "os";
|
|
12449
|
+
import { join as join34 } from "path";
|
|
12450
|
+
import { existsSync as existsSync29 } from "fs";
|
|
11697
12451
|
import { readFile as readFile26, writeFile as writeFile16 } from "fs/promises";
|
|
11698
12452
|
function getNotesFilePath() {
|
|
11699
|
-
return
|
|
12453
|
+
return join34(homedir19(), ".msapling", "notes.json");
|
|
11700
12454
|
}
|
|
11701
12455
|
async function readNotes(filePath = getNotesFilePath()) {
|
|
11702
12456
|
try {
|
|
11703
|
-
if (!
|
|
12457
|
+
if (!existsSync29(filePath)) return [];
|
|
11704
12458
|
const raw = await readFile26(filePath, "utf8");
|
|
11705
12459
|
const parsed = JSON.parse(raw);
|
|
11706
12460
|
if (!Array.isArray(parsed)) return [];
|
|
@@ -11710,7 +12464,7 @@ async function readNotes(filePath = getNotesFilePath()) {
|
|
|
11710
12464
|
}
|
|
11711
12465
|
}
|
|
11712
12466
|
async function writeNotes(notes, filePath = getNotesFilePath()) {
|
|
11713
|
-
const dir =
|
|
12467
|
+
const dir = join34(homedir19(), ".msapling");
|
|
11714
12468
|
ensureConfigDir(dir);
|
|
11715
12469
|
await writeFile16(filePath, JSON.stringify(notes, null, 2), "utf8");
|
|
11716
12470
|
}
|
|
@@ -11856,17 +12610,17 @@ var init_todo = __esm({
|
|
|
11856
12610
|
});
|
|
11857
12611
|
|
|
11858
12612
|
// src/commands/outputStyle.ts
|
|
11859
|
-
import { homedir as
|
|
11860
|
-
import { join as
|
|
11861
|
-
import { existsSync as
|
|
12613
|
+
import { homedir as homedir20 } from "os";
|
|
12614
|
+
import { join as join35, basename as basename3, extname as extname3 } from "path";
|
|
12615
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync6, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
11862
12616
|
function resolveHome() {
|
|
11863
|
-
return process.env.HOME || process.env.USERPROFILE ||
|
|
12617
|
+
return process.env.HOME || process.env.USERPROFILE || homedir20();
|
|
11864
12618
|
}
|
|
11865
12619
|
function stylesDir() {
|
|
11866
|
-
return
|
|
12620
|
+
return join35(resolveHome(), ".msapling", "output-styles");
|
|
11867
12621
|
}
|
|
11868
12622
|
function activeFile() {
|
|
11869
|
-
return
|
|
12623
|
+
return join35(stylesDir(), ".active");
|
|
11870
12624
|
}
|
|
11871
12625
|
function parseStyleFile(text) {
|
|
11872
12626
|
const fm = text.match(/^---\s*\n([\s\S]*?)\n---\s*\n?/);
|
|
@@ -11887,13 +12641,13 @@ function parseStyleFile(text) {
|
|
|
11887
12641
|
}
|
|
11888
12642
|
function listUserStyles() {
|
|
11889
12643
|
const dir = stylesDir();
|
|
11890
|
-
if (!
|
|
12644
|
+
if (!existsSync30(dir)) return [];
|
|
11891
12645
|
const out = [];
|
|
11892
|
-
for (const entry of
|
|
12646
|
+
for (const entry of readdirSync4(dir)) {
|
|
11893
12647
|
if (extname3(entry).toLowerCase() !== ".md") continue;
|
|
11894
|
-
const full =
|
|
12648
|
+
const full = join35(dir, entry);
|
|
11895
12649
|
try {
|
|
11896
|
-
const text =
|
|
12650
|
+
const text = readFileSync4(full, "utf8");
|
|
11897
12651
|
const { description, body } = parseStyleFile(text);
|
|
11898
12652
|
out.push({
|
|
11899
12653
|
name: basename3(entry, ".md"),
|
|
@@ -11919,15 +12673,15 @@ function findStyle(name) {
|
|
|
11919
12673
|
function getActiveStyleName() {
|
|
11920
12674
|
try {
|
|
11921
12675
|
const f = activeFile();
|
|
11922
|
-
if (!
|
|
11923
|
-
return
|
|
12676
|
+
if (!existsSync30(f)) return "default";
|
|
12677
|
+
return readFileSync4(f, "utf8").trim() || "default";
|
|
11924
12678
|
} catch {
|
|
11925
12679
|
return "default";
|
|
11926
12680
|
}
|
|
11927
12681
|
}
|
|
11928
12682
|
function setActiveStyleName(name) {
|
|
11929
12683
|
const dir = stylesDir();
|
|
11930
|
-
if (!
|
|
12684
|
+
if (!existsSync30(dir)) mkdirSync6(dir, { recursive: true });
|
|
11931
12685
|
writeFileSync5(activeFile(), `${name}
|
|
11932
12686
|
`, "utf8");
|
|
11933
12687
|
}
|
|
@@ -11940,8 +12694,8 @@ function createUserStyle(name, description, body) {
|
|
|
11940
12694
|
throw new Error(`Invalid style name "${name}" \u2014 use letters, digits, _ and - only.`);
|
|
11941
12695
|
}
|
|
11942
12696
|
const dir = stylesDir();
|
|
11943
|
-
if (!
|
|
11944
|
-
const target =
|
|
12697
|
+
if (!existsSync30(dir)) mkdirSync6(dir, { recursive: true });
|
|
12698
|
+
const target = join35(dir, `${name}.md`);
|
|
11945
12699
|
const frontmatter = `---
|
|
11946
12700
|
description: ${description.replace(/\n/g, " ")}
|
|
11947
12701
|
---
|
|
@@ -13105,6 +13859,259 @@ Note: server may return a [server notice] caveat for v1-scaffold features.`
|
|
|
13105
13859
|
}
|
|
13106
13860
|
});
|
|
13107
13861
|
|
|
13862
|
+
// src/commands/rewind.ts
|
|
13863
|
+
var rewindCommand;
|
|
13864
|
+
var init_rewind = __esm({
|
|
13865
|
+
"src/commands/rewind.ts"() {
|
|
13866
|
+
"use strict";
|
|
13867
|
+
init_esm_shims();
|
|
13868
|
+
init_src3();
|
|
13869
|
+
rewindCommand = {
|
|
13870
|
+
name: "rewind",
|
|
13871
|
+
aliases: ["checkpoint", "restore"],
|
|
13872
|
+
args: "[n] [--confirm]",
|
|
13873
|
+
description: "List file-edit checkpoints or restore files to a prior snapshot. Usage: /rewind (list) \xB7 /rewind <n> (preview) \xB7 /rewind <n> --confirm (restore).",
|
|
13874
|
+
category: "chat",
|
|
13875
|
+
handler(args2, context) {
|
|
13876
|
+
const checkpoints = listCheckpoints();
|
|
13877
|
+
if (args2.length === 0) {
|
|
13878
|
+
if (checkpoints.length === 0) {
|
|
13879
|
+
context.addMessage(
|
|
13880
|
+
"system",
|
|
13881
|
+
"No checkpoints found. File edits made by the agent will be listed here."
|
|
13882
|
+
);
|
|
13883
|
+
return;
|
|
13884
|
+
}
|
|
13885
|
+
const lines = [
|
|
13886
|
+
`${checkpoints.length} checkpoint(s) \u2014 most recent first:`,
|
|
13887
|
+
""
|
|
13888
|
+
];
|
|
13889
|
+
checkpoints.forEach((cp, idx) => {
|
|
13890
|
+
const num = idx + 1;
|
|
13891
|
+
const fileCount = cp.backups.length;
|
|
13892
|
+
const when = new Date(cp.timestamp).toLocaleString();
|
|
13893
|
+
lines.push(
|
|
13894
|
+
` ${num}. [${when}] ${cp.label} (${fileCount} file${fileCount !== 1 ? "s" : ""})`
|
|
13895
|
+
);
|
|
13896
|
+
});
|
|
13897
|
+
lines.push("");
|
|
13898
|
+
lines.push("To preview a restore: /rewind <n>");
|
|
13899
|
+
lines.push("To restore: /rewind <n> --confirm");
|
|
13900
|
+
context.addMessage("system", lines.join("\n"));
|
|
13901
|
+
return;
|
|
13902
|
+
}
|
|
13903
|
+
const positional = args2.filter((a) => !a.startsWith("-"));
|
|
13904
|
+
const hasConfirm = args2.includes("--confirm") || args2.includes("-y") || args2.includes("--yes");
|
|
13905
|
+
if (positional.length === 0) {
|
|
13906
|
+
context.addMessage("system", "Usage: /rewind <n> [--confirm]");
|
|
13907
|
+
return;
|
|
13908
|
+
}
|
|
13909
|
+
const rawN = positional[0];
|
|
13910
|
+
const n = parseInt(rawN, 10);
|
|
13911
|
+
if (isNaN(n) || n < 1) {
|
|
13912
|
+
context.addMessage(
|
|
13913
|
+
"error",
|
|
13914
|
+
`Invalid checkpoint number: "${rawN}". Run /rewind to list available checkpoints.`
|
|
13915
|
+
);
|
|
13916
|
+
return;
|
|
13917
|
+
}
|
|
13918
|
+
if (checkpoints.length === 0) {
|
|
13919
|
+
context.addMessage("system", "No checkpoints available to restore.");
|
|
13920
|
+
return;
|
|
13921
|
+
}
|
|
13922
|
+
if (n > checkpoints.length) {
|
|
13923
|
+
context.addMessage(
|
|
13924
|
+
"error",
|
|
13925
|
+
`Checkpoint #${n} does not exist. ${checkpoints.length} checkpoint(s) available. Run /rewind to list them.`
|
|
13926
|
+
);
|
|
13927
|
+
return;
|
|
13928
|
+
}
|
|
13929
|
+
const target = checkpoints[n - 1];
|
|
13930
|
+
if (!hasConfirm) {
|
|
13931
|
+
const when = new Date(target.timestamp).toLocaleString();
|
|
13932
|
+
const lines = [
|
|
13933
|
+
`Checkpoint #${n}: ${target.label} [${when}]`,
|
|
13934
|
+
""
|
|
13935
|
+
];
|
|
13936
|
+
if (target.backups.length === 0) {
|
|
13937
|
+
lines.push(" (no file edits recorded in this checkpoint)");
|
|
13938
|
+
} else {
|
|
13939
|
+
lines.push("Files that will be reverted:");
|
|
13940
|
+
for (const rec of target.backups) {
|
|
13941
|
+
lines.push(` - ${rec.originalPath}`);
|
|
13942
|
+
}
|
|
13943
|
+
}
|
|
13944
|
+
lines.push("");
|
|
13945
|
+
lines.push(`To restore these files, run: /rewind ${n} --confirm`);
|
|
13946
|
+
lines.push(
|
|
13947
|
+
"Note: only file contents are restored (chat history is NOT truncated)."
|
|
13948
|
+
);
|
|
13949
|
+
context.addMessage("system", lines.join("\n"));
|
|
13950
|
+
return;
|
|
13951
|
+
}
|
|
13952
|
+
if (target.backups.length === 0) {
|
|
13953
|
+
context.addMessage(
|
|
13954
|
+
"system",
|
|
13955
|
+
`Checkpoint #${n} has no file edits \u2014 nothing to restore.`
|
|
13956
|
+
);
|
|
13957
|
+
return;
|
|
13958
|
+
}
|
|
13959
|
+
const results = restoreCheckpoint(target);
|
|
13960
|
+
const restored = results.filter((r) => r.startsWith("Restored:"));
|
|
13961
|
+
const skipped = results.filter((r) => r.startsWith("Skip"));
|
|
13962
|
+
const errors = results.filter((r) => r.startsWith("Error"));
|
|
13963
|
+
const summary = [
|
|
13964
|
+
`Rewind to checkpoint #${n} complete.`,
|
|
13965
|
+
"",
|
|
13966
|
+
...results
|
|
13967
|
+
];
|
|
13968
|
+
if (errors.length > 0) {
|
|
13969
|
+
summary.push("");
|
|
13970
|
+
summary.push(
|
|
13971
|
+
`${errors.length} error(s) occurred. Check that the backup files in ~/.msapling/backups/ are accessible.`
|
|
13972
|
+
);
|
|
13973
|
+
}
|
|
13974
|
+
if (restored.length > 0 && errors.length === 0) {
|
|
13975
|
+
summary.push("");
|
|
13976
|
+
summary.push(
|
|
13977
|
+
`${restored.length} file(s) restored successfully. Note: chat history was NOT truncated (follow-up: REWIND-FOLLOW-UP-01).`
|
|
13978
|
+
);
|
|
13979
|
+
}
|
|
13980
|
+
context.addMessage("system", summary.join("\n"));
|
|
13981
|
+
}
|
|
13982
|
+
};
|
|
13983
|
+
}
|
|
13984
|
+
});
|
|
13985
|
+
|
|
13986
|
+
// src/commands/bashes.ts
|
|
13987
|
+
var bashesCommand;
|
|
13988
|
+
var init_bashes = __esm({
|
|
13989
|
+
"src/commands/bashes.ts"() {
|
|
13990
|
+
"use strict";
|
|
13991
|
+
init_esm_shims();
|
|
13992
|
+
init_src3();
|
|
13993
|
+
bashesCommand = {
|
|
13994
|
+
name: "bashes",
|
|
13995
|
+
aliases: ["bg", "background"],
|
|
13996
|
+
args: "[read <id> [n] | kill <id|all> | <id>]",
|
|
13997
|
+
description: "List, inspect, or kill background shells",
|
|
13998
|
+
category: "debug",
|
|
13999
|
+
handler(args2, context) {
|
|
14000
|
+
const sub = (args2[0] ?? "").toLowerCase();
|
|
14001
|
+
if (sub === "kill") {
|
|
14002
|
+
const target = args2[1] ?? "";
|
|
14003
|
+
if (!target) {
|
|
14004
|
+
context.addMessage("error", "Usage: /bashes kill <id|all>");
|
|
14005
|
+
return;
|
|
14006
|
+
}
|
|
14007
|
+
if (target.toLowerCase() === "all") {
|
|
14008
|
+
const running = BackgroundShellRegistry.list().filter((s) => s.status === "running");
|
|
14009
|
+
BackgroundShellRegistry.killAll();
|
|
14010
|
+
context.addMessage("system", `Killed ${running.length} running background shell(s).`);
|
|
14011
|
+
return;
|
|
14012
|
+
}
|
|
14013
|
+
const result = BackgroundShellRegistry.kill(target);
|
|
14014
|
+
if (result === "killed") context.addMessage("system", `Killed background shell ${target}.`);
|
|
14015
|
+
else if (result === "already")
|
|
14016
|
+
context.addMessage("system", `Background shell ${target} had already finished.`);
|
|
14017
|
+
else context.addMessage("error", `No background shell with id "${target}".`);
|
|
14018
|
+
return;
|
|
14019
|
+
}
|
|
14020
|
+
const explicitRead = sub === "read";
|
|
14021
|
+
const id = explicitRead ? args2[1] : args2[0];
|
|
14022
|
+
if (id) {
|
|
14023
|
+
const lineArg = explicitRead ? args2[2] : args2[1];
|
|
14024
|
+
const lines = lineArg && /^\d+$/.test(lineArg) ? parseInt(lineArg, 10) : void 0;
|
|
14025
|
+
const read = BackgroundShellRegistry.readOutput(id, lines);
|
|
14026
|
+
const info = BackgroundShellRegistry.get(id);
|
|
14027
|
+
if (!read || !info) {
|
|
14028
|
+
context.addMessage("error", `No background shell with id "${id}".`);
|
|
14029
|
+
return;
|
|
14030
|
+
}
|
|
14031
|
+
const header = `[${info.id}] ${info.status.toUpperCase()}` + (info.exitCode !== void 0 && info.exitCode !== null ? ` exit=${info.exitCode}` : "") + ` $ ${info.command}
|
|
14032
|
+
`;
|
|
14033
|
+
const body = read.output.trim() === "" ? "(no output yet)" : read.output;
|
|
14034
|
+
const trunc = read.truncated ? "\n... [output buffer truncated to most recent 256 KB]" : "";
|
|
14035
|
+
context.addMessage("system", header + body + trunc);
|
|
14036
|
+
return;
|
|
14037
|
+
}
|
|
14038
|
+
context.addMessage("system", formatBackgroundShells(BackgroundShellRegistry.list()));
|
|
14039
|
+
}
|
|
14040
|
+
};
|
|
14041
|
+
}
|
|
14042
|
+
});
|
|
14043
|
+
|
|
14044
|
+
// src/commands/agents.ts
|
|
14045
|
+
import { homedir as homedir21 } from "os";
|
|
14046
|
+
function resolveHome2() {
|
|
14047
|
+
return process.env.HOME || process.env.USERPROFILE || homedir21();
|
|
14048
|
+
}
|
|
14049
|
+
function describeTools(agent) {
|
|
14050
|
+
if (agent.tools === void 0) return "all (inherits full tool set)";
|
|
14051
|
+
if (agent.tools.length === 0) return "none (read/reason only)";
|
|
14052
|
+
return agent.tools.join(", ");
|
|
14053
|
+
}
|
|
14054
|
+
var agentsCommand;
|
|
14055
|
+
var init_agents = __esm({
|
|
14056
|
+
"src/commands/agents.ts"() {
|
|
14057
|
+
"use strict";
|
|
14058
|
+
init_esm_shims();
|
|
14059
|
+
init_src3();
|
|
14060
|
+
agentsCommand = {
|
|
14061
|
+
name: "agents",
|
|
14062
|
+
aliases: ["agent"],
|
|
14063
|
+
args: "[name]",
|
|
14064
|
+
description: "List named subagents (.msapling/agents/*.md), or show one. Named agents have their own system prompt, model, and tool allowlist.",
|
|
14065
|
+
category: "swarm",
|
|
14066
|
+
handler: (args2, context) => {
|
|
14067
|
+
const cwd = process.cwd();
|
|
14068
|
+
const home = resolveHome2();
|
|
14069
|
+
if (args2.length >= 1 && args2[0] !== "list" && args2[0] !== "ls") {
|
|
14070
|
+
const ref = args2[0];
|
|
14071
|
+
const agent = findNamedAgent(ref, cwd, home);
|
|
14072
|
+
if (!agent) {
|
|
14073
|
+
context.addMessage(
|
|
14074
|
+
"error",
|
|
14075
|
+
`No named agent "${ref}". Run /agents (no args) to list available agents.`
|
|
14076
|
+
);
|
|
14077
|
+
return;
|
|
14078
|
+
}
|
|
14079
|
+
context.addMessage("system", `Agent: ${agent.name} [${agent.scope}]`);
|
|
14080
|
+
context.addMessage("system", ` description: ${agent.description}`);
|
|
14081
|
+
context.addMessage("system", ` model: ${agent.model ?? "(inherit session model)"}`);
|
|
14082
|
+
context.addMessage("system", ` tools: ${describeTools(agent)}`);
|
|
14083
|
+
context.addMessage("system", ` source: ${agent.path}`);
|
|
14084
|
+
context.addMessage("system", " \u2500\u2500\u2500 system prompt \u2500\u2500\u2500");
|
|
14085
|
+
context.addMessage("system", agent.systemPrompt || "(empty)");
|
|
14086
|
+
context.addMessage(
|
|
14087
|
+
"system",
|
|
14088
|
+
`Run it: the model can call dispatch_agent with agent="${agent.name}".`
|
|
14089
|
+
);
|
|
14090
|
+
return;
|
|
14091
|
+
}
|
|
14092
|
+
const agents = loadNamedAgents(cwd, home);
|
|
14093
|
+
if (agents.length === 0) {
|
|
14094
|
+
context.addMessage(
|
|
14095
|
+
"system",
|
|
14096
|
+
"No named agents found. Create one at .msapling/agents/<name>.md (project) or ~/.msapling/agents/<name>.md (global). Frontmatter: name, description, model (optional), tools (optional allowlist); body = the system prompt."
|
|
14097
|
+
);
|
|
14098
|
+
return;
|
|
14099
|
+
}
|
|
14100
|
+
context.addMessage("system", `Named subagents (${agents.length}):`);
|
|
14101
|
+
for (const a of agents) {
|
|
14102
|
+
const model = a.model ? ` model=${a.model}` : "";
|
|
14103
|
+
const toolCount = a.tools === void 0 ? "tools=all" : `tools=${a.tools.length}`;
|
|
14104
|
+
context.addMessage(
|
|
14105
|
+
"system",
|
|
14106
|
+
` ${a.name.padEnd(20)} [${a.scope}] ${toolCount}${model} \u2014 ${a.description}`
|
|
14107
|
+
);
|
|
14108
|
+
}
|
|
14109
|
+
context.addMessage("system", "Run /agents <name> to see an agent in full.");
|
|
14110
|
+
}
|
|
14111
|
+
};
|
|
14112
|
+
}
|
|
14113
|
+
});
|
|
14114
|
+
|
|
13108
14115
|
// src/commands/index.ts
|
|
13109
14116
|
var commands_exports = {};
|
|
13110
14117
|
__export(commands_exports, {
|
|
@@ -13176,6 +14183,9 @@ var init_commands = __esm({
|
|
|
13176
14183
|
init_diag();
|
|
13177
14184
|
init_parallel();
|
|
13178
14185
|
init_remoteAgent();
|
|
14186
|
+
init_rewind();
|
|
14187
|
+
init_bashes();
|
|
14188
|
+
init_agents();
|
|
13179
14189
|
commands = [
|
|
13180
14190
|
loginCommand,
|
|
13181
14191
|
logoutCommand,
|
|
@@ -13239,7 +14249,10 @@ var init_commands = __esm({
|
|
|
13239
14249
|
syncCommand,
|
|
13240
14250
|
diagCommand,
|
|
13241
14251
|
parallelCommand,
|
|
13242
|
-
remoteAgentCommand
|
|
14252
|
+
remoteAgentCommand,
|
|
14253
|
+
rewindCommand,
|
|
14254
|
+
bashesCommand,
|
|
14255
|
+
agentsCommand
|
|
13243
14256
|
];
|
|
13244
14257
|
}
|
|
13245
14258
|
});
|
|
@@ -13309,15 +14322,15 @@ var exec_exports = {};
|
|
|
13309
14322
|
__export(exec_exports, {
|
|
13310
14323
|
runExec: () => runExec
|
|
13311
14324
|
});
|
|
13312
|
-
import { existsSync as
|
|
14325
|
+
import { existsSync as existsSync33 } from "fs";
|
|
13313
14326
|
import { readFile as readFile29 } from "fs/promises";
|
|
13314
|
-
import { homedir as
|
|
13315
|
-
import { join as
|
|
14327
|
+
import { homedir as homedir23 } from "os";
|
|
14328
|
+
import { join as join37 } from "path";
|
|
13316
14329
|
async function loadPersistedSettings() {
|
|
13317
14330
|
const out = { mode: "default", theme: null };
|
|
13318
14331
|
try {
|
|
13319
|
-
const p =
|
|
13320
|
-
if (!
|
|
14332
|
+
const p = join37(homedir23(), ".msapling", "settings.json");
|
|
14333
|
+
if (!existsSync33(p)) return out;
|
|
13321
14334
|
const raw = JSON.parse(await readFile29(p, "utf8"));
|
|
13322
14335
|
const parsed = parseApprovalMode(raw, Date.now());
|
|
13323
14336
|
if (parsed.kind === "ok") out.mode = parsed.mode;
|
|
@@ -13553,7 +14566,7 @@ var init_format = __esm({
|
|
|
13553
14566
|
});
|
|
13554
14567
|
|
|
13555
14568
|
// src/commands/billing/open-browser.ts
|
|
13556
|
-
import { spawn as
|
|
14569
|
+
import { spawn as spawn11 } from "child_process";
|
|
13557
14570
|
async function openBrowser(url) {
|
|
13558
14571
|
const platform5 = process.platform;
|
|
13559
14572
|
let cmd;
|
|
@@ -13568,13 +14581,13 @@ async function openBrowser(url) {
|
|
|
13568
14581
|
cmd = "xdg-open";
|
|
13569
14582
|
args2 = [url];
|
|
13570
14583
|
}
|
|
13571
|
-
return new Promise((
|
|
14584
|
+
return new Promise((resolve21) => {
|
|
13572
14585
|
try {
|
|
13573
|
-
const child =
|
|
14586
|
+
const child = spawn11(cmd, args2, { stdio: "ignore", detached: true });
|
|
13574
14587
|
child.unref();
|
|
13575
14588
|
} catch {
|
|
13576
14589
|
}
|
|
13577
|
-
|
|
14590
|
+
resolve21();
|
|
13578
14591
|
});
|
|
13579
14592
|
}
|
|
13580
14593
|
var init_open_browser = __esm({
|
|
@@ -13664,7 +14677,7 @@ var init_checkout = __esm({
|
|
|
13664
14677
|
// src/commands/billing/sub.ts
|
|
13665
14678
|
import * as readline from "readline";
|
|
13666
14679
|
function prompt(rl, question) {
|
|
13667
|
-
return new Promise((
|
|
14680
|
+
return new Promise((resolve21) => rl.question(question, resolve21));
|
|
13668
14681
|
}
|
|
13669
14682
|
async function runSub(argv) {
|
|
13670
14683
|
const subCmd = argv[0] ?? "";
|
|
@@ -13790,11 +14803,11 @@ var init_sub = __esm({
|
|
|
13790
14803
|
// src/commands/billing/topup.ts
|
|
13791
14804
|
import * as readline2 from "readline";
|
|
13792
14805
|
function prompt2(rl, question) {
|
|
13793
|
-
return new Promise((
|
|
14806
|
+
return new Promise((resolve21) => rl.question(question, resolve21));
|
|
13794
14807
|
}
|
|
13795
14808
|
function promptDefault(rl, question, defaultVal) {
|
|
13796
14809
|
return new Promise(
|
|
13797
|
-
(
|
|
14810
|
+
(resolve21) => rl.question(`${question} [${defaultVal}]: `, (ans) => resolve21(ans.trim() || defaultVal))
|
|
13798
14811
|
);
|
|
13799
14812
|
}
|
|
13800
14813
|
async function runTopup(argv) {
|
|
@@ -13929,7 +14942,7 @@ var init_redeem = __esm({
|
|
|
13929
14942
|
// src/commands/billing/gift.ts
|
|
13930
14943
|
import * as readline3 from "readline";
|
|
13931
14944
|
function prompt3(rl, question) {
|
|
13932
|
-
return new Promise((
|
|
14945
|
+
return new Promise((resolve21) => rl.question(question, resolve21));
|
|
13933
14946
|
}
|
|
13934
14947
|
async function runGift(argv) {
|
|
13935
14948
|
const subCmd = argv[0] ?? "";
|
|
@@ -14175,11 +15188,11 @@ var doctor_exports = {};
|
|
|
14175
15188
|
__export(doctor_exports, {
|
|
14176
15189
|
runDoctor: () => runDoctor
|
|
14177
15190
|
});
|
|
14178
|
-
import { homedir as
|
|
14179
|
-
import { join as
|
|
14180
|
-
import { existsSync as
|
|
15191
|
+
import { homedir as homedir24, platform as platform4, tmpdir } from "os";
|
|
15192
|
+
import { join as join38 } from "path";
|
|
15193
|
+
import { existsSync as existsSync34, statSync as statSync8 } from "fs";
|
|
14181
15194
|
import { readdir as readdir3, mkdir as mkdir9, rm as rm3 } from "fs/promises";
|
|
14182
|
-
import { exec } from "child_process";
|
|
15195
|
+
import { exec as exec2 } from "child_process";
|
|
14183
15196
|
import { promisify } from "util";
|
|
14184
15197
|
async function checkNodeVersion() {
|
|
14185
15198
|
const version = process.version;
|
|
@@ -14200,8 +15213,8 @@ async function checkNodeVersion() {
|
|
|
14200
15213
|
};
|
|
14201
15214
|
}
|
|
14202
15215
|
async function checkConfigDir() {
|
|
14203
|
-
const configDir =
|
|
14204
|
-
if (!
|
|
15216
|
+
const configDir = join38(homedir24(), ".msapling");
|
|
15217
|
+
if (!existsSync34(configDir)) {
|
|
14205
15218
|
return {
|
|
14206
15219
|
name: "Config directory",
|
|
14207
15220
|
status: "WARN",
|
|
@@ -14209,7 +15222,7 @@ async function checkConfigDir() {
|
|
|
14209
15222
|
remediation: `mkdir -p "${configDir}" && chmod 700 "${configDir}"`
|
|
14210
15223
|
};
|
|
14211
15224
|
}
|
|
14212
|
-
const stats =
|
|
15225
|
+
const stats = statSync8(configDir);
|
|
14213
15226
|
if (!stats.isDirectory()) {
|
|
14214
15227
|
return {
|
|
14215
15228
|
name: "Config directory",
|
|
@@ -14270,7 +15283,7 @@ async function checkPathConflicts() {
|
|
|
14270
15283
|
const timedOutDirs = [];
|
|
14271
15284
|
const DIR_TIMEOUT_MS = 1500;
|
|
14272
15285
|
for (const dir of paths) {
|
|
14273
|
-
if (!dir || !
|
|
15286
|
+
if (!dir || !existsSync34(dir)) continue;
|
|
14274
15287
|
try {
|
|
14275
15288
|
const files = await Promise.race([
|
|
14276
15289
|
readdir3(dir),
|
|
@@ -14283,7 +15296,7 @@ async function checkPathConflicts() {
|
|
|
14283
15296
|
]);
|
|
14284
15297
|
for (const file of files) {
|
|
14285
15298
|
if (file === "msapling" || file === "msapling.exe" || file === "msapling.py") {
|
|
14286
|
-
const fullPath =
|
|
15299
|
+
const fullPath = join38(dir, file);
|
|
14287
15300
|
conflicts.push(fullPath);
|
|
14288
15301
|
}
|
|
14289
15302
|
}
|
|
@@ -14401,9 +15414,9 @@ async function checkTokenValidity() {
|
|
|
14401
15414
|
}
|
|
14402
15415
|
async function checkOsSpecific() {
|
|
14403
15416
|
if (platform4() === "win32") {
|
|
14404
|
-
const testDir =
|
|
15417
|
+
const testDir = join38(tmpdir(), `msapling-longpath-test-${Date.now()}`);
|
|
14405
15418
|
const longDirName = "A".repeat(260);
|
|
14406
|
-
const testPath =
|
|
15419
|
+
const testPath = join38(testDir, longDirName);
|
|
14407
15420
|
try {
|
|
14408
15421
|
await mkdir9(testDir, { recursive: true });
|
|
14409
15422
|
try {
|
|
@@ -14557,7 +15570,7 @@ var init_doctor2 = __esm({
|
|
|
14557
15570
|
"use strict";
|
|
14558
15571
|
init_esm_shims();
|
|
14559
15572
|
init_doctorRedact();
|
|
14560
|
-
execAsync = promisify(
|
|
15573
|
+
execAsync = promisify(exec2);
|
|
14561
15574
|
}
|
|
14562
15575
|
});
|
|
14563
15576
|
|
|
@@ -14603,21 +15616,21 @@ var init_registry_merger = __esm({
|
|
|
14603
15616
|
});
|
|
14604
15617
|
|
|
14605
15618
|
// ../core/src/mcp/local_tools.ts
|
|
14606
|
-
import { spawn as
|
|
15619
|
+
import { spawn as spawn12 } from "child_process";
|
|
14607
15620
|
import { readdir as readdir4, stat as stat4, realpath as realpath3 } from "fs/promises";
|
|
14608
|
-
import { resolve as
|
|
15621
|
+
import { resolve as resolve18 } from "path";
|
|
14609
15622
|
function asResult(text, isError = false) {
|
|
14610
15623
|
return { content: [{ type: "text", text }], ...isError ? { isError: true } : {} };
|
|
14611
15624
|
}
|
|
14612
15625
|
async function runCommand(command, cwd) {
|
|
14613
|
-
return new Promise((
|
|
15626
|
+
return new Promise((resolve21) => {
|
|
14614
15627
|
let p;
|
|
14615
15628
|
const timeout = setTimeout(() => {
|
|
14616
15629
|
if (p) p.kill();
|
|
14617
|
-
|
|
15630
|
+
resolve21({ stdout: "", stderr: "Command timed out after 30s", exit_code: -1 });
|
|
14618
15631
|
}, 3e4);
|
|
14619
15632
|
try {
|
|
14620
|
-
p =
|
|
15633
|
+
p = spawn12("sh", ["-c", command], {
|
|
14621
15634
|
cwd: cwd || process.cwd(),
|
|
14622
15635
|
stdio: ["ignore", "pipe", "pipe"],
|
|
14623
15636
|
timeout: 3e4
|
|
@@ -14632,15 +15645,15 @@ async function runCommand(command, cwd) {
|
|
|
14632
15645
|
});
|
|
14633
15646
|
p.on("error", (e) => {
|
|
14634
15647
|
clearTimeout(timeout);
|
|
14635
|
-
|
|
15648
|
+
resolve21({ stdout, stderr: stderr + (e?.message ?? ""), exit_code: -1 });
|
|
14636
15649
|
});
|
|
14637
15650
|
p.on("exit", (code) => {
|
|
14638
15651
|
clearTimeout(timeout);
|
|
14639
|
-
|
|
15652
|
+
resolve21({ stdout, stderr, exit_code: code });
|
|
14640
15653
|
});
|
|
14641
15654
|
} catch (e) {
|
|
14642
15655
|
clearTimeout(timeout);
|
|
14643
|
-
|
|
15656
|
+
resolve21({
|
|
14644
15657
|
stdout: "",
|
|
14645
15658
|
stderr: e?.message ?? "Failed to spawn process",
|
|
14646
15659
|
exit_code: -1
|
|
@@ -14707,7 +15720,7 @@ async function callLocalTool(name, args2, projectRoot) {
|
|
|
14707
15720
|
const command = String(args2.command ?? "");
|
|
14708
15721
|
let cwd = projectRoot;
|
|
14709
15722
|
if (args2.cwd) {
|
|
14710
|
-
cwd =
|
|
15723
|
+
cwd = resolve18(projectRoot, String(args2.cwd));
|
|
14711
15724
|
try {
|
|
14712
15725
|
const resolvedCwd = await realpath3(cwd);
|
|
14713
15726
|
const resolvedRoot = await realpath3(projectRoot);
|
|
@@ -14744,7 +15757,7 @@ ${res.stderr}`
|
|
|
14744
15757
|
return asResult("path is required", true);
|
|
14745
15758
|
}
|
|
14746
15759
|
try {
|
|
14747
|
-
const resolvedPath = await realpath3(
|
|
15760
|
+
const resolvedPath = await realpath3(resolve18(projectRoot, pathArg));
|
|
14748
15761
|
const resolvedRoot = await realpath3(projectRoot);
|
|
14749
15762
|
if (!resolvedPath.startsWith(resolvedRoot)) {
|
|
14750
15763
|
return asResult("Error: path attempts to escape project root", true);
|
|
@@ -14763,7 +15776,7 @@ ${res.stderr}`
|
|
|
14763
15776
|
}
|
|
14764
15777
|
case "local_glob": {
|
|
14765
15778
|
const pattern = String(args2.pattern ?? "");
|
|
14766
|
-
let cwd = args2.cwd ?
|
|
15779
|
+
let cwd = args2.cwd ? resolve18(projectRoot, String(args2.cwd)) : projectRoot;
|
|
14767
15780
|
if (!pattern) {
|
|
14768
15781
|
return asResult("pattern is required", true);
|
|
14769
15782
|
}
|
|
@@ -14792,7 +15805,7 @@ ${res.stderr}`
|
|
|
14792
15805
|
}
|
|
14793
15806
|
if (path2) {
|
|
14794
15807
|
try {
|
|
14795
|
-
const resolvedPath = await realpath3(
|
|
15808
|
+
const resolvedPath = await realpath3(resolve18(projectRoot, path2));
|
|
14796
15809
|
const resolvedRoot = await realpath3(projectRoot);
|
|
14797
15810
|
if (!resolvedPath.startsWith(resolvedRoot)) {
|
|
14798
15811
|
return asResult("Error: path attempts to escape project root", true);
|
|
@@ -14812,7 +15825,7 @@ ${res.stderr}`
|
|
|
14812
15825
|
return asResult("cwd is required", true);
|
|
14813
15826
|
}
|
|
14814
15827
|
try {
|
|
14815
|
-
const resolvedCwd = await realpath3(
|
|
15828
|
+
const resolvedCwd = await realpath3(resolve18(projectRoot, cwdArg));
|
|
14816
15829
|
const resolvedRoot = await realpath3(projectRoot);
|
|
14817
15830
|
if (!resolvedCwd.startsWith(resolvedRoot)) {
|
|
14818
15831
|
return asResult("Error: cwd attempts to escape project root", true);
|
|
@@ -14838,7 +15851,7 @@ ${status2.porcelain || "(clean)"}`
|
|
|
14838
15851
|
return asResult("cwd is required", true);
|
|
14839
15852
|
}
|
|
14840
15853
|
try {
|
|
14841
|
-
const resolvedCwd = await realpath3(
|
|
15854
|
+
const resolvedCwd = await realpath3(resolve18(projectRoot, cwdArg));
|
|
14842
15855
|
const resolvedRoot = await realpath3(projectRoot);
|
|
14843
15856
|
if (!resolvedCwd.startsWith(resolvedRoot)) {
|
|
14844
15857
|
return asResult("Error: cwd attempts to escape project root", true);
|
|
@@ -15032,13 +16045,13 @@ var init_base = __esm({
|
|
|
15032
16045
|
editLength++;
|
|
15033
16046
|
};
|
|
15034
16047
|
if (callback) {
|
|
15035
|
-
(function
|
|
16048
|
+
(function exec3() {
|
|
15036
16049
|
setTimeout(function() {
|
|
15037
16050
|
if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {
|
|
15038
16051
|
return callback(void 0);
|
|
15039
16052
|
}
|
|
15040
16053
|
if (!execEditLength()) {
|
|
15041
|
-
|
|
16054
|
+
exec3();
|
|
15042
16055
|
}
|
|
15043
16056
|
}, 0);
|
|
15044
16057
|
})();
|
|
@@ -16080,8 +17093,8 @@ var init_libesm = __esm({
|
|
|
16080
17093
|
});
|
|
16081
17094
|
|
|
16082
17095
|
// ../core/src/mcp/catalog.ts
|
|
16083
|
-
import { readdirSync as
|
|
16084
|
-
import { join as
|
|
17096
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync5, statSync as statSync9 } from "fs";
|
|
17097
|
+
import { join as join39, relative as relative16 } from "path";
|
|
16085
17098
|
function buildFileTree(root, maxFiles) {
|
|
16086
17099
|
const SKIP_DIRS2 = /* @__PURE__ */ new Set(["node_modules", ".git", "build", "dist", ".venv", "venv", ".next", "__pycache__", ".dart_tool", ".bun", "target"]);
|
|
16087
17100
|
const SOURCE_EXT = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs", ".java", ".kt", ".swift", ".dart", ".rb", ".cs", ".cpp", ".c", ".h", ".md", ".json", ".yaml", ".yml", ".toml", ".sh", ".sql"]);
|
|
@@ -16091,17 +17104,17 @@ function buildFileTree(root, maxFiles) {
|
|
|
16091
17104
|
const dir = queue.shift();
|
|
16092
17105
|
let entries;
|
|
16093
17106
|
try {
|
|
16094
|
-
entries =
|
|
17107
|
+
entries = readdirSync5(dir);
|
|
16095
17108
|
} catch {
|
|
16096
17109
|
continue;
|
|
16097
17110
|
}
|
|
16098
17111
|
for (const name of entries) {
|
|
16099
17112
|
if (out.length >= maxFiles) break;
|
|
16100
17113
|
if (SKIP_DIRS2.has(name)) continue;
|
|
16101
|
-
const full =
|
|
17114
|
+
const full = join39(dir, name);
|
|
16102
17115
|
let s;
|
|
16103
17116
|
try {
|
|
16104
|
-
s =
|
|
17117
|
+
s = statSync9(full);
|
|
16105
17118
|
} catch {
|
|
16106
17119
|
continue;
|
|
16107
17120
|
}
|
|
@@ -16122,12 +17135,12 @@ function readFilesAsContext(root, files, maxKB) {
|
|
|
16122
17135
|
for (const f of files) {
|
|
16123
17136
|
let body;
|
|
16124
17137
|
try {
|
|
16125
|
-
body =
|
|
17138
|
+
body = readFileSync5(f, "utf8");
|
|
16126
17139
|
} catch {
|
|
16127
17140
|
continue;
|
|
16128
17141
|
}
|
|
16129
17142
|
if (body.length > cap) body = body.slice(0, cap) + "\n[...truncated]";
|
|
16130
|
-
const rel =
|
|
17143
|
+
const rel = relative16(root, f).replace(/\\/g, "/");
|
|
16131
17144
|
parts.push(`### ${rel}
|
|
16132
17145
|
|
|
16133
17146
|
\`\`\`
|
|
@@ -16329,7 +17342,7 @@ var init_types = __esm({
|
|
|
16329
17342
|
});
|
|
16330
17343
|
|
|
16331
17344
|
// ../core/src/mcp/handlers.ts
|
|
16332
|
-
import { resolve as
|
|
17345
|
+
import { resolve as resolve20 } from "path";
|
|
16333
17346
|
async function callTool(name, args2, client, getIsProCached) {
|
|
16334
17347
|
switch (name) {
|
|
16335
17348
|
case "msapling_chat": {
|
|
@@ -16427,7 +17440,7 @@ ${r.response ?? ""}`;
|
|
|
16427
17440
|
return asResult2(JSON.stringify(result));
|
|
16428
17441
|
}
|
|
16429
17442
|
case "msapling_project_context": {
|
|
16430
|
-
const root =
|
|
17443
|
+
const root = resolve20(String(args2.path ?? "."));
|
|
16431
17444
|
const maxFiles = Number.isFinite(args2.max_files) ? Number(args2.max_files) : 30;
|
|
16432
17445
|
const maxKB = Number.isFinite(args2.max_file_size_kb) ? Number(args2.max_file_size_kb) : 50;
|
|
16433
17446
|
const files = buildFileTree(root, maxFiles);
|
|
@@ -16682,13 +17695,13 @@ var init_server = __esm({
|
|
|
16682
17695
|
if (inflight === 0) {
|
|
16683
17696
|
return [];
|
|
16684
17697
|
}
|
|
16685
|
-
const forcedResponses = await new Promise((
|
|
16686
|
-
this._drainResolve = () =>
|
|
17698
|
+
const forcedResponses = await new Promise((resolve21) => {
|
|
17699
|
+
this._drainResolve = () => resolve21([]);
|
|
16687
17700
|
setTimeout(() => {
|
|
16688
17701
|
this._drainResolve = null;
|
|
16689
17702
|
const remaining = Array.from(this._inflightCalls.values());
|
|
16690
17703
|
if (remaining.length === 0) {
|
|
16691
|
-
|
|
17704
|
+
resolve21([]);
|
|
16692
17705
|
return;
|
|
16693
17706
|
}
|
|
16694
17707
|
process.stderr.write(
|
|
@@ -16704,7 +17717,7 @@ var init_server = __esm({
|
|
|
16704
17717
|
}
|
|
16705
17718
|
}));
|
|
16706
17719
|
this._inflightCalls.clear();
|
|
16707
|
-
|
|
17720
|
+
resolve21(errorResponses);
|
|
16708
17721
|
}, DRAIN_TIMEOUT_MS);
|
|
16709
17722
|
});
|
|
16710
17723
|
return forcedResponses;
|
|
@@ -16904,7 +17917,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
16904
17917
|
var Header = () => /* @__PURE__ */ jsxs(Box, { borderStyle: "single", borderColor: "cyan", paddingX: 1, marginBottom: 1, children: [
|
|
16905
17918
|
/* @__PURE__ */ jsxs(Text, { bold: true, color: "cyan", children: [
|
|
16906
17919
|
"\u25CF MSapling CLI v",
|
|
16907
|
-
"2.3.6-beta.
|
|
17920
|
+
"2.3.6-beta.47"
|
|
16908
17921
|
] }),
|
|
16909
17922
|
/* @__PURE__ */ jsx(Box, { marginLeft: 2, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "Platinum Tier Architecture" }) })
|
|
16910
17923
|
] });
|
|
@@ -17309,19 +18322,19 @@ function createIdleAwarePoll(pollingIntervalRef, lastActivityRef, client, setUse
|
|
|
17309
18322
|
init_esm_shims();
|
|
17310
18323
|
init_commands();
|
|
17311
18324
|
init_plan();
|
|
17312
|
-
import { spawn as
|
|
18325
|
+
import { spawn as spawn10 } from "child_process";
|
|
17313
18326
|
|
|
17314
18327
|
// src/state/persistentState.ts
|
|
17315
18328
|
init_esm_shims();
|
|
17316
|
-
import { homedir as
|
|
17317
|
-
import { join as
|
|
17318
|
-
import { existsSync as
|
|
18329
|
+
import { homedir as homedir22 } from "os";
|
|
18330
|
+
import { join as join36, dirname as dirname5 } from "path";
|
|
18331
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync7 } from "fs";
|
|
17319
18332
|
import { readFile as readFile27, writeFile as writeFile17, rename as rename3 } from "fs/promises";
|
|
17320
|
-
import { randomBytes as
|
|
17321
|
-
var STATE_PATH =
|
|
18333
|
+
import { randomBytes as randomBytes15 } from "crypto";
|
|
18334
|
+
var STATE_PATH = join36(homedir22(), ".msapling", "state.json");
|
|
17322
18335
|
async function loadPersistentState(statePath = STATE_PATH) {
|
|
17323
18336
|
try {
|
|
17324
|
-
if (!
|
|
18337
|
+
if (!existsSync31(statePath)) return { version: 1 };
|
|
17325
18338
|
const text = await readFile27(statePath, "utf8");
|
|
17326
18339
|
const parsed = JSON.parse(text);
|
|
17327
18340
|
if (parsed.version !== 1) return { version: 1 };
|
|
@@ -17337,7 +18350,7 @@ async function loadPersistentState(statePath = STATE_PATH) {
|
|
|
17337
18350
|
async function savePersistentState(state, statePath = STATE_PATH) {
|
|
17338
18351
|
try {
|
|
17339
18352
|
const dir = dirname5(statePath);
|
|
17340
|
-
if (!
|
|
18353
|
+
if (!existsSync31(dir)) mkdirSync7(dir, { recursive: true });
|
|
17341
18354
|
const existing = await loadPersistentState(statePath);
|
|
17342
18355
|
const merged = {
|
|
17343
18356
|
version: 1,
|
|
@@ -17345,7 +18358,7 @@ async function savePersistentState(state, statePath = STATE_PATH) {
|
|
|
17345
18358
|
lastChatId: state.lastChatId ?? existing.lastChatId
|
|
17346
18359
|
};
|
|
17347
18360
|
const pid = process.pid;
|
|
17348
|
-
const rand =
|
|
18361
|
+
const rand = randomBytes15(4).toString("hex");
|
|
17349
18362
|
const tmp = `${statePath}.tmp.${pid}.${rand}`;
|
|
17350
18363
|
await writeFile17(tmp, JSON.stringify(merged, null, 2), "utf8");
|
|
17351
18364
|
await rename3(tmp, statePath);
|
|
@@ -17432,7 +18445,7 @@ ${prompt4}` : prompt4;
|
|
|
17432
18445
|
ctx.addMessage("system", "\u26A0 Local shell command executed without MCP/tool-level safety controls. Ensure command is trusted.");
|
|
17433
18446
|
try {
|
|
17434
18447
|
const shellArgv = process.platform === "win32" ? ["cmd.exe", "/c", execCmd] : ["sh", "-c", execCmd];
|
|
17435
|
-
const proc =
|
|
18448
|
+
const proc = spawn10(shellArgv[0], shellArgv.slice(1), {
|
|
17436
18449
|
stdio: ["inherit", "pipe", "pipe"]
|
|
17437
18450
|
});
|
|
17438
18451
|
let stdout = "";
|
|
@@ -17443,9 +18456,9 @@ ${prompt4}` : prompt4;
|
|
|
17443
18456
|
if (proc.stderr) proc.stderr.on("data", (chunk) => {
|
|
17444
18457
|
stderr += chunk.toString();
|
|
17445
18458
|
});
|
|
17446
|
-
await new Promise((
|
|
18459
|
+
await new Promise((resolve21, reject) => {
|
|
17447
18460
|
proc.on("close", (code) => {
|
|
17448
|
-
if (code === 0 || code === null)
|
|
18461
|
+
if (code === 0 || code === null) resolve21();
|
|
17449
18462
|
else reject(new Error(`Process exited with code ${code}`));
|
|
17450
18463
|
});
|
|
17451
18464
|
proc.on("error", reject);
|
|
@@ -17484,9 +18497,9 @@ ${prompt4}` : prompt4;
|
|
|
17484
18497
|
for (const mention of fileMentions) {
|
|
17485
18498
|
const filePath = mention.slice(1);
|
|
17486
18499
|
try {
|
|
17487
|
-
const { existsSync:
|
|
18500
|
+
const { existsSync: existsSync35 } = await import("fs");
|
|
17488
18501
|
const { readFile: readFile30 } = await import("fs/promises");
|
|
17489
|
-
if (
|
|
18502
|
+
if (existsSync35(filePath)) {
|
|
17490
18503
|
const content = await readFile30(filePath, "utf8");
|
|
17491
18504
|
const MAX_LEN = 32768;
|
|
17492
18505
|
const truncated = content.length > MAX_LEN ? content.slice(0, MAX_LEN) + "\n...[TRUNCATED]" : content;
|
|
@@ -17548,7 +18561,7 @@ init_src3();
|
|
|
17548
18561
|
init_src();
|
|
17549
18562
|
init_parseApprovalMode();
|
|
17550
18563
|
import { readFile as readFile28 } from "fs/promises";
|
|
17551
|
-
import { existsSync as
|
|
18564
|
+
import { existsSync as existsSync32 } from "fs";
|
|
17552
18565
|
async function initSession(ctx) {
|
|
17553
18566
|
try {
|
|
17554
18567
|
const journalEncrypted = await initJournalEncryption();
|
|
@@ -17573,10 +18586,10 @@ async function initSession(ctx) {
|
|
|
17573
18586
|
ctx.setShellEscapeEnabled(settings.shellEscapeEnabled !== false);
|
|
17574
18587
|
}
|
|
17575
18588
|
try {
|
|
17576
|
-
const { homedir:
|
|
17577
|
-
const { join:
|
|
17578
|
-
const userSettingsPath =
|
|
17579
|
-
if (
|
|
18589
|
+
const { homedir: homedir25 } = await import("os");
|
|
18590
|
+
const { join: join41 } = await import("path");
|
|
18591
|
+
const userSettingsPath = join41(homedir25(), ".msapling", "settings.json");
|
|
18592
|
+
if (existsSync32(userSettingsPath)) {
|
|
17580
18593
|
const userText = await readFile28(userSettingsPath, "utf8");
|
|
17581
18594
|
let parsed;
|
|
17582
18595
|
try {
|
|
@@ -17734,8 +18747,8 @@ var App = ({ compact: compact2 = false, continueSession: continueSession2 = fals
|
|
|
17734
18747
|
{ kind: "approval-request", tool: request.tool, command: request.command, reason: request.reason },
|
|
17735
18748
|
request.tool
|
|
17736
18749
|
);
|
|
17737
|
-
return new Promise((
|
|
17738
|
-
setPendingApproval({ request, resolve:
|
|
18750
|
+
return new Promise((resolve21) => {
|
|
18751
|
+
setPendingApproval({ request, resolve: resolve21 });
|
|
17739
18752
|
});
|
|
17740
18753
|
}, []);
|
|
17741
18754
|
const agent = useRef(new Agent(client, process.cwd(), requestApproval)).current;
|
|
@@ -17993,14 +19006,14 @@ var App = ({ compact: compact2 = false, continueSession: continueSession2 = fals
|
|
|
17993
19006
|
|
|
17994
19007
|
// src/runtime/bootstrap.ts
|
|
17995
19008
|
init_esm_shims();
|
|
17996
|
-
import { readFileSync as
|
|
19009
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
17997
19010
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
17998
|
-
import { dirname as dirname6, join as
|
|
19011
|
+
import { dirname as dirname6, join as join40 } from "path";
|
|
17999
19012
|
function readCliVersion2() {
|
|
18000
19013
|
const here = dirname6(fileURLToPath2(import.meta.url));
|
|
18001
19014
|
for (const rel of ["../package.json", "../../package.json"]) {
|
|
18002
19015
|
try {
|
|
18003
|
-
const pkg = JSON.parse(
|
|
19016
|
+
const pkg = JSON.parse(readFileSync6(join40(here, rel), "utf8"));
|
|
18004
19017
|
if (pkg.name && pkg.version) {
|
|
18005
19018
|
return { name: pkg.name, version: pkg.version };
|
|
18006
19019
|
}
|
|
@@ -18106,6 +19119,7 @@ function handleCliArgs(args2) {
|
|
|
18106
19119
|
}
|
|
18107
19120
|
|
|
18108
19121
|
// src/index.tsx
|
|
19122
|
+
init_src3();
|
|
18109
19123
|
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
18110
19124
|
var index_default = App;
|
|
18111
19125
|
function restoreTerminalMode() {
|
|
@@ -18131,6 +19145,12 @@ msapling: fatal ${label}: ${msg}`);
|
|
|
18131
19145
|
if (!process.env.NODE_ENV?.includes("test")) {
|
|
18132
19146
|
process.on("uncaughtException", (err) => handleFatal("uncaught exception", err));
|
|
18133
19147
|
process.on("unhandledRejection", (reason) => handleFatal("unhandled rejection", reason));
|
|
19148
|
+
process.on("exit", () => {
|
|
19149
|
+
try {
|
|
19150
|
+
BackgroundShellRegistry.killAll();
|
|
19151
|
+
} catch {
|
|
19152
|
+
}
|
|
19153
|
+
});
|
|
18134
19154
|
}
|
|
18135
19155
|
var args = process.argv.slice(2);
|
|
18136
19156
|
var compact = args.includes("--compact");
|