@mtreeai/msapling-cli 2.3.6-beta.46 → 2.3.6-beta.48
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 +1521 -409
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1032,6 +1032,47 @@ var init_src = __esm({
|
|
|
1032
1032
|
body: JSON.stringify({ query, max_results: maxResults })
|
|
1033
1033
|
});
|
|
1034
1034
|
}
|
|
1035
|
+
/**
|
|
1036
|
+
* CLI-LOOP-01: cached backend capability map (from GET /chat/health). `null`
|
|
1037
|
+
* until first probed. Cached for the client's lifetime — capabilities are a
|
|
1038
|
+
* deploy-time property of the backend, so a single probe per session suffices.
|
|
1039
|
+
*/
|
|
1040
|
+
_capabilities = null;
|
|
1041
|
+
_capabilitiesProbe = null;
|
|
1042
|
+
/**
|
|
1043
|
+
* CLI-LOOP-01: fetch (and cache) the backend capability map from
|
|
1044
|
+
* GET /chat/health. Returns an empty object when the backend is older / the
|
|
1045
|
+
* probe fails, so callers degrade to legacy behavior rather than throwing.
|
|
1046
|
+
*
|
|
1047
|
+
* Concurrent callers share a single in-flight probe.
|
|
1048
|
+
*/
|
|
1049
|
+
async getCapabilities(force = false) {
|
|
1050
|
+
if (this._capabilities && !force) return this._capabilities;
|
|
1051
|
+
if (this._capabilitiesProbe && !force) return this._capabilitiesProbe;
|
|
1052
|
+
this._capabilitiesProbe = (async () => {
|
|
1053
|
+
try {
|
|
1054
|
+
const data = await this.request("/api/chat/health");
|
|
1055
|
+
const caps = data && typeof data === "object" && data.capabilities && typeof data.capabilities === "object" ? data.capabilities : {};
|
|
1056
|
+
this._capabilities = caps;
|
|
1057
|
+
return caps;
|
|
1058
|
+
} catch {
|
|
1059
|
+
this._capabilities = {};
|
|
1060
|
+
return this._capabilities;
|
|
1061
|
+
} finally {
|
|
1062
|
+
this._capabilitiesProbe = null;
|
|
1063
|
+
}
|
|
1064
|
+
})();
|
|
1065
|
+
return this._capabilitiesProbe;
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* CLI-LOOP-01: convenience — does the backend accept the additive structured
|
|
1069
|
+
* `tool_results` array (native parallel multi-tool-use)? When false the Agent
|
|
1070
|
+
* loop uses the legacy `[TOOL_RESULT]`-per-prompt path.
|
|
1071
|
+
*/
|
|
1072
|
+
async supportsStructuredToolResults() {
|
|
1073
|
+
const caps = await this.getCapabilities();
|
|
1074
|
+
return caps.structured_tool_results === true;
|
|
1075
|
+
}
|
|
1035
1076
|
async getHistory(chatId) {
|
|
1036
1077
|
const data = await this.request(`/api/projects/chat/${chatId}/history`);
|
|
1037
1078
|
return data.messages.map((m) => ({
|
|
@@ -2194,7 +2235,7 @@ var init_RunCommandTool = __esm({
|
|
|
2194
2235
|
this.activeCommands++;
|
|
2195
2236
|
return;
|
|
2196
2237
|
}
|
|
2197
|
-
return new Promise((
|
|
2238
|
+
return new Promise((resolve21) => this.queue.push(resolve21));
|
|
2198
2239
|
}
|
|
2199
2240
|
static releaseLock() {
|
|
2200
2241
|
if (this.queue.length > 0) {
|
|
@@ -2274,9 +2315,9 @@ var init_RunCommandTool = __esm({
|
|
|
2274
2315
|
const chunks = { stdout: [], stderr: [] };
|
|
2275
2316
|
proc.stdout?.on("data", (chunk) => chunks.stdout.push(chunk));
|
|
2276
2317
|
proc.stderr?.on("data", (chunk) => chunks.stderr.push(chunk));
|
|
2277
|
-
const exitCode = await new Promise((
|
|
2278
|
-
proc.on("exit", (code) =>
|
|
2279
|
-
proc.on("error", () =>
|
|
2318
|
+
const exitCode = await new Promise((resolve21) => {
|
|
2319
|
+
proc.on("exit", (code) => resolve21(code ?? 1));
|
|
2320
|
+
proc.on("error", () => resolve21(1));
|
|
2280
2321
|
});
|
|
2281
2322
|
const stdout = Buffer.concat(chunks.stdout).toString("utf-8");
|
|
2282
2323
|
const stderr = Buffer.concat(chunks.stderr).toString("utf-8");
|
|
@@ -2434,7 +2475,7 @@ var init_src2 = __esm({
|
|
|
2434
2475
|
const message = `Content-Length: ${Buffer.byteLength(content, "utf8")}\r
|
|
2435
2476
|
\r
|
|
2436
2477
|
${content}`;
|
|
2437
|
-
return new Promise((
|
|
2478
|
+
return new Promise((resolve21, reject) => {
|
|
2438
2479
|
const timeoutHandle = setTimeout(() => {
|
|
2439
2480
|
this.pendingRequests.delete(id);
|
|
2440
2481
|
reject(new Error(`LSP request timeout after ${timeoutMs}ms: ${method}`));
|
|
@@ -2442,7 +2483,7 @@ ${content}`;
|
|
|
2442
2483
|
this.pendingRequests.set(id, {
|
|
2443
2484
|
resolve: (response) => {
|
|
2444
2485
|
clearTimeout(timeoutHandle);
|
|
2445
|
-
|
|
2486
|
+
resolve21(response);
|
|
2446
2487
|
},
|
|
2447
2488
|
reject: (error) => {
|
|
2448
2489
|
clearTimeout(timeoutHandle);
|
|
@@ -2595,9 +2636,9 @@ var init_SubShellTool = __esm({
|
|
|
2595
2636
|
}
|
|
2596
2637
|
throw e;
|
|
2597
2638
|
}
|
|
2598
|
-
await new Promise((
|
|
2599
|
-
proc.on("exit", () =>
|
|
2600
|
-
proc.on("error", () =>
|
|
2639
|
+
await new Promise((resolve21) => {
|
|
2640
|
+
proc.on("exit", () => resolve21());
|
|
2641
|
+
proc.on("error", () => resolve21());
|
|
2601
2642
|
});
|
|
2602
2643
|
return { content: `Successfully launched separate window for ${args2.worker_id}` };
|
|
2603
2644
|
}
|
|
@@ -2676,27 +2717,42 @@ async function* walkFiles(root, current = root) {
|
|
|
2676
2717
|
}
|
|
2677
2718
|
}
|
|
2678
2719
|
}
|
|
2720
|
+
function rgCandidates() {
|
|
2721
|
+
if (process.platform === "win32") {
|
|
2722
|
+
return [
|
|
2723
|
+
"rg.exe",
|
|
2724
|
+
"rg",
|
|
2725
|
+
"C:\\Program Files\\ripgrep\\rg.exe",
|
|
2726
|
+
"C:\\ProgramData\\chocolatey\\bin\\rg.exe"
|
|
2727
|
+
];
|
|
2728
|
+
}
|
|
2729
|
+
return ["rg", "/usr/bin/rg", "/usr/local/bin/rg", "/opt/homebrew/bin/rg"];
|
|
2730
|
+
}
|
|
2679
2731
|
async function findRg() {
|
|
2680
|
-
|
|
2681
|
-
for (const bin of
|
|
2732
|
+
if (_rgCache.resolved) return _rgCache.value;
|
|
2733
|
+
for (const bin of rgCandidates()) {
|
|
2682
2734
|
try {
|
|
2683
|
-
const exited = await new Promise((
|
|
2735
|
+
const exited = await new Promise((resolve21) => {
|
|
2684
2736
|
try {
|
|
2685
2737
|
const p = spawn4(bin, ["--version"], { stdio: ["ignore", "pipe", "pipe"] });
|
|
2686
|
-
p.on("error", () =>
|
|
2687
|
-
p.on("exit", (code) =>
|
|
2738
|
+
p.on("error", () => resolve21(null));
|
|
2739
|
+
p.on("exit", (code) => resolve21(code));
|
|
2688
2740
|
} catch {
|
|
2689
|
-
|
|
2741
|
+
resolve21(null);
|
|
2690
2742
|
}
|
|
2691
2743
|
});
|
|
2692
|
-
if (exited === 0)
|
|
2744
|
+
if (exited === 0) {
|
|
2745
|
+
_rgCache = { resolved: true, value: bin };
|
|
2746
|
+
return bin;
|
|
2747
|
+
}
|
|
2693
2748
|
} catch {
|
|
2694
2749
|
}
|
|
2695
2750
|
}
|
|
2751
|
+
_rgCache = { resolved: true, value: null };
|
|
2696
2752
|
return null;
|
|
2697
2753
|
}
|
|
2698
2754
|
function runRg(bin, args2) {
|
|
2699
|
-
return new Promise((
|
|
2755
|
+
return new Promise((resolve21) => {
|
|
2700
2756
|
const p = spawn4(bin, args2, { stdio: ["ignore", "pipe", "pipe"] });
|
|
2701
2757
|
let stdout = "";
|
|
2702
2758
|
let stderr = "";
|
|
@@ -2707,17 +2763,24 @@ function runRg(bin, args2) {
|
|
|
2707
2763
|
stderr += d.toString("utf8");
|
|
2708
2764
|
});
|
|
2709
2765
|
p.on("error", (e) => {
|
|
2710
|
-
|
|
2766
|
+
resolve21({ stdout, stderr: stderr + (e?.message ?? ""), exitCode: -1 });
|
|
2711
2767
|
});
|
|
2712
2768
|
p.on("exit", (code) => {
|
|
2713
|
-
|
|
2769
|
+
resolve21({ stdout, stderr, exitCode: code });
|
|
2714
2770
|
});
|
|
2715
2771
|
});
|
|
2716
2772
|
}
|
|
2717
|
-
async function nodeGrepFallback(pattern, searchRoot, maxMatches, caseSensitive) {
|
|
2773
|
+
async function nodeGrepFallback(pattern, searchRoot, maxMatches, caseSensitive, include, exclude) {
|
|
2718
2774
|
const regex = new RegExp(pattern, caseSensitive ? "" : "i");
|
|
2719
2775
|
const matches2 = [];
|
|
2776
|
+
const relPaths = [];
|
|
2720
2777
|
for await (const relPath of walkFiles(searchRoot)) {
|
|
2778
|
+
if (include && !include.test(relPath)) continue;
|
|
2779
|
+
if (exclude && exclude.test(relPath)) continue;
|
|
2780
|
+
relPaths.push(relPath);
|
|
2781
|
+
}
|
|
2782
|
+
relPaths.sort();
|
|
2783
|
+
for (const relPath of relPaths) {
|
|
2721
2784
|
if (matches2.length >= maxMatches) break;
|
|
2722
2785
|
const fullPath = join6(searchRoot, relPath);
|
|
2723
2786
|
try {
|
|
@@ -2737,7 +2800,7 @@ async function nodeGrepFallback(pattern, searchRoot, maxMatches, caseSensitive)
|
|
|
2737
2800
|
}
|
|
2738
2801
|
return matches2;
|
|
2739
2802
|
}
|
|
2740
|
-
var MAX_GLOB_RESULTS, MAX_GREP_MATCHES, SKIP_DIRS, GlobFilesTool, GrepSearchTool;
|
|
2803
|
+
var MAX_GLOB_RESULTS, MAX_GREP_MATCHES, SKIP_DIRS, GlobFilesTool, _rgCache, GrepSearchTool;
|
|
2741
2804
|
var init_SearchTools = __esm({
|
|
2742
2805
|
"../core/src/tools/SearchTools.ts"() {
|
|
2743
2806
|
"use strict";
|
|
@@ -2825,9 +2888,13 @@ var init_SearchTools = __esm({
|
|
|
2825
2888
|
}
|
|
2826
2889
|
}
|
|
2827
2890
|
};
|
|
2891
|
+
_rgCache = {
|
|
2892
|
+
resolved: false,
|
|
2893
|
+
value: null
|
|
2894
|
+
};
|
|
2828
2895
|
GrepSearchTool = class extends BaseTool {
|
|
2829
2896
|
name = "grep_search";
|
|
2830
|
-
description =
|
|
2897
|
+
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
2898
|
parameters = {
|
|
2832
2899
|
type: "object",
|
|
2833
2900
|
required: ["pattern"],
|
|
@@ -2843,6 +2910,14 @@ var init_SearchTools = __esm({
|
|
|
2843
2910
|
case_sensitive: {
|
|
2844
2911
|
type: "boolean",
|
|
2845
2912
|
description: "Whether the search is case-sensitive (default: false)."
|
|
2913
|
+
},
|
|
2914
|
+
include: {
|
|
2915
|
+
type: "string",
|
|
2916
|
+
description: 'Optional glob of files to include (e.g. "*.ts", "src/**/*.tsx"). Passed to ripgrep as --glob.'
|
|
2917
|
+
},
|
|
2918
|
+
exclude: {
|
|
2919
|
+
type: "string",
|
|
2920
|
+
description: 'Optional glob of files to exclude (e.g. "*.test.ts", "dist/**"). Passed to ripgrep as a negated --glob.'
|
|
2846
2921
|
}
|
|
2847
2922
|
}
|
|
2848
2923
|
};
|
|
@@ -2870,6 +2945,8 @@ var init_SearchTools = __esm({
|
|
|
2870
2945
|
};
|
|
2871
2946
|
}
|
|
2872
2947
|
const caseSensitive = args2?.case_sensitive === true;
|
|
2948
|
+
const include = typeof args2?.include === "string" && args2.include.trim() ? args2.include.trim() : void 0;
|
|
2949
|
+
const exclude = typeof args2?.exclude === "string" && args2.exclude.trim() ? args2.exclude.trim() : void 0;
|
|
2873
2950
|
const rg = await findRg();
|
|
2874
2951
|
if (rg) {
|
|
2875
2952
|
try {
|
|
@@ -2877,9 +2954,14 @@ var init_SearchTools = __esm({
|
|
|
2877
2954
|
"--line-number",
|
|
2878
2955
|
`--max-count=${MAX_GREP_MATCHES}`,
|
|
2879
2956
|
"--no-heading",
|
|
2880
|
-
"--color=never"
|
|
2957
|
+
"--color=never",
|
|
2958
|
+
// Deterministic output: sort by file path so repeated queries return
|
|
2959
|
+
// identical ordering (ripgrep parallelises by default → non-stable).
|
|
2960
|
+
"--sort=path"
|
|
2881
2961
|
];
|
|
2882
2962
|
if (!caseSensitive) rgArgs.push("--ignore-case");
|
|
2963
|
+
if (include) rgArgs.push("--glob", include);
|
|
2964
|
+
if (exclude) rgArgs.push("--glob", `!${exclude}`);
|
|
2883
2965
|
rgArgs.push("--", pattern, searchPath);
|
|
2884
2966
|
const { stdout, stderr, exitCode } = await runRg(rg, rgArgs);
|
|
2885
2967
|
if (exitCode !== 0 && exitCode !== 1) {
|
|
@@ -2899,15 +2981,28 @@ var init_SearchTools = __esm({
|
|
|
2899
2981
|
}
|
|
2900
2982
|
try {
|
|
2901
2983
|
const isFile = statSync(searchPath).isFile();
|
|
2984
|
+
const includeRe = include ? globToRegExp(include) : void 0;
|
|
2985
|
+
const excludeRe = exclude ? globToRegExp(exclude) : void 0;
|
|
2902
2986
|
let matches2;
|
|
2903
2987
|
if (isFile) {
|
|
2904
|
-
const content = await readFile4(searchPath, "utf8");
|
|
2905
|
-
const regex = new RegExp(pattern, caseSensitive ? "" : "i");
|
|
2906
|
-
const lines = content.split("\n");
|
|
2907
2988
|
const relPath = args2?.path ?? searchPath;
|
|
2908
|
-
|
|
2989
|
+
if (includeRe && !includeRe.test(String(relPath)) || excludeRe && excludeRe.test(String(relPath))) {
|
|
2990
|
+
matches2 = [];
|
|
2991
|
+
} else {
|
|
2992
|
+
const content = await readFile4(searchPath, "utf8");
|
|
2993
|
+
const regex = new RegExp(pattern, caseSensitive ? "" : "i");
|
|
2994
|
+
const lines = content.split("\n");
|
|
2995
|
+
matches2 = lines.flatMap((line, idx) => regex.test(line) ? [`${relPath}:${idx + 1}:${line}`] : []).slice(0, MAX_GREP_MATCHES);
|
|
2996
|
+
}
|
|
2909
2997
|
} else {
|
|
2910
|
-
matches2 = await nodeGrepFallback(
|
|
2998
|
+
matches2 = await nodeGrepFallback(
|
|
2999
|
+
pattern,
|
|
3000
|
+
searchPath,
|
|
3001
|
+
MAX_GREP_MATCHES,
|
|
3002
|
+
caseSensitive,
|
|
3003
|
+
includeRe,
|
|
3004
|
+
excludeRe
|
|
3005
|
+
);
|
|
2911
3006
|
}
|
|
2912
3007
|
if (matches2.length === 0) return { content: "No matches found." };
|
|
2913
3008
|
const suffix = matches2.length >= MAX_GREP_MATCHES ? `
|
|
@@ -3400,13 +3495,164 @@ Pre-edit content backed up to: ${backedUpTo}`;
|
|
|
3400
3495
|
}
|
|
3401
3496
|
});
|
|
3402
3497
|
|
|
3498
|
+
// ../core/src/agent/namedAgents.ts
|
|
3499
|
+
import { readdirSync as readdirSync2, statSync as statSync3, readFileSync as readFileSync2 } from "fs";
|
|
3500
|
+
import { join as join9 } from "path";
|
|
3501
|
+
function normalizeToolName(raw) {
|
|
3502
|
+
const trimmed = raw.trim();
|
|
3503
|
+
if (!trimmed) return "";
|
|
3504
|
+
const lower = trimmed.toLowerCase();
|
|
3505
|
+
const key = lower.replace(/[^a-z0-9]/g, "");
|
|
3506
|
+
if (TOOL_NAME_ALIASES[key]) return TOOL_NAME_ALIASES[key];
|
|
3507
|
+
return lower;
|
|
3508
|
+
}
|
|
3509
|
+
function discoverAgentFiles(cwd, homeDir) {
|
|
3510
|
+
const seen = /* @__PURE__ */ new Map();
|
|
3511
|
+
for (const scope of [
|
|
3512
|
+
{ dir: join9(homeDir, ".msapling", "agents"), scope: "user" },
|
|
3513
|
+
{ dir: join9(cwd, ".msapling", "agents"), scope: "project" }
|
|
3514
|
+
]) {
|
|
3515
|
+
const entries = safeReaddir(scope.dir);
|
|
3516
|
+
for (const fname of entries) {
|
|
3517
|
+
if (!fname.toLowerCase().endsWith(".md")) continue;
|
|
3518
|
+
const path2 = join9(scope.dir, fname);
|
|
3519
|
+
if (!isRegularFile(path2)) continue;
|
|
3520
|
+
const base = fname.slice(0, -".md".length);
|
|
3521
|
+
seen.set(base, { name: base, path: path2, scope: scope.scope });
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
return [...seen.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
3525
|
+
}
|
|
3526
|
+
function safeReaddir(dir) {
|
|
3527
|
+
try {
|
|
3528
|
+
return readdirSync2(dir);
|
|
3529
|
+
} catch {
|
|
3530
|
+
return [];
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
function isRegularFile(p) {
|
|
3534
|
+
try {
|
|
3535
|
+
return statSync3(p).isFile();
|
|
3536
|
+
} catch {
|
|
3537
|
+
return false;
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
3540
|
+
function parseToolsValue(raw) {
|
|
3541
|
+
if (raw === void 0) return void 0;
|
|
3542
|
+
let s = raw.trim();
|
|
3543
|
+
if (s === "") return void 0;
|
|
3544
|
+
if (s === "*" || s.toLowerCase() === "all") return void 0;
|
|
3545
|
+
if (s.startsWith("[") && s.endsWith("]")) s = s.slice(1, -1);
|
|
3546
|
+
const parts = s.split(",").map((t) => t.trim().replace(/^['"]|['"]$/g, "")).map((t) => normalizeToolName(t)).filter(Boolean);
|
|
3547
|
+
return [...new Set(parts)];
|
|
3548
|
+
}
|
|
3549
|
+
function parseAgentFile(text, fallbackName) {
|
|
3550
|
+
const fm = text.match(/^---\s*\n([\s\S]*?)\n---\s*\n?/);
|
|
3551
|
+
if (!fm) {
|
|
3552
|
+
const body = text.trim();
|
|
3553
|
+
const firstLine = body.split(/\r?\n/).find((l) => l.trim()) ?? "";
|
|
3554
|
+
return {
|
|
3555
|
+
name: fallbackName.toLowerCase(),
|
|
3556
|
+
description: firstLine.replace(/^#\s+/, "").trim() || `(agent: ${fallbackName})`,
|
|
3557
|
+
systemPrompt: body
|
|
3558
|
+
};
|
|
3559
|
+
}
|
|
3560
|
+
const meta = fm[1];
|
|
3561
|
+
const systemPrompt = text.slice(fm[0].length).trim();
|
|
3562
|
+
const scalar = (key) => {
|
|
3563
|
+
const m = meta.match(new RegExp(`^${key}[ \\t]*:[ \\t]*(.+)$`, "m"));
|
|
3564
|
+
return m ? m[1].trim().replace(/^['"]|['"]$/g, "") : void 0;
|
|
3565
|
+
};
|
|
3566
|
+
const name = (scalar("name") ?? fallbackName).toLowerCase();
|
|
3567
|
+
const description = scalar("description") ?? `(agent: ${name})`;
|
|
3568
|
+
const model = scalar("model");
|
|
3569
|
+
let tools;
|
|
3570
|
+
const inlineTools = scalar("tools");
|
|
3571
|
+
const blockMatch = meta.match(/^tools[ \t]*:[ \t]*\n((?:[ \t]*-[ \t]*.+\n?)+)/m);
|
|
3572
|
+
if (blockMatch && (inlineTools === void 0 || inlineTools === "")) {
|
|
3573
|
+
const items = blockMatch[1].split(/\r?\n/).map((l) => l.replace(/^[ \t]*-[ \t]*/, "").trim().replace(/^['"]|['"]$/g, "")).map((t) => normalizeToolName(t)).filter(Boolean);
|
|
3574
|
+
tools = [...new Set(items)];
|
|
3575
|
+
} else {
|
|
3576
|
+
tools = parseToolsValue(inlineTools);
|
|
3577
|
+
}
|
|
3578
|
+
return { name, description, model, tools, systemPrompt };
|
|
3579
|
+
}
|
|
3580
|
+
function loadNamedAgents(cwd, homeDir) {
|
|
3581
|
+
const out = [];
|
|
3582
|
+
for (const file of discoverAgentFiles(cwd, homeDir)) {
|
|
3583
|
+
let text;
|
|
3584
|
+
try {
|
|
3585
|
+
text = readFileSync2(file.path, "utf8");
|
|
3586
|
+
} catch {
|
|
3587
|
+
continue;
|
|
3588
|
+
}
|
|
3589
|
+
const parsed = parseAgentFile(text, file.name);
|
|
3590
|
+
out.push({
|
|
3591
|
+
name: parsed.name,
|
|
3592
|
+
description: parsed.description,
|
|
3593
|
+
model: parsed.model,
|
|
3594
|
+
tools: parsed.tools,
|
|
3595
|
+
systemPrompt: parsed.systemPrompt,
|
|
3596
|
+
path: file.path,
|
|
3597
|
+
scope: file.scope
|
|
3598
|
+
});
|
|
3599
|
+
}
|
|
3600
|
+
const byName = /* @__PURE__ */ new Map();
|
|
3601
|
+
for (const a of out) {
|
|
3602
|
+
const prev = byName.get(a.name);
|
|
3603
|
+
if (!prev || prev.scope === "user" && a.scope === "project") {
|
|
3604
|
+
byName.set(a.name, a);
|
|
3605
|
+
}
|
|
3606
|
+
}
|
|
3607
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
3608
|
+
}
|
|
3609
|
+
function findNamedAgent(name, cwd, homeDir) {
|
|
3610
|
+
const target = name.trim().toLowerCase();
|
|
3611
|
+
return loadNamedAgents(cwd, homeDir).find((a) => a.name === target) ?? null;
|
|
3612
|
+
}
|
|
3613
|
+
var TOOL_NAME_ALIASES;
|
|
3614
|
+
var init_namedAgents = __esm({
|
|
3615
|
+
"../core/src/agent/namedAgents.ts"() {
|
|
3616
|
+
"use strict";
|
|
3617
|
+
init_esm_shims();
|
|
3618
|
+
TOOL_NAME_ALIASES = {
|
|
3619
|
+
read: "read_file",
|
|
3620
|
+
write: "write_file",
|
|
3621
|
+
edit: "edit_file",
|
|
3622
|
+
multiedit: "multi_edit_file",
|
|
3623
|
+
patch: "patch_file",
|
|
3624
|
+
move: "move_file",
|
|
3625
|
+
delete: "delete_file",
|
|
3626
|
+
bash: "bash_command",
|
|
3627
|
+
shell: "bash_command",
|
|
3628
|
+
run: "run_command",
|
|
3629
|
+
glob: "glob_files",
|
|
3630
|
+
grep: "grep_search",
|
|
3631
|
+
ls: "list_directory",
|
|
3632
|
+
list: "list_directory",
|
|
3633
|
+
webfetch: "web_fetch",
|
|
3634
|
+
websearch: "web_search",
|
|
3635
|
+
fetch: "web_fetch",
|
|
3636
|
+
search: "web_search",
|
|
3637
|
+
task: "dispatch_agent",
|
|
3638
|
+
dispatch: "dispatch_agent",
|
|
3639
|
+
todowrite: "todo_write",
|
|
3640
|
+
todoread: "todo_read",
|
|
3641
|
+
notebookread: "notebook_read",
|
|
3642
|
+
notebookedit: "notebook_edit_cell"
|
|
3643
|
+
};
|
|
3644
|
+
}
|
|
3645
|
+
});
|
|
3646
|
+
|
|
3403
3647
|
// ../core/src/tools/DispatchAgentTool.ts
|
|
3648
|
+
import { homedir as homedir5 } from "os";
|
|
3404
3649
|
var SUB_AGENT_TIMEOUT_MS, MAX_PROMPT_CHARS, MAX_RESPONSE_CHARS, DEFAULT_SUB_AGENT_MODEL, DispatchAgentTool;
|
|
3405
3650
|
var init_DispatchAgentTool = __esm({
|
|
3406
3651
|
"../core/src/tools/DispatchAgentTool.ts"() {
|
|
3407
3652
|
"use strict";
|
|
3408
3653
|
init_esm_shims();
|
|
3409
3654
|
init_BaseTool();
|
|
3655
|
+
init_namedAgents();
|
|
3410
3656
|
SUB_AGENT_TIMEOUT_MS = 12e4;
|
|
3411
3657
|
MAX_PROMPT_CHARS = 8e3;
|
|
3412
3658
|
MAX_RESPONSE_CHARS = 16e3;
|
|
@@ -3422,6 +3668,10 @@ var init_DispatchAgentTool = __esm({
|
|
|
3422
3668
|
type: "string",
|
|
3423
3669
|
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
3670
|
},
|
|
3671
|
+
agent: {
|
|
3672
|
+
type: "string",
|
|
3673
|
+
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."
|
|
3674
|
+
},
|
|
3425
3675
|
description: {
|
|
3426
3676
|
type: "string",
|
|
3427
3677
|
description: 'Short human-readable label for this sub-task (e.g. "Summarise auth.ts"). Appears in the session log alongside the result.'
|
|
@@ -3440,12 +3690,19 @@ var init_DispatchAgentTool = __esm({
|
|
|
3440
3690
|
parentChatId;
|
|
3441
3691
|
projectRoot;
|
|
3442
3692
|
onSubagentStop;
|
|
3693
|
+
runNamedAgent;
|
|
3443
3694
|
constructor(options) {
|
|
3444
3695
|
super();
|
|
3445
3696
|
this.client = options.client;
|
|
3446
3697
|
this.parentChatId = options.parentChatId ?? void 0;
|
|
3447
3698
|
this.projectRoot = options.projectRoot;
|
|
3448
3699
|
this.onSubagentStop = options.onSubagentStop;
|
|
3700
|
+
this.runNamedAgent = options.runNamedAgent;
|
|
3701
|
+
}
|
|
3702
|
+
/** Resolve the user home dir, preferring env overrides so test isolation
|
|
3703
|
+
* (HOME / USERPROFILE) is honored exactly like outputStyle.ts. */
|
|
3704
|
+
resolveHome() {
|
|
3705
|
+
return process.env.HOME || process.env.USERPROFILE || homedir5();
|
|
3449
3706
|
}
|
|
3450
3707
|
/** Fire the subagent-stop callback defensively — it must never throw. */
|
|
3451
3708
|
signalStop(info) {
|
|
@@ -3462,9 +3719,19 @@ var init_DispatchAgentTool = __esm({
|
|
|
3462
3719
|
};
|
|
3463
3720
|
}
|
|
3464
3721
|
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
3722
|
const chatId = typeof args2.chat_id === "string" && args2.chat_id.trim() ? args2.chat_id.trim() : this.parentChatId;
|
|
3467
|
-
|
|
3723
|
+
let named = null;
|
|
3724
|
+
if (typeof args2.agent === "string" && args2.agent.trim()) {
|
|
3725
|
+
named = findNamedAgent(args2.agent.trim(), this.projectRoot, this.resolveHome());
|
|
3726
|
+
if (!named) {
|
|
3727
|
+
return {
|
|
3728
|
+
content: `dispatch_agent error: no named agent "${args2.agent.trim()}" found in .msapling/agents/. Run /agents to list available agents.`,
|
|
3729
|
+
isError: true
|
|
3730
|
+
};
|
|
3731
|
+
}
|
|
3732
|
+
}
|
|
3733
|
+
const model = typeof args2.model === "string" && args2.model.trim() ? args2.model.trim() : named?.model && named.model.trim() ? named.model.trim() : DEFAULT_SUB_AGENT_MODEL;
|
|
3734
|
+
const description = typeof args2.description === "string" && args2.description.trim() ? args2.description.trim() : named ? `agent:${named.name}` : "sub-task";
|
|
3468
3735
|
let response = "";
|
|
3469
3736
|
const timeoutPromise = new Promise(
|
|
3470
3737
|
(_, reject) => setTimeout(
|
|
@@ -3473,10 +3740,28 @@ var init_DispatchAgentTool = __esm({
|
|
|
3473
3740
|
)
|
|
3474
3741
|
);
|
|
3475
3742
|
const streamPromise = (async () => {
|
|
3743
|
+
if (named && this.runNamedAgent) {
|
|
3744
|
+
await this.runNamedAgent({
|
|
3745
|
+
agent: named,
|
|
3746
|
+
prompt: prompt4,
|
|
3747
|
+
model,
|
|
3748
|
+
chatId,
|
|
3749
|
+
onContent: (chunk) => {
|
|
3750
|
+
response += chunk;
|
|
3751
|
+
}
|
|
3752
|
+
});
|
|
3753
|
+
return;
|
|
3754
|
+
}
|
|
3755
|
+
const effectivePrompt = named ? `[System prompt for agent "${named.name}"]
|
|
3756
|
+
${named.systemPrompt}
|
|
3757
|
+
|
|
3758
|
+
---
|
|
3759
|
+
|
|
3760
|
+
${prompt4}`.slice(0, MAX_PROMPT_CHARS) : prompt4;
|
|
3476
3761
|
const stream = this.client.streamChat({
|
|
3477
|
-
prompt:
|
|
3762
|
+
prompt: effectivePrompt,
|
|
3478
3763
|
model,
|
|
3479
|
-
// No tools — sub-agent is read/reasoning only.
|
|
3764
|
+
// No tools — sub-agent is read/reasoning only on this path.
|
|
3480
3765
|
tools: [],
|
|
3481
3766
|
...chatId ? { chat_id: chatId } : {},
|
|
3482
3767
|
// Forward project root as context so the backend can enrich the prompt
|
|
@@ -4051,12 +4336,12 @@ Command: ${command}`,
|
|
|
4051
4336
|
proc.stdout?.on("data", (chunk) => chunks.stdout.push(chunk));
|
|
4052
4337
|
proc.stderr?.on("data", (chunk) => chunks.stderr.push(chunk));
|
|
4053
4338
|
const timeoutPromise = new Promise(
|
|
4054
|
-
(
|
|
4339
|
+
(resolve21) => setTimeout(() => resolve21("timeout"), timeoutMs)
|
|
4055
4340
|
);
|
|
4056
4341
|
const processPromise = (async () => {
|
|
4057
|
-
const exitCode2 = await new Promise((
|
|
4058
|
-
proc.on("exit", (code) =>
|
|
4059
|
-
proc.on("error", () =>
|
|
4342
|
+
const exitCode2 = await new Promise((resolve21) => {
|
|
4343
|
+
proc.on("exit", (code) => resolve21(code ?? 1));
|
|
4344
|
+
proc.on("error", () => resolve21(1));
|
|
4060
4345
|
});
|
|
4061
4346
|
const stdout2 = Buffer.concat(chunks.stdout).toString("utf-8");
|
|
4062
4347
|
const stderr2 = Buffer.concat(chunks.stderr).toString("utf-8");
|
|
@@ -4090,8 +4375,315 @@ ${stderr}`;
|
|
|
4090
4375
|
}
|
|
4091
4376
|
});
|
|
4092
4377
|
|
|
4378
|
+
// ../core/src/tools/BackgroundShellTool.ts
|
|
4379
|
+
import { resolve as resolve8, normalize as normalize7, relative as relative8, isAbsolute as isAbsolute8 } from "path";
|
|
4380
|
+
import { spawn as spawn6 } from "child_process";
|
|
4381
|
+
function formatBackgroundShells(shells) {
|
|
4382
|
+
if (shells.length === 0) return "No background shells.";
|
|
4383
|
+
return shells.map((s) => {
|
|
4384
|
+
const dur = ((s.endedAt ?? Date.now()) - s.startedAt) / 1e3;
|
|
4385
|
+
const exit = s.exitCode !== void 0 && s.exitCode !== null ? ` exit=${s.exitCode}` : "";
|
|
4386
|
+
const pid = s.pid !== void 0 ? ` pid=${s.pid}` : "";
|
|
4387
|
+
return `[${s.id}] ${s.status.toUpperCase()}${pid}${exit} ${dur.toFixed(1)}s $ ${s.command}`;
|
|
4388
|
+
}).join("\n");
|
|
4389
|
+
}
|
|
4390
|
+
function resolveCwd(projectRoot, rawArg) {
|
|
4391
|
+
let cwd = projectRoot;
|
|
4392
|
+
if (typeof rawArg === "string" && rawArg.trim()) {
|
|
4393
|
+
const raw = rawArg.trim();
|
|
4394
|
+
const resolved = isAbsolute8(raw) ? normalize7(raw) : resolve8(projectRoot, raw);
|
|
4395
|
+
const rel = relative8(projectRoot, resolved);
|
|
4396
|
+
if (rel.startsWith("..") || isAbsolute8(rel)) {
|
|
4397
|
+
return { error: `Security Block: cwd "${raw}" resolves outside the project root.` };
|
|
4398
|
+
}
|
|
4399
|
+
cwd = resolved;
|
|
4400
|
+
}
|
|
4401
|
+
return { cwd };
|
|
4402
|
+
}
|
|
4403
|
+
var MAX_BUFFER_BYTES, MAX_FINISHED_RETAINED, BackgroundShellRegistryImpl, BackgroundShellRegistry, BashBackgroundTool, ListBackgroundShellsTool, ReadBackgroundShellTool, KillBackgroundShellTool;
|
|
4404
|
+
var init_BackgroundShellTool = __esm({
|
|
4405
|
+
"../core/src/tools/BackgroundShellTool.ts"() {
|
|
4406
|
+
"use strict";
|
|
4407
|
+
init_esm_shims();
|
|
4408
|
+
init_BaseTool();
|
|
4409
|
+
init_BashTool();
|
|
4410
|
+
MAX_BUFFER_BYTES = 256 * 1024;
|
|
4411
|
+
MAX_FINISHED_RETAINED = 50;
|
|
4412
|
+
BackgroundShellRegistryImpl = class {
|
|
4413
|
+
shells = /* @__PURE__ */ new Map();
|
|
4414
|
+
counter = 0;
|
|
4415
|
+
/** Monotonic, human-readable id: bg-1, bg-2, … */
|
|
4416
|
+
nextId() {
|
|
4417
|
+
this.counter += 1;
|
|
4418
|
+
return `bg-${this.counter}`;
|
|
4419
|
+
}
|
|
4420
|
+
/**
|
|
4421
|
+
* Register an already-spawned child process and start buffering its output.
|
|
4422
|
+
* Returns the assigned shell id.
|
|
4423
|
+
*/
|
|
4424
|
+
register(proc, command, cwd) {
|
|
4425
|
+
const id = this.nextId();
|
|
4426
|
+
const entry = {
|
|
4427
|
+
id,
|
|
4428
|
+
command,
|
|
4429
|
+
cwd,
|
|
4430
|
+
status: "running",
|
|
4431
|
+
pid: proc.pid,
|
|
4432
|
+
startedAt: Date.now(),
|
|
4433
|
+
proc,
|
|
4434
|
+
buffer: "",
|
|
4435
|
+
bufferTruncated: false
|
|
4436
|
+
};
|
|
4437
|
+
this.shells.set(id, entry);
|
|
4438
|
+
const append = (chunk) => {
|
|
4439
|
+
entry.buffer += chunk.toString("utf-8");
|
|
4440
|
+
if (entry.buffer.length > MAX_BUFFER_BYTES) {
|
|
4441
|
+
entry.buffer = entry.buffer.slice(entry.buffer.length - MAX_BUFFER_BYTES);
|
|
4442
|
+
entry.bufferTruncated = true;
|
|
4443
|
+
}
|
|
4444
|
+
};
|
|
4445
|
+
proc.stdout?.on("data", append);
|
|
4446
|
+
proc.stderr?.on("data", append);
|
|
4447
|
+
proc.on("error", (err) => {
|
|
4448
|
+
if (entry.status === "running") {
|
|
4449
|
+
entry.status = "error";
|
|
4450
|
+
entry.endedAt = Date.now();
|
|
4451
|
+
entry.buffer += `
|
|
4452
|
+
[spawn error: ${err.message}]`;
|
|
4453
|
+
}
|
|
4454
|
+
this.reapFinished();
|
|
4455
|
+
});
|
|
4456
|
+
proc.on("exit", (code, signal) => {
|
|
4457
|
+
if (entry.status === "running") {
|
|
4458
|
+
entry.status = "exited";
|
|
4459
|
+
}
|
|
4460
|
+
entry.exitCode = code;
|
|
4461
|
+
entry.endedAt = Date.now();
|
|
4462
|
+
if (signal) entry.buffer += `
|
|
4463
|
+
[terminated by signal ${signal}]`;
|
|
4464
|
+
this.reapFinished();
|
|
4465
|
+
});
|
|
4466
|
+
return id;
|
|
4467
|
+
}
|
|
4468
|
+
/** Evict the oldest finished shells once we exceed the retention cap. */
|
|
4469
|
+
reapFinished() {
|
|
4470
|
+
const finished = [...this.shells.values()].filter((s) => s.status !== "running").sort((a, b) => (a.endedAt ?? 0) - (b.endedAt ?? 0));
|
|
4471
|
+
let excess = finished.length - MAX_FINISHED_RETAINED;
|
|
4472
|
+
for (let i = 0; i < finished.length && excess > 0; i++, excess--) {
|
|
4473
|
+
this.shells.delete(finished[i].id);
|
|
4474
|
+
}
|
|
4475
|
+
}
|
|
4476
|
+
/** Public view of all tracked shells, newest first. */
|
|
4477
|
+
list() {
|
|
4478
|
+
return [...this.shells.values()].sort((a, b) => b.startedAt - a.startedAt).map((s) => this.toInfo(s));
|
|
4479
|
+
}
|
|
4480
|
+
get(id) {
|
|
4481
|
+
const s = this.shells.get(id);
|
|
4482
|
+
return s ? this.toInfo(s) : void 0;
|
|
4483
|
+
}
|
|
4484
|
+
/**
|
|
4485
|
+
* Read the buffered output of a shell. `lines` (optional) tails the last N
|
|
4486
|
+
* lines. Returns null if the id is unknown.
|
|
4487
|
+
*/
|
|
4488
|
+
readOutput(id, lines) {
|
|
4489
|
+
const s = this.shells.get(id);
|
|
4490
|
+
if (!s) return null;
|
|
4491
|
+
let output = s.buffer;
|
|
4492
|
+
if (lines && lines > 0) {
|
|
4493
|
+
const all = output.split("\n");
|
|
4494
|
+
output = all.slice(-lines).join("\n");
|
|
4495
|
+
}
|
|
4496
|
+
return { output, truncated: s.bufferTruncated };
|
|
4497
|
+
}
|
|
4498
|
+
/**
|
|
4499
|
+
* Terminate a running shell. Returns:
|
|
4500
|
+
* - 'killed' on success
|
|
4501
|
+
* - 'not-found' if the id is unknown
|
|
4502
|
+
* - 'already' if the shell had already finished
|
|
4503
|
+
*/
|
|
4504
|
+
kill(id) {
|
|
4505
|
+
const s = this.shells.get(id);
|
|
4506
|
+
if (!s) return "not-found";
|
|
4507
|
+
if (s.status !== "running") return "already";
|
|
4508
|
+
s.status = "killed";
|
|
4509
|
+
s.endedAt = Date.now();
|
|
4510
|
+
try {
|
|
4511
|
+
s.proc.kill();
|
|
4512
|
+
} catch {
|
|
4513
|
+
}
|
|
4514
|
+
return "killed";
|
|
4515
|
+
}
|
|
4516
|
+
/** Kill every running shell (used on CLI shutdown). */
|
|
4517
|
+
killAll() {
|
|
4518
|
+
for (const s of this.shells.values()) {
|
|
4519
|
+
if (s.status === "running") {
|
|
4520
|
+
s.status = "killed";
|
|
4521
|
+
s.endedAt = Date.now();
|
|
4522
|
+
try {
|
|
4523
|
+
s.proc.kill();
|
|
4524
|
+
} catch {
|
|
4525
|
+
}
|
|
4526
|
+
}
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
4529
|
+
/** Test-only: drop all tracked shells (does not kill — caller's responsibility). */
|
|
4530
|
+
_reset() {
|
|
4531
|
+
this.shells.clear();
|
|
4532
|
+
this.counter = 0;
|
|
4533
|
+
}
|
|
4534
|
+
toInfo(s) {
|
|
4535
|
+
return {
|
|
4536
|
+
id: s.id,
|
|
4537
|
+
command: s.command,
|
|
4538
|
+
cwd: s.cwd,
|
|
4539
|
+
status: s.status,
|
|
4540
|
+
pid: s.pid,
|
|
4541
|
+
exitCode: s.exitCode,
|
|
4542
|
+
startedAt: s.startedAt,
|
|
4543
|
+
endedAt: s.endedAt
|
|
4544
|
+
};
|
|
4545
|
+
}
|
|
4546
|
+
};
|
|
4547
|
+
BackgroundShellRegistry = new BackgroundShellRegistryImpl();
|
|
4548
|
+
BashBackgroundTool = class extends BaseTool {
|
|
4549
|
+
name = "bash_background";
|
|
4550
|
+
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.";
|
|
4551
|
+
parameters = {
|
|
4552
|
+
type: "object",
|
|
4553
|
+
required: ["command"],
|
|
4554
|
+
properties: {
|
|
4555
|
+
command: {
|
|
4556
|
+
type: "string",
|
|
4557
|
+
description: "The shell command to run in the background. May contain pipes, redirects, and other shell operators."
|
|
4558
|
+
},
|
|
4559
|
+
cwd: {
|
|
4560
|
+
type: "string",
|
|
4561
|
+
description: "Optional working directory (relative to project root). Defaults to the project root."
|
|
4562
|
+
}
|
|
4563
|
+
}
|
|
4564
|
+
};
|
|
4565
|
+
async execute(args2, projectRoot) {
|
|
4566
|
+
if (!args2?.command || typeof args2.command !== "string" || args2.command.trim() === "") {
|
|
4567
|
+
return {
|
|
4568
|
+
content: 'Error: bash_background requires a non-empty "command" argument.',
|
|
4569
|
+
isError: true
|
|
4570
|
+
};
|
|
4571
|
+
}
|
|
4572
|
+
const command = args2.command;
|
|
4573
|
+
const block = BashTool.checkBlocklist(command);
|
|
4574
|
+
if (block.blocked) {
|
|
4575
|
+
return {
|
|
4576
|
+
content: `Security Block: command rejected \u2014 ${block.reason}.
|
|
4577
|
+
Command: ${command}`,
|
|
4578
|
+
isError: true
|
|
4579
|
+
};
|
|
4580
|
+
}
|
|
4581
|
+
const cwdResult = resolveCwd(projectRoot, args2.cwd);
|
|
4582
|
+
if ("error" in cwdResult) {
|
|
4583
|
+
return { content: cwdResult.error, isError: true };
|
|
4584
|
+
}
|
|
4585
|
+
const cwd = cwdResult.cwd;
|
|
4586
|
+
const isWindows = process.platform === "win32";
|
|
4587
|
+
const shellArgv = isWindows ? ["cmd", "/c", command] : ["bash", "-c", command];
|
|
4588
|
+
let proc;
|
|
4589
|
+
try {
|
|
4590
|
+
proc = spawn6(shellArgv[0], shellArgv.slice(1), {
|
|
4591
|
+
cwd,
|
|
4592
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
4593
|
+
shell: false
|
|
4594
|
+
});
|
|
4595
|
+
} catch (e) {
|
|
4596
|
+
return { content: `Error: failed to spawn background shell: ${e.message}`, isError: true };
|
|
4597
|
+
}
|
|
4598
|
+
const id = BackgroundShellRegistry.register(proc, command, cwd);
|
|
4599
|
+
return {
|
|
4600
|
+
content: `Started background shell ${id} (pid ${proc.pid ?? "?"}).
|
|
4601
|
+
Command: ${command}
|
|
4602
|
+
Use read_background_shell with shell_id="${id}" to read its output, or kill_background_shell to stop it.`
|
|
4603
|
+
};
|
|
4604
|
+
}
|
|
4605
|
+
};
|
|
4606
|
+
ListBackgroundShellsTool = class extends BaseTool {
|
|
4607
|
+
name = "list_background_shells";
|
|
4608
|
+
description = "List all background shells started in this session (running and finished), with their id, status, pid, exit code, and command.";
|
|
4609
|
+
parameters = {
|
|
4610
|
+
type: "object",
|
|
4611
|
+
properties: {}
|
|
4612
|
+
};
|
|
4613
|
+
async execute() {
|
|
4614
|
+
return { content: formatBackgroundShells(BackgroundShellRegistry.list()) };
|
|
4615
|
+
}
|
|
4616
|
+
};
|
|
4617
|
+
ReadBackgroundShellTool = class extends BaseTool {
|
|
4618
|
+
name = "read_background_shell";
|
|
4619
|
+
description = "Read the buffered output of a background shell by its id. Optionally tail only the last N lines.";
|
|
4620
|
+
parameters = {
|
|
4621
|
+
type: "object",
|
|
4622
|
+
required: ["shell_id"],
|
|
4623
|
+
properties: {
|
|
4624
|
+
shell_id: {
|
|
4625
|
+
type: "string",
|
|
4626
|
+
description: 'The background shell id (e.g. "bg-1") returned by bash_background.'
|
|
4627
|
+
},
|
|
4628
|
+
lines: {
|
|
4629
|
+
type: "number",
|
|
4630
|
+
description: "Optional: return only the last N lines of buffered output."
|
|
4631
|
+
}
|
|
4632
|
+
}
|
|
4633
|
+
};
|
|
4634
|
+
async execute(args2) {
|
|
4635
|
+
const id = typeof args2?.shell_id === "string" ? args2.shell_id.trim() : "";
|
|
4636
|
+
if (!id) {
|
|
4637
|
+
return { content: 'Error: read_background_shell requires a "shell_id" argument.', isError: true };
|
|
4638
|
+
}
|
|
4639
|
+
const lines = typeof args2?.lines === "number" && args2.lines > 0 ? Math.floor(args2.lines) : void 0;
|
|
4640
|
+
const read = BackgroundShellRegistry.readOutput(id, lines);
|
|
4641
|
+
if (!read) {
|
|
4642
|
+
return { content: `Error: no background shell with id "${id}".`, isError: true };
|
|
4643
|
+
}
|
|
4644
|
+
const info = BackgroundShellRegistry.get(id);
|
|
4645
|
+
const header = `[${info.id}] ${info.status.toUpperCase()}` + (info.exitCode !== void 0 && info.exitCode !== null ? ` exit=${info.exitCode}` : "") + ` $ ${info.command}
|
|
4646
|
+
`;
|
|
4647
|
+
const body = read.output.trim() === "" ? "(no output yet)" : read.output;
|
|
4648
|
+
const truncNote = read.truncated ? "\n... [output buffer truncated to most recent 256 KB]" : "";
|
|
4649
|
+
return { content: header + body + truncNote };
|
|
4650
|
+
}
|
|
4651
|
+
};
|
|
4652
|
+
KillBackgroundShellTool = class extends BaseTool {
|
|
4653
|
+
name = "kill_background_shell";
|
|
4654
|
+
description = "Terminate a running background shell by its id.";
|
|
4655
|
+
parameters = {
|
|
4656
|
+
type: "object",
|
|
4657
|
+
required: ["shell_id"],
|
|
4658
|
+
properties: {
|
|
4659
|
+
shell_id: {
|
|
4660
|
+
type: "string",
|
|
4661
|
+
description: 'The background shell id (e.g. "bg-1") to terminate.'
|
|
4662
|
+
}
|
|
4663
|
+
}
|
|
4664
|
+
};
|
|
4665
|
+
async execute(args2) {
|
|
4666
|
+
const id = typeof args2?.shell_id === "string" ? args2.shell_id.trim() : "";
|
|
4667
|
+
if (!id) {
|
|
4668
|
+
return { content: 'Error: kill_background_shell requires a "shell_id" argument.', isError: true };
|
|
4669
|
+
}
|
|
4670
|
+
const result = BackgroundShellRegistry.kill(id);
|
|
4671
|
+
switch (result) {
|
|
4672
|
+
case "killed":
|
|
4673
|
+
return { content: `Killed background shell ${id}.` };
|
|
4674
|
+
case "already":
|
|
4675
|
+
return { content: `Background shell ${id} had already finished.` };
|
|
4676
|
+
case "not-found":
|
|
4677
|
+
default:
|
|
4678
|
+
return { content: `Error: no background shell with id "${id}".`, isError: true };
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4681
|
+
};
|
|
4682
|
+
}
|
|
4683
|
+
});
|
|
4684
|
+
|
|
4093
4685
|
// ../core/src/tools/NotebookReadTool.ts
|
|
4094
|
-
import { resolve as
|
|
4686
|
+
import { resolve as resolve9, normalize as normalize8, relative as relative9, isAbsolute as isAbsolute9, extname } from "path";
|
|
4095
4687
|
import { readFile as readFile6 } from "fs/promises";
|
|
4096
4688
|
import { existsSync as existsSync7 } from "fs";
|
|
4097
4689
|
function joinSource(source) {
|
|
@@ -4246,9 +4838,9 @@ var init_NotebookReadTool = __esm({
|
|
|
4246
4838
|
isError: true
|
|
4247
4839
|
};
|
|
4248
4840
|
}
|
|
4249
|
-
const absPath =
|
|
4250
|
-
const rel =
|
|
4251
|
-
if (rel.startsWith("..") ||
|
|
4841
|
+
const absPath = isAbsolute9(rawPath) ? normalize8(rawPath) : resolve9(projectRoot, rawPath);
|
|
4842
|
+
const rel = relative9(projectRoot, absPath);
|
|
4843
|
+
if (rel.startsWith("..") || isAbsolute9(rel)) {
|
|
4252
4844
|
return {
|
|
4253
4845
|
content: `Security Block: path "${rawPath}" resolves outside the project root.`,
|
|
4254
4846
|
isError: true
|
|
@@ -4316,10 +4908,10 @@ _(Note: notebook has ${nb.cells.length} cells; only first ${MAX_CELLS} shown.)_`
|
|
|
4316
4908
|
});
|
|
4317
4909
|
|
|
4318
4910
|
// ../core/src/tools/NotebookEditTool.ts
|
|
4319
|
-
import { resolve as
|
|
4911
|
+
import { resolve as resolve10, normalize as normalize9, relative as relative10, isAbsolute as isAbsolute10, extname as extname2, join as join11 } from "path";
|
|
4320
4912
|
import { readFile as readFile7, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
4321
4913
|
import { existsSync as existsSync8 } from "fs";
|
|
4322
|
-
import { homedir as
|
|
4914
|
+
import { homedir as homedir6 } from "os";
|
|
4323
4915
|
import { randomBytes as randomBytes5 } from "crypto";
|
|
4324
4916
|
function normaliseSource(source) {
|
|
4325
4917
|
if (source === "") return [];
|
|
@@ -4440,9 +5032,9 @@ var init_NotebookEditTool = __esm({
|
|
|
4440
5032
|
isError: true
|
|
4441
5033
|
};
|
|
4442
5034
|
}
|
|
4443
|
-
const absPath =
|
|
4444
|
-
const rel =
|
|
4445
|
-
if (rel.startsWith("..") ||
|
|
5035
|
+
const absPath = isAbsolute10(rawPath) ? normalize9(rawPath) : resolve10(projectRoot, rawPath);
|
|
5036
|
+
const rel = relative10(projectRoot, absPath);
|
|
5037
|
+
if (rel.startsWith("..") || isAbsolute10(rel)) {
|
|
4446
5038
|
return {
|
|
4447
5039
|
content: `Security Block: path "${rawPath}" resolves outside the project root.`,
|
|
4448
5040
|
isError: true
|
|
@@ -4499,13 +5091,13 @@ var init_NotebookEditTool = __esm({
|
|
|
4499
5091
|
const filename = absPath.split(/[\\/]/).pop() ?? "notebook.ipynb";
|
|
4500
5092
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4501
5093
|
const suffix = randomBytes5(4).toString("hex");
|
|
4502
|
-
const backupPath =
|
|
4503
|
-
|
|
5094
|
+
const backupPath = join11(
|
|
5095
|
+
homedir6(),
|
|
4504
5096
|
".msapling",
|
|
4505
5097
|
"backups",
|
|
4506
5098
|
`${filename}.backup-${stamp}-${suffix}.bak`
|
|
4507
5099
|
);
|
|
4508
|
-
await mkdir3(
|
|
5100
|
+
await mkdir3(join11(homedir6(), ".msapling", "backups"), { recursive: true });
|
|
4509
5101
|
await writeFile3(backupPath, rawJson, "utf8");
|
|
4510
5102
|
backedUpTo = backupPath;
|
|
4511
5103
|
} catch {
|
|
@@ -4575,10 +5167,10 @@ Backup: ${backedUpTo}`;
|
|
|
4575
5167
|
});
|
|
4576
5168
|
|
|
4577
5169
|
// ../core/src/tools/MultiEditFileTool.ts
|
|
4578
|
-
import { resolve as
|
|
5170
|
+
import { resolve as resolve11, normalize as normalize10, relative as relative11, isAbsolute as isAbsolute11, join as join12 } from "path";
|
|
4579
5171
|
import { readFile as readFile8, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
|
|
4580
5172
|
import { existsSync as existsSync9 } from "fs";
|
|
4581
|
-
import { homedir as
|
|
5173
|
+
import { homedir as homedir7 } from "os";
|
|
4582
5174
|
import { randomBytes as randomBytes6 } from "crypto";
|
|
4583
5175
|
var MAX_EDITS, MultiEditFileTool;
|
|
4584
5176
|
var init_MultiEditFileTool = __esm({
|
|
@@ -4672,9 +5264,9 @@ var init_MultiEditFileTool = __esm({
|
|
|
4672
5264
|
};
|
|
4673
5265
|
}
|
|
4674
5266
|
}
|
|
4675
|
-
const normalizedTarget =
|
|
4676
|
-
const rel =
|
|
4677
|
-
if (rel.startsWith("..") ||
|
|
5267
|
+
const normalizedTarget = isAbsolute11(args2.path) ? normalize10(args2.path) : resolve11(projectRoot, args2.path.trim());
|
|
5268
|
+
const rel = relative11(projectRoot, normalizedTarget);
|
|
5269
|
+
if (rel.startsWith("..") || isAbsolute11(rel)) {
|
|
4678
5270
|
return {
|
|
4679
5271
|
content: `Security Block: path "${args2.path}" resolves outside the project root.`,
|
|
4680
5272
|
isError: true
|
|
@@ -4742,13 +5334,13 @@ No changes were written (atomic: all-or-nothing).`,
|
|
|
4742
5334
|
const filename = resolvedTarget.split(/[\\/]/).pop() ?? "file";
|
|
4743
5335
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4744
5336
|
const suffix = randomBytes6(4).toString("hex");
|
|
4745
|
-
const backupPath =
|
|
4746
|
-
|
|
5337
|
+
const backupPath = join12(
|
|
5338
|
+
homedir7(),
|
|
4747
5339
|
".msapling",
|
|
4748
5340
|
"backups",
|
|
4749
5341
|
`${filename}.backup-${stamp}-${suffix}.bak`
|
|
4750
5342
|
);
|
|
4751
|
-
await mkdir4(
|
|
5343
|
+
await mkdir4(join12(homedir7(), ".msapling", "backups"), { recursive: true });
|
|
4752
5344
|
await writeFile4(backupPath, originalContent, "utf8");
|
|
4753
5345
|
backedUpTo = backupPath;
|
|
4754
5346
|
} catch {
|
|
@@ -4776,14 +5368,14 @@ Pre-edit content backed up to: ${backedUpTo}`;
|
|
|
4776
5368
|
});
|
|
4777
5369
|
|
|
4778
5370
|
// ../core/src/tools/MoveFileTool.ts
|
|
4779
|
-
import { resolve as
|
|
5371
|
+
import { resolve as resolve12, normalize as normalize11, relative as relative12, isAbsolute as isAbsolute12, dirname as dirname2 } from "path";
|
|
4780
5372
|
import { rename, mkdir as mkdir5, copyFile, rm, stat as stat2, readdir as readdir2 } from "fs/promises";
|
|
4781
|
-
import { existsSync as existsSync10, statSync as
|
|
5373
|
+
import { existsSync as existsSync10, statSync as statSync4 } from "fs";
|
|
4782
5374
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
4783
5375
|
function containedPath(p, root) {
|
|
4784
|
-
const abs =
|
|
4785
|
-
const rel =
|
|
4786
|
-
if (rel.startsWith("..") ||
|
|
5376
|
+
const abs = isAbsolute12(p) ? normalize11(p) : resolve12(root, p.trim());
|
|
5377
|
+
const rel = relative12(root, abs);
|
|
5378
|
+
if (rel.startsWith("..") || isAbsolute12(rel)) {
|
|
4787
5379
|
return { ok: false, reason: `path "${p}" resolves outside the project root` };
|
|
4788
5380
|
}
|
|
4789
5381
|
return { ok: true, abs };
|
|
@@ -4792,8 +5384,8 @@ async function copyDir(src, dst) {
|
|
|
4792
5384
|
await mkdir5(dst, { recursive: true });
|
|
4793
5385
|
const entries = await readdir2(src, { withFileTypes: true });
|
|
4794
5386
|
for (const entry of entries) {
|
|
4795
|
-
const srcPath =
|
|
4796
|
-
const dstPath =
|
|
5387
|
+
const srcPath = resolve12(src, entry.name);
|
|
5388
|
+
const dstPath = resolve12(dst, entry.name);
|
|
4797
5389
|
if (entry.isDirectory()) {
|
|
4798
5390
|
await copyDir(srcPath, dstPath);
|
|
4799
5391
|
} else {
|
|
@@ -4804,18 +5396,18 @@ async function copyDir(src, dst) {
|
|
|
4804
5396
|
async function backupFile(absPath) {
|
|
4805
5397
|
try {
|
|
4806
5398
|
const { readFile: readFile30, writeFile: writeFile19, mkdir: mkdir10 } = await import("fs/promises");
|
|
4807
|
-
const { homedir:
|
|
4808
|
-
const { join:
|
|
5399
|
+
const { homedir: homedir25 } = await import("os");
|
|
5400
|
+
const { join: join41 } = await import("path");
|
|
4809
5401
|
const filename = absPath.split(/[\\/]/).pop() ?? "file";
|
|
4810
5402
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4811
5403
|
const suffix = randomBytes7(4).toString("hex");
|
|
4812
|
-
const backupPath =
|
|
4813
|
-
|
|
5404
|
+
const backupPath = join41(
|
|
5405
|
+
homedir25(),
|
|
4814
5406
|
".msapling",
|
|
4815
5407
|
"backups",
|
|
4816
5408
|
`${filename}.backup-${stamp}-${suffix}.bak`
|
|
4817
5409
|
);
|
|
4818
|
-
await mkdir10(
|
|
5410
|
+
await mkdir10(join41(homedir25(), ".msapling", "backups"), { recursive: true });
|
|
4819
5411
|
const content = await readFile30(absPath, "utf8");
|
|
4820
5412
|
await writeFile19(backupPath, content, "utf8");
|
|
4821
5413
|
return backupPath;
|
|
@@ -4875,7 +5467,7 @@ var init_MoveFileTool = __esm({
|
|
|
4875
5467
|
isError: true
|
|
4876
5468
|
};
|
|
4877
5469
|
}
|
|
4878
|
-
const srcStat =
|
|
5470
|
+
const srcStat = statSync4(absSrc);
|
|
4879
5471
|
const srcIsDir = srcStat.isDirectory();
|
|
4880
5472
|
let rawDst = args2.destination.trim();
|
|
4881
5473
|
const dstPrelimCheck = containedPath(rawDst, projectRoot);
|
|
@@ -4883,11 +5475,11 @@ var init_MoveFileTool = __esm({
|
|
|
4883
5475
|
return { content: `Security Block: ${dstPrelimCheck.reason}.`, isError: true };
|
|
4884
5476
|
}
|
|
4885
5477
|
let absDst = dstPrelimCheck.abs;
|
|
4886
|
-
if (existsSync10(absDst) &&
|
|
5478
|
+
if (existsSync10(absDst) && statSync4(absDst).isDirectory() && !srcIsDir) {
|
|
4887
5479
|
const srcName = absSrc.split(/[\\/]/).pop();
|
|
4888
|
-
absDst =
|
|
4889
|
-
const rel2 =
|
|
4890
|
-
if (rel2.startsWith("..") ||
|
|
5480
|
+
absDst = resolve12(absDst, srcName);
|
|
5481
|
+
const rel2 = relative12(projectRoot, absDst);
|
|
5482
|
+
if (rel2.startsWith("..") || isAbsolute12(rel2)) {
|
|
4891
5483
|
return {
|
|
4892
5484
|
content: `Security Block: adjusted destination "${absDst}" resolves outside the project root.`,
|
|
4893
5485
|
isError: true
|
|
@@ -4908,7 +5500,7 @@ var init_MoveFileTool = __esm({
|
|
|
4908
5500
|
isError: true
|
|
4909
5501
|
};
|
|
4910
5502
|
}
|
|
4911
|
-
if (!
|
|
5503
|
+
if (!statSync4(absDst).isDirectory()) {
|
|
4912
5504
|
backedUpTo = await backupFile(absDst);
|
|
4913
5505
|
}
|
|
4914
5506
|
}
|
|
@@ -4967,7 +5559,7 @@ Overwritten destination backed up to: ${backedUpTo}`;
|
|
|
4967
5559
|
});
|
|
4968
5560
|
|
|
4969
5561
|
// ../core/src/tools/DeleteFileTool.ts
|
|
4970
|
-
import { resolve as
|
|
5562
|
+
import { resolve as resolve13, normalize as normalize12, relative as relative13, isAbsolute as isAbsolute13, join as join14 } from "path";
|
|
4971
5563
|
import { rm as rm2, stat as stat3 } from "fs/promises";
|
|
4972
5564
|
import { randomBytes as randomBytes8 } from "crypto";
|
|
4973
5565
|
var DeleteFileTool;
|
|
@@ -5002,15 +5594,15 @@ var init_DeleteFileTool = __esm({
|
|
|
5002
5594
|
}
|
|
5003
5595
|
const rawPath = args2.path.trim();
|
|
5004
5596
|
const recursive = args2.recursive === true;
|
|
5005
|
-
const abs =
|
|
5006
|
-
const rel =
|
|
5007
|
-
if (rel.startsWith("..") ||
|
|
5597
|
+
const abs = isAbsolute13(rawPath) ? normalize12(rawPath) : resolve13(projectRoot, rawPath);
|
|
5598
|
+
const rel = relative13(projectRoot, abs);
|
|
5599
|
+
if (rel.startsWith("..") || isAbsolute13(rel)) {
|
|
5008
5600
|
return {
|
|
5009
5601
|
content: `Security Block: path "${rawPath}" resolves outside the project root.`,
|
|
5010
5602
|
isError: true
|
|
5011
5603
|
};
|
|
5012
5604
|
}
|
|
5013
|
-
if (rel === "" || abs ===
|
|
5605
|
+
if (rel === "" || abs === normalize12(projectRoot)) {
|
|
5014
5606
|
return {
|
|
5015
5607
|
content: "Security Block: refusing to delete the project root directory.",
|
|
5016
5608
|
isError: true
|
|
@@ -5043,13 +5635,13 @@ var init_DeleteFileTool = __esm({
|
|
|
5043
5635
|
if (isFile) {
|
|
5044
5636
|
try {
|
|
5045
5637
|
const { readFile: readFile30, writeFile: writeFile19, mkdir: mkdir10 } = await import("fs/promises");
|
|
5046
|
-
const { homedir:
|
|
5638
|
+
const { homedir: homedir25 } = await import("os");
|
|
5047
5639
|
const existingContent = await readFile30(abs, "utf8");
|
|
5048
5640
|
const filename = abs.split(/[\\/]/).pop() ?? "file";
|
|
5049
5641
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
5050
5642
|
const suffix = randomBytes8(4).toString("hex");
|
|
5051
|
-
const backupPath =
|
|
5052
|
-
await mkdir10(
|
|
5643
|
+
const backupPath = join14(homedir25(), ".msapling", "backups", `${filename}.backup-${stamp}-${suffix}.bak`);
|
|
5644
|
+
await mkdir10(join14(homedir25(), ".msapling", "backups"), { recursive: true });
|
|
5053
5645
|
await writeFile19(backupPath, existingContent, "utf8");
|
|
5054
5646
|
backedUpTo = backupPath;
|
|
5055
5647
|
} catch {
|
|
@@ -5191,7 +5783,7 @@ var init_MDrive = __esm({
|
|
|
5191
5783
|
});
|
|
5192
5784
|
|
|
5193
5785
|
// ../core/src/Sandbox.ts
|
|
5194
|
-
import { resolve as
|
|
5786
|
+
import { resolve as resolve14, normalize as normalize13, relative as relative14, isAbsolute as isAbsolute14 } from "path";
|
|
5195
5787
|
import { realpathSync } from "fs";
|
|
5196
5788
|
import { realpath as realpath2 } from "fs/promises";
|
|
5197
5789
|
import { createHash as createHash3 } from "crypto";
|
|
@@ -5250,7 +5842,7 @@ var init_Sandbox = __esm({
|
|
|
5250
5842
|
"sc"
|
|
5251
5843
|
]);
|
|
5252
5844
|
constructor(projectRoot, opts) {
|
|
5253
|
-
this.projectRoot =
|
|
5845
|
+
this.projectRoot = resolve14(projectRoot);
|
|
5254
5846
|
this.realpathSyncFn = opts?.realpathSync ?? realpathSync;
|
|
5255
5847
|
}
|
|
5256
5848
|
setPermissions(state) {
|
|
@@ -5265,15 +5857,15 @@ var init_Sandbox = __esm({
|
|
|
5265
5857
|
return hasher.digest("hex");
|
|
5266
5858
|
}
|
|
5267
5859
|
isPathSafe(targetPath) {
|
|
5268
|
-
const normalizedTarget =
|
|
5860
|
+
const normalizedTarget = isAbsolute14(targetPath) ? normalize13(targetPath) : resolve14(this.projectRoot, targetPath);
|
|
5269
5861
|
let resolvedTarget = normalizedTarget;
|
|
5270
5862
|
try {
|
|
5271
5863
|
resolvedTarget = this.realpathSyncFn(normalizedTarget);
|
|
5272
5864
|
} catch {
|
|
5273
5865
|
resolvedTarget = normalizedTarget;
|
|
5274
5866
|
}
|
|
5275
|
-
const rel =
|
|
5276
|
-
const isOutside = rel.startsWith("..") ||
|
|
5867
|
+
const rel = relative14(this.projectRoot, resolvedTarget);
|
|
5868
|
+
const isOutside = rel.startsWith("..") || isAbsolute14(rel);
|
|
5277
5869
|
if (isOutside) {
|
|
5278
5870
|
if (this.permissions.trustedPaths.includes(resolvedTarget) || this.permissions.trustedPaths.includes(normalizedTarget)) {
|
|
5279
5871
|
return { safe: true, absolutePath: resolvedTarget };
|
|
@@ -5291,15 +5883,15 @@ var init_Sandbox = __esm({
|
|
|
5291
5883
|
* isPathSafe (sync) is kept for cold/boot-time paths and existing tests.
|
|
5292
5884
|
*/
|
|
5293
5885
|
async isPathSafeAsync(targetPath) {
|
|
5294
|
-
const normalizedTarget =
|
|
5886
|
+
const normalizedTarget = isAbsolute14(targetPath) ? normalize13(targetPath) : resolve14(this.projectRoot, targetPath);
|
|
5295
5887
|
let resolvedTarget = normalizedTarget;
|
|
5296
5888
|
try {
|
|
5297
5889
|
resolvedTarget = await realpath2(normalizedTarget);
|
|
5298
5890
|
} catch {
|
|
5299
5891
|
resolvedTarget = normalizedTarget;
|
|
5300
5892
|
}
|
|
5301
|
-
const rel =
|
|
5302
|
-
const isOutside = rel.startsWith("..") ||
|
|
5893
|
+
const rel = relative14(this.projectRoot, resolvedTarget);
|
|
5894
|
+
const isOutside = rel.startsWith("..") || isAbsolute14(rel);
|
|
5303
5895
|
if (isOutside) {
|
|
5304
5896
|
if (this.permissions.trustedPaths.includes(resolvedTarget) || this.permissions.trustedPaths.includes(normalizedTarget)) {
|
|
5305
5897
|
return { safe: true, absolutePath: resolvedTarget };
|
|
@@ -5331,7 +5923,7 @@ var init_Sandbox = __esm({
|
|
|
5331
5923
|
}
|
|
5332
5924
|
const parsed = parse(commandLine);
|
|
5333
5925
|
const tokens = parsed.filter((t) => typeof t === "string");
|
|
5334
|
-
const
|
|
5926
|
+
const normalize14 = (raw) => {
|
|
5335
5927
|
let b = raw.toLowerCase();
|
|
5336
5928
|
const slash = Math.max(b.lastIndexOf("/"), b.lastIndexOf("\\"));
|
|
5337
5929
|
if (slash >= 0) b = b.slice(slash + 1);
|
|
@@ -5339,7 +5931,7 @@ var init_Sandbox = __esm({
|
|
|
5339
5931
|
return b;
|
|
5340
5932
|
};
|
|
5341
5933
|
for (const tok of tokens) {
|
|
5342
|
-
const b =
|
|
5934
|
+
const b = normalize14(tok);
|
|
5343
5935
|
if (_Sandbox.DANGEROUS_BINARIES.has(b)) {
|
|
5344
5936
|
return { status: "blocked", reason: `Binary '${b}' is explicitly forbidden.`, hash };
|
|
5345
5937
|
}
|
|
@@ -5347,7 +5939,7 @@ var init_Sandbox = __esm({
|
|
|
5347
5939
|
if (tokens.length === 0) {
|
|
5348
5940
|
return { status: "safe", hash };
|
|
5349
5941
|
}
|
|
5350
|
-
const binary =
|
|
5942
|
+
const binary = normalize14(tokens[0]);
|
|
5351
5943
|
const sub = tokens.length > 1 ? tokens[1].toLowerCase() : "";
|
|
5352
5944
|
if (binary === "git") {
|
|
5353
5945
|
const destructiveGit = /* @__PURE__ */ new Set(["push", "reset", "clean", "rebase", "merge", "force-push"]);
|
|
@@ -5446,7 +6038,7 @@ var init_Sandbox = __esm({
|
|
|
5446
6038
|
});
|
|
5447
6039
|
|
|
5448
6040
|
// ../core/src/Voice.ts
|
|
5449
|
-
import { spawn as
|
|
6041
|
+
import { spawn as spawn7 } from "child_process";
|
|
5450
6042
|
function buildTtsInvocation(text, rate) {
|
|
5451
6043
|
const psCommand = `
|
|
5452
6044
|
Add-Type -AssemblyName System.Speech;
|
|
@@ -5486,10 +6078,10 @@ var init_Voice = __esm({
|
|
|
5486
6078
|
const { command, args: args2, env } = buildTtsInvocation(text, rate);
|
|
5487
6079
|
try {
|
|
5488
6080
|
if (process.platform === "win32") {
|
|
5489
|
-
await new Promise((
|
|
6081
|
+
await new Promise((resolve21, reject) => {
|
|
5490
6082
|
try {
|
|
5491
|
-
const proc =
|
|
5492
|
-
proc.on("exit", () =>
|
|
6083
|
+
const proc = spawn7(command, args2, { env });
|
|
6084
|
+
proc.on("exit", () => resolve21());
|
|
5493
6085
|
proc.on("error", reject);
|
|
5494
6086
|
} catch (e) {
|
|
5495
6087
|
reject(e);
|
|
@@ -5586,7 +6178,7 @@ var init_ShadowService = __esm({
|
|
|
5586
6178
|
});
|
|
5587
6179
|
|
|
5588
6180
|
// ../core/src/Hooks.ts
|
|
5589
|
-
import {
|
|
6181
|
+
import { exec } from "child_process";
|
|
5590
6182
|
function matches(entry, ctx) {
|
|
5591
6183
|
if (!entry.matcher) return true;
|
|
5592
6184
|
let re;
|
|
@@ -5603,55 +6195,34 @@ function matches(entry, ctx) {
|
|
|
5603
6195
|
async function runOne(entry, ctx) {
|
|
5604
6196
|
const timeoutMs = entry.timeoutMs ?? DEFAULT_TIMEOUT_MS3;
|
|
5605
6197
|
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 = "";
|
|
6198
|
+
const env = { ...process.env, MSAPLING_HOOK_PAYLOAD: JSON.stringify({ ...ctx }) };
|
|
6199
|
+
return new Promise((resolve21) => {
|
|
5614
6200
|
let timedOut = false;
|
|
5615
6201
|
let settled = false;
|
|
5616
|
-
const finish = (exitCode) => {
|
|
6202
|
+
const finish = (exitCode, stdout, stderr) => {
|
|
5617
6203
|
if (settled) return;
|
|
5618
6204
|
settled = true;
|
|
5619
6205
|
clearTimeout(killer);
|
|
5620
6206
|
const blocked = !NON_BLOCKING_EVENTS.has(ctx.event) && !!entry.blocking && (exitCode === null || exitCode !== 0);
|
|
5621
|
-
|
|
6207
|
+
resolve21({ command, exitCode, stdout, stderr, timedOut, blocked });
|
|
5622
6208
|
};
|
|
6209
|
+
const child = exec(command, {
|
|
6210
|
+
cwd: ctx.cwd ?? process.cwd(),
|
|
6211
|
+
env,
|
|
6212
|
+
maxBuffer: MAX_OUTPUT_BYTES3
|
|
6213
|
+
}, (err, stdout, stderr) => {
|
|
6214
|
+
const rawCode = err ? err.code : 0;
|
|
6215
|
+
const exitCode = typeof rawCode === "number" ? rawCode : null;
|
|
6216
|
+
finish(exitCode, stdout.slice(0, MAX_OUTPUT_BYTES3), stderr.slice(0, MAX_OUTPUT_BYTES3));
|
|
6217
|
+
});
|
|
5623
6218
|
const killer = setTimeout(() => {
|
|
5624
6219
|
timedOut = true;
|
|
5625
6220
|
try {
|
|
5626
6221
|
child.kill("SIGKILL");
|
|
5627
6222
|
} catch {
|
|
5628
6223
|
}
|
|
5629
|
-
finish(null);
|
|
6224
|
+
finish(null, "", "");
|
|
5630
6225
|
}, 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
6226
|
});
|
|
5656
6227
|
}
|
|
5657
6228
|
var NON_BLOCKING_EVENTS, DEFAULT_TIMEOUT_MS3, MAX_OUTPUT_BYTES3, HookRunner;
|
|
@@ -5869,9 +6440,9 @@ var init_specs = __esm({
|
|
|
5869
6440
|
|
|
5870
6441
|
// ../core/src/governor/ResourceGovernor.ts
|
|
5871
6442
|
import { freemem as freemem2 } from "os";
|
|
5872
|
-
import { homedir as
|
|
6443
|
+
import { homedir as homedir8 } from "os";
|
|
5873
6444
|
import { readFile as readFile9 } from "fs/promises";
|
|
5874
|
-
import { join as
|
|
6445
|
+
import { join as join15 } from "path";
|
|
5875
6446
|
function determineTier(specs) {
|
|
5876
6447
|
const memGB = specs.memory.totalGB;
|
|
5877
6448
|
const cores = specs.cpu.cores;
|
|
@@ -5886,7 +6457,7 @@ function recommendLimits(specs) {
|
|
|
5886
6457
|
}
|
|
5887
6458
|
async function readConfigOverrides() {
|
|
5888
6459
|
try {
|
|
5889
|
-
const configPath =
|
|
6460
|
+
const configPath = join15(homedir8(), ".msapling", "config.json");
|
|
5890
6461
|
const content = await readFile9(configPath, "utf-8");
|
|
5891
6462
|
const config = JSON.parse(content);
|
|
5892
6463
|
return config.limits ?? null;
|
|
@@ -5967,7 +6538,7 @@ var init_ResourceGovernor = __esm({
|
|
|
5967
6538
|
this.activeAgents++;
|
|
5968
6539
|
return;
|
|
5969
6540
|
}
|
|
5970
|
-
await new Promise((
|
|
6541
|
+
await new Promise((resolve21) => this.agentWaiters.push(resolve21));
|
|
5971
6542
|
this.activeAgents++;
|
|
5972
6543
|
}
|
|
5973
6544
|
/**
|
|
@@ -5986,7 +6557,7 @@ var init_ResourceGovernor = __esm({
|
|
|
5986
6557
|
this.activeTool++;
|
|
5987
6558
|
return;
|
|
5988
6559
|
}
|
|
5989
|
-
await new Promise((
|
|
6560
|
+
await new Promise((resolve21) => this.toolWaiters.push(resolve21));
|
|
5990
6561
|
this.activeTool++;
|
|
5991
6562
|
}
|
|
5992
6563
|
/**
|
|
@@ -6044,6 +6615,7 @@ var init_ToolExecutor = __esm({
|
|
|
6044
6615
|
init_WebFetchTool();
|
|
6045
6616
|
init_WebSearchTool();
|
|
6046
6617
|
init_BashTool();
|
|
6618
|
+
init_BackgroundShellTool();
|
|
6047
6619
|
init_NotebookReadTool();
|
|
6048
6620
|
init_NotebookEditTool();
|
|
6049
6621
|
init_MultiEditFileTool();
|
|
@@ -6056,9 +6628,14 @@ var init_ToolExecutor = __esm({
|
|
|
6056
6628
|
init_ShadowService();
|
|
6057
6629
|
init_Hooks();
|
|
6058
6630
|
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"]);
|
|
6631
|
+
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
6632
|
ToolExecutor = class {
|
|
6061
6633
|
tools = /* @__PURE__ */ new Map();
|
|
6634
|
+
/** Backend client — retained so named-subagent dispatch can open its own
|
|
6635
|
+
* scoped stream (CLI-PARITY-P1-6). */
|
|
6636
|
+
client;
|
|
6637
|
+
/** Project root — retained for named-subagent dispatch context. */
|
|
6638
|
+
projectRoot;
|
|
6062
6639
|
sandbox;
|
|
6063
6640
|
mdrive;
|
|
6064
6641
|
voice;
|
|
@@ -6093,6 +6670,8 @@ var init_ToolExecutor = __esm({
|
|
|
6093
6670
|
*/
|
|
6094
6671
|
onModeChange = null;
|
|
6095
6672
|
constructor(client, projectRoot) {
|
|
6673
|
+
this.client = client;
|
|
6674
|
+
this.projectRoot = projectRoot;
|
|
6096
6675
|
this.sandbox = new Sandbox(projectRoot);
|
|
6097
6676
|
this.mdrive = new MDriveService(client);
|
|
6098
6677
|
this.voice = new VoiceService();
|
|
@@ -6121,11 +6700,20 @@ var init_ToolExecutor = __esm({
|
|
|
6121
6700
|
payload: info,
|
|
6122
6701
|
cwd: projectRoot
|
|
6123
6702
|
});
|
|
6124
|
-
}
|
|
6703
|
+
},
|
|
6704
|
+
// CLI-PARITY-P1-6: named-subagent runner. Runs a named agent with its own
|
|
6705
|
+
// system prompt + model + TOOL ALLOWLIST. Tools execute back through THIS
|
|
6706
|
+
// executor (so the existing permission/approval system gates writes), but
|
|
6707
|
+
// only the agent's allowlisted tools are advertised + permitted.
|
|
6708
|
+
runNamedAgent: (req) => this.runNamedSubagent(req)
|
|
6125
6709
|
}));
|
|
6126
6710
|
this.registerTool(new WebFetchTool());
|
|
6127
6711
|
this.registerTool(new WebSearchTool({ client }));
|
|
6128
6712
|
this.registerTool(new BashTool());
|
|
6713
|
+
this.registerTool(new BashBackgroundTool());
|
|
6714
|
+
this.registerTool(new ListBackgroundShellsTool());
|
|
6715
|
+
this.registerTool(new ReadBackgroundShellTool());
|
|
6716
|
+
this.registerTool(new KillBackgroundShellTool());
|
|
6129
6717
|
this.registerTool(new NotebookReadTool());
|
|
6130
6718
|
this.registerTool(new NotebookEditTool());
|
|
6131
6719
|
this.registerTool(new MultiEditFileTool());
|
|
@@ -6317,13 +6905,13 @@ ${blocker.stderr || "(empty)"}`,
|
|
|
6317
6905
|
return { content: `Security Block: ${check2.reason}`, isError: true };
|
|
6318
6906
|
}
|
|
6319
6907
|
}
|
|
6320
|
-
if (toolName === "run_command" || toolName === "bash_command") {
|
|
6908
|
+
if (toolName === "run_command" || toolName === "bash_command" || toolName === "bash_background") {
|
|
6321
6909
|
if (!args2.command) {
|
|
6322
6910
|
return { content: `Error: ${toolName} requires a command argument`, isError: true };
|
|
6323
6911
|
}
|
|
6324
6912
|
const analysis = this.sandbox.analyzeCommand(args2.command);
|
|
6325
6913
|
if (analysis.status === "blocked") {
|
|
6326
|
-
const staleCmdKey = toolName === "
|
|
6914
|
+
const staleCmdKey = toolName === "run_command" ? `run_command:${(args2.command || "").trim()}` : `${toolName}:${(args2.command || "").trim().replace(/\s+/g, " ")}${args2.cwd ? `:cwd=${args2.cwd}` : ""}`;
|
|
6327
6915
|
if (this.trustStore?.has(staleCmdKey)) {
|
|
6328
6916
|
this.trustStore.delete(staleCmdKey).catch(() => {
|
|
6329
6917
|
});
|
|
@@ -6338,10 +6926,10 @@ ${blocker.stderr || "(empty)"}`,
|
|
|
6338
6926
|
let cmdKey = "";
|
|
6339
6927
|
if (toolName === "run_command") {
|
|
6340
6928
|
cmdKey = `run_command:${(args2.command || "").trim()}`;
|
|
6341
|
-
} else if (toolName === "bash_command") {
|
|
6929
|
+
} else if (toolName === "bash_command" || toolName === "bash_background") {
|
|
6342
6930
|
const normalizedCmd = (args2.command || "").trim().replace(/\s+/g, " ");
|
|
6343
6931
|
const cwdSuffix = args2.cwd ? `:cwd=${args2.cwd}` : "";
|
|
6344
|
-
cmdKey =
|
|
6932
|
+
cmdKey = `${toolName}:${normalizedCmd}${cwdSuffix}`;
|
|
6345
6933
|
} else {
|
|
6346
6934
|
cmdKey = `${toolName}:${(args2.path || args2.instruction || "").trim()}`;
|
|
6347
6935
|
}
|
|
@@ -6365,7 +6953,7 @@ ${blocker.stderr || "(empty)"}`,
|
|
|
6365
6953
|
}
|
|
6366
6954
|
}
|
|
6367
6955
|
}
|
|
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") {
|
|
6956
|
+
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
6957
|
const audit = await this.shadow.verifyAction(
|
|
6370
6958
|
JSON.stringify({ tool: toolName, args: args2 }),
|
|
6371
6959
|
`Execution context: ${projectRoot}`
|
|
@@ -6426,6 +7014,80 @@ Please approve the diff in the UI to sync this change locally.`
|
|
|
6426
7014
|
}));
|
|
6427
7015
|
return [...builtin, ...mcp];
|
|
6428
7016
|
}
|
|
7017
|
+
/**
|
|
7018
|
+
* CLI-PARITY-P1-6: tool schemas filtered to a named agent's allowlist.
|
|
7019
|
+
*
|
|
7020
|
+
* - `undefined` allowlist → inherit the FULL builtin+MCP set (getToolSchemas).
|
|
7021
|
+
* - `[]` → no tools (read/reason-only agent).
|
|
7022
|
+
* - `['read_file', …]` → ONLY those tools (silently drops names that
|
|
7023
|
+
* don't resolve to a registered tool).
|
|
7024
|
+
*
|
|
7025
|
+
* `dispatch_agent` is always excluded from a subagent's view so a named agent
|
|
7026
|
+
* cannot recursively spawn further named agents (avoids unbounded fan-out).
|
|
7027
|
+
*/
|
|
7028
|
+
getToolSchemasForAllowlist(allow) {
|
|
7029
|
+
const all = this.getToolSchemas();
|
|
7030
|
+
if (allow === void 0) {
|
|
7031
|
+
return all.filter((s) => s.name !== "dispatch_agent");
|
|
7032
|
+
}
|
|
7033
|
+
const allowSet = new Set(allow);
|
|
7034
|
+
return all.filter((s) => s.name !== "dispatch_agent" && allowSet.has(s.name));
|
|
7035
|
+
}
|
|
7036
|
+
/**
|
|
7037
|
+
* CLI-PARITY-P1-6: run a NAMED subagent to completion.
|
|
7038
|
+
*
|
|
7039
|
+
* Drives a bounded agentic loop against the backend using the agent's system
|
|
7040
|
+
* prompt + model, advertising ONLY the agent's allowlisted tools. Tool calls
|
|
7041
|
+
* execute back through `this.execute(...)`, so the SAME permission/approval +
|
|
7042
|
+
* shadow-audit gates apply — the allowlist narrows WHICH tools the agent may
|
|
7043
|
+
* request; the permission system still governs WHETHER a given write runs.
|
|
7044
|
+
*
|
|
7045
|
+
* This is the mechanism that closes the "subagents are read-only" gap: a named
|
|
7046
|
+
* agent whose frontmatter grants `write_file`/`edit_file`/`bash_command` can
|
|
7047
|
+
* use them (subject to approval), whereas the anonymous dispatch path cannot.
|
|
7048
|
+
*/
|
|
7049
|
+
async runNamedSubagent(req) {
|
|
7050
|
+
const { agent, prompt: prompt4, model, chatId, onContent } = req;
|
|
7051
|
+
const schemas = this.getToolSchemasForAllowlist(agent.tools);
|
|
7052
|
+
const framedPrompt = `[You are the "${agent.name}" subagent. Operate strictly within this role.]
|
|
7053
|
+
${agent.systemPrompt}
|
|
7054
|
+
|
|
7055
|
+
---
|
|
7056
|
+
|
|
7057
|
+
${prompt4}`;
|
|
7058
|
+
const MAX_SUBAGENT_ROUNDS = 12;
|
|
7059
|
+
const queue = [framedPrompt];
|
|
7060
|
+
let rounds = 0;
|
|
7061
|
+
while (queue.length > 0 && rounds < MAX_SUBAGENT_ROUNDS) {
|
|
7062
|
+
const current = queue.shift();
|
|
7063
|
+
rounds++;
|
|
7064
|
+
const stream = this.client.streamChat({
|
|
7065
|
+
...chatId ? { chat_id: chatId } : {},
|
|
7066
|
+
prompt: current,
|
|
7067
|
+
model,
|
|
7068
|
+
tools: schemas,
|
|
7069
|
+
project_root: this.projectRoot,
|
|
7070
|
+
mode: this.mode
|
|
7071
|
+
});
|
|
7072
|
+
for await (const chunk of stream) {
|
|
7073
|
+
if (chunk.content) onContent(chunk.content);
|
|
7074
|
+
if (chunk.tool_use) {
|
|
7075
|
+
const name = chunk.tool_use.name;
|
|
7076
|
+
if (agent.tools !== void 0 && !agent.tools.includes(name)) {
|
|
7077
|
+
queue.push(
|
|
7078
|
+
`[TOOL_RESULT]: ${JSON.stringify({
|
|
7079
|
+
content: `Tool "${name}" is not in the "${agent.name}" agent's allowlist; refused.`,
|
|
7080
|
+
isError: true
|
|
7081
|
+
})}`
|
|
7082
|
+
);
|
|
7083
|
+
continue;
|
|
7084
|
+
}
|
|
7085
|
+
const result = await this.execute(name, chunk.tool_use.args, this.projectRoot);
|
|
7086
|
+
queue.push(`[TOOL_RESULT]: ${JSON.stringify(result)}`);
|
|
7087
|
+
}
|
|
7088
|
+
}
|
|
7089
|
+
}
|
|
7090
|
+
}
|
|
6429
7091
|
};
|
|
6430
7092
|
}
|
|
6431
7093
|
});
|
|
@@ -6471,8 +7133,8 @@ var init_Safety = __esm({
|
|
|
6471
7133
|
});
|
|
6472
7134
|
|
|
6473
7135
|
// ../core/src/ProjectConfig.ts
|
|
6474
|
-
import { homedir as
|
|
6475
|
-
import { join as
|
|
7136
|
+
import { homedir as homedir9 } from "os";
|
|
7137
|
+
import { join as join16, dirname as dirname3, parse as parsePath } from "path";
|
|
6476
7138
|
import { existsSync as existsSync11 } from "fs";
|
|
6477
7139
|
import { readFile as readFile11 } from "fs/promises";
|
|
6478
7140
|
async function readIfExists(path2) {
|
|
@@ -6486,7 +7148,7 @@ async function readIfExists(path2) {
|
|
|
6486
7148
|
}
|
|
6487
7149
|
async function findInDir(dir) {
|
|
6488
7150
|
for (const filename of FILENAMES) {
|
|
6489
|
-
const path2 =
|
|
7151
|
+
const path2 = join16(dir, filename);
|
|
6490
7152
|
const content = await readIfExists(path2);
|
|
6491
7153
|
if (content !== null) {
|
|
6492
7154
|
return { path: path2, filename, content };
|
|
@@ -6509,9 +7171,9 @@ async function findProjectConfig(start) {
|
|
|
6509
7171
|
return null;
|
|
6510
7172
|
}
|
|
6511
7173
|
async function findUserConfig() {
|
|
6512
|
-
const home =
|
|
7174
|
+
const home = homedir9();
|
|
6513
7175
|
if (!home) return null;
|
|
6514
|
-
const userDir =
|
|
7176
|
+
const userDir = join16(home, ".msapling");
|
|
6515
7177
|
return findInDir(userDir);
|
|
6516
7178
|
}
|
|
6517
7179
|
function buildCombined(user, project) {
|
|
@@ -6786,8 +7448,15 @@ var init_Agent = __esm({
|
|
|
6786
7448
|
}
|
|
6787
7449
|
const config = await this.getProjectConfig();
|
|
6788
7450
|
const MAX_WORKER_TURN_DEPTH = 25;
|
|
7451
|
+
let structuredToolResults = false;
|
|
7452
|
+
try {
|
|
7453
|
+
structuredToolResults = await this.client.supportsStructuredToolResults();
|
|
7454
|
+
} catch {
|
|
7455
|
+
structuredToolResults = false;
|
|
7456
|
+
}
|
|
6789
7457
|
const queue = [prompt4];
|
|
6790
7458
|
let rounds = 0;
|
|
7459
|
+
let toolCallSeq = 0;
|
|
6791
7460
|
let streamUsage = null;
|
|
6792
7461
|
while (queue.length > 0 && rounds < MAX_WORKER_TURN_DEPTH) {
|
|
6793
7462
|
if (this.contextBudget.needsCompaction() && queue.length > 0) {
|
|
@@ -6831,14 +7500,25 @@ var init_Agent = __esm({
|
|
|
6831
7500
|
this.contextBudget.reset();
|
|
6832
7501
|
if (compactionSummary) {
|
|
6833
7502
|
const next = queue[0];
|
|
6834
|
-
|
|
7503
|
+
if (typeof next === "string") {
|
|
7504
|
+
queue[0] = `[CONTEXT_SUMMARY]: ${compactionSummary}
|
|
6835
7505
|
|
|
6836
7506
|
${next}`;
|
|
7507
|
+
} else {
|
|
7508
|
+
queue[0] = {
|
|
7509
|
+
...next,
|
|
7510
|
+
prompt: `[CONTEXT_SUMMARY]: ${compactionSummary}
|
|
7511
|
+
|
|
7512
|
+
${next.prompt}`
|
|
7513
|
+
};
|
|
7514
|
+
}
|
|
6837
7515
|
}
|
|
6838
7516
|
continue;
|
|
6839
7517
|
}
|
|
6840
|
-
const
|
|
7518
|
+
const currentItem = queue.shift();
|
|
6841
7519
|
rounds++;
|
|
7520
|
+
const currentPrompt = typeof currentItem === "string" ? currentItem : currentItem.prompt;
|
|
7521
|
+
const currentToolResults = typeof currentItem === "string" ? void 0 : currentItem.toolResults;
|
|
6842
7522
|
const stream = this.chatWithFallback(
|
|
6843
7523
|
{
|
|
6844
7524
|
chat_id: chatId,
|
|
@@ -6847,11 +7527,16 @@ ${next}`;
|
|
|
6847
7527
|
tools: this.executor.getToolSchemas(),
|
|
6848
7528
|
project_root: this.projectRoot,
|
|
6849
7529
|
mode: this.executor.getMode(),
|
|
7530
|
+
// CLI-LOOP-01: carry the concurrently-executed tool_results from the
|
|
7531
|
+
// previous turn as a structured batch. Only set in the capability-
|
|
7532
|
+
// enabled path; absent => backend sees the legacy single-prompt turn.
|
|
7533
|
+
...currentToolResults && currentToolResults.length > 0 ? { tool_results: currentToolResults } : {},
|
|
6850
7534
|
...config.combined ? { project_context: config.combined } : {}
|
|
6851
7535
|
},
|
|
6852
7536
|
chatId,
|
|
6853
7537
|
currentPrompt
|
|
6854
7538
|
);
|
|
7539
|
+
const pendingToolCalls = [];
|
|
6855
7540
|
for await (const chunk of stream) {
|
|
6856
7541
|
if (chunk.content) {
|
|
6857
7542
|
onContent(chunk.content);
|
|
@@ -6868,11 +7553,39 @@ ${next}`;
|
|
|
6868
7553
|
});
|
|
6869
7554
|
}
|
|
6870
7555
|
if (chunk.tool_use) {
|
|
6871
|
-
const
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
7556
|
+
const id = chunk.tool_use.id ?? `call_${rounds}_${toolCallSeq++}`;
|
|
7557
|
+
pendingToolCalls.push({ id, name: chunk.tool_use.name, args: chunk.tool_use.args });
|
|
7558
|
+
}
|
|
7559
|
+
}
|
|
7560
|
+
if (pendingToolCalls.length === 0) {
|
|
7561
|
+
continue;
|
|
7562
|
+
}
|
|
7563
|
+
if (structuredToolResults) {
|
|
7564
|
+
const toolResults = await Promise.all(
|
|
7565
|
+
pendingToolCalls.map(async (call) => {
|
|
7566
|
+
try {
|
|
7567
|
+
const result = await this.executor.execute(call.name, call.args, this.projectRoot);
|
|
7568
|
+
const redactedContent = SafetyGuard.redact(result.content);
|
|
7569
|
+
return {
|
|
7570
|
+
tool_use_id: call.id,
|
|
7571
|
+
name: call.name,
|
|
7572
|
+
content: redactedContent,
|
|
7573
|
+
is_error: !!result.isError
|
|
7574
|
+
};
|
|
7575
|
+
} catch (e) {
|
|
7576
|
+
return {
|
|
7577
|
+
tool_use_id: call.id,
|
|
7578
|
+
name: call.name,
|
|
7579
|
+
content: SafetyGuard.redact(`Tool execution failed: ${e?.message ?? String(e)}`),
|
|
7580
|
+
is_error: true
|
|
7581
|
+
};
|
|
7582
|
+
}
|
|
7583
|
+
})
|
|
7584
|
+
);
|
|
7585
|
+
queue.push({ prompt: "", toolResults });
|
|
7586
|
+
} else {
|
|
7587
|
+
for (const call of pendingToolCalls) {
|
|
7588
|
+
const result = await this.executor.execute(call.name, call.args, this.projectRoot);
|
|
6876
7589
|
const redactedContent = SafetyGuard.redact(result.content);
|
|
6877
7590
|
const redactedResult = { ...result, content: redactedContent };
|
|
6878
7591
|
queue.push(`[TOOL_RESULT]: ${JSON.stringify(redactedResult)}`);
|
|
@@ -7119,8 +7832,8 @@ var init_Mutex = __esm({
|
|
|
7119
7832
|
*/
|
|
7120
7833
|
acquire() {
|
|
7121
7834
|
let release3;
|
|
7122
|
-
const next = new Promise((
|
|
7123
|
-
release3 =
|
|
7835
|
+
const next = new Promise((resolve21) => {
|
|
7836
|
+
release3 = resolve21;
|
|
7124
7837
|
});
|
|
7125
7838
|
const entry = this._queue.then(() => release3);
|
|
7126
7839
|
this._queue = this._queue.then(() => next);
|
|
@@ -7158,8 +7871,8 @@ var init_lockfile = __esm({
|
|
|
7158
7871
|
});
|
|
7159
7872
|
|
|
7160
7873
|
// ../core/src/TrustStore.ts
|
|
7161
|
-
import { join as
|
|
7162
|
-
import { homedir as
|
|
7874
|
+
import { join as join17, dirname as dirname4 } from "path";
|
|
7875
|
+
import { homedir as homedir10, platform as platform3 } from "os";
|
|
7163
7876
|
import { existsSync as existsSync12, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
7164
7877
|
import { readFile as readFile12, writeFile as writeFile5, rename as rename2, chmod } from "fs/promises";
|
|
7165
7878
|
import { randomBytes as randomBytes9 } from "crypto";
|
|
@@ -7170,7 +7883,7 @@ var init_TrustStore = __esm({
|
|
|
7170
7883
|
init_esm_shims();
|
|
7171
7884
|
init_Mutex();
|
|
7172
7885
|
init_lockfile();
|
|
7173
|
-
USER_SETTINGS_PATH =
|
|
7886
|
+
USER_SETTINGS_PATH = join17(homedir10(), ".msapling", "settings.json");
|
|
7174
7887
|
TrustStore = class {
|
|
7175
7888
|
/** Current in-memory set of trusted `tool:command` keys. */
|
|
7176
7889
|
trusted = /* @__PURE__ */ new Set();
|
|
@@ -7327,9 +8040,118 @@ var init_TrustStore = __esm({
|
|
|
7327
8040
|
}
|
|
7328
8041
|
});
|
|
7329
8042
|
|
|
8043
|
+
// ../core/src/BackupIndex.ts
|
|
8044
|
+
import { join as join18 } from "path";
|
|
8045
|
+
import { homedir as homedir11 } from "os";
|
|
8046
|
+
import {
|
|
8047
|
+
readFileSync as readFileSync3,
|
|
8048
|
+
appendFileSync as appendFileSync2,
|
|
8049
|
+
existsSync as existsSync13,
|
|
8050
|
+
mkdirSync as mkdirSync3,
|
|
8051
|
+
copyFileSync
|
|
8052
|
+
} from "fs";
|
|
8053
|
+
import { randomBytes as randomBytes10 } from "crypto";
|
|
8054
|
+
function _setBackupDirOverride(dir) {
|
|
8055
|
+
_backupDirOverride = dir;
|
|
8056
|
+
}
|
|
8057
|
+
function getBackupDir() {
|
|
8058
|
+
return _backupDirOverride ?? join18(homedir11(), ".msapling", "backups");
|
|
8059
|
+
}
|
|
8060
|
+
function getManifestPath() {
|
|
8061
|
+
return join18(getBackupDir(), "manifest.jsonl");
|
|
8062
|
+
}
|
|
8063
|
+
function ensureBackupDir() {
|
|
8064
|
+
const dir = getBackupDir();
|
|
8065
|
+
if (!existsSync13(dir)) {
|
|
8066
|
+
mkdirSync3(dir, { recursive: true });
|
|
8067
|
+
}
|
|
8068
|
+
}
|
|
8069
|
+
function openTurn(label) {
|
|
8070
|
+
ensureBackupDir();
|
|
8071
|
+
_turnCounter += 1;
|
|
8072
|
+
const turnId = `turn-${Date.now()}-${randomBytes10(4).toString("hex")}`;
|
|
8073
|
+
const entry = {
|
|
8074
|
+
turnId,
|
|
8075
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8076
|
+
label: label ?? `Turn ${_turnCounter}`,
|
|
8077
|
+
backups: []
|
|
8078
|
+
};
|
|
8079
|
+
_openTurns.set(turnId, entry);
|
|
8080
|
+
return turnId;
|
|
8081
|
+
}
|
|
8082
|
+
function recordBackup(turnId, originalPath, backupPath) {
|
|
8083
|
+
const entry = _openTurns.get(turnId);
|
|
8084
|
+
if (!entry) return;
|
|
8085
|
+
entry.backups.push({ originalPath, backupPath });
|
|
8086
|
+
}
|
|
8087
|
+
function closeTurn(turnId) {
|
|
8088
|
+
const entry = _openTurns.get(turnId);
|
|
8089
|
+
if (!entry) return;
|
|
8090
|
+
_openTurns.delete(turnId);
|
|
8091
|
+
entry.closedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8092
|
+
try {
|
|
8093
|
+
ensureBackupDir();
|
|
8094
|
+
appendFileSync2(getManifestPath(), JSON.stringify(entry) + "\n", "utf8");
|
|
8095
|
+
} catch {
|
|
8096
|
+
}
|
|
8097
|
+
}
|
|
8098
|
+
function listCheckpoints() {
|
|
8099
|
+
const mp = getManifestPath();
|
|
8100
|
+
if (!existsSync13(mp)) return [];
|
|
8101
|
+
let raw;
|
|
8102
|
+
try {
|
|
8103
|
+
raw = readFileSync3(mp, "utf8");
|
|
8104
|
+
} catch {
|
|
8105
|
+
return [];
|
|
8106
|
+
}
|
|
8107
|
+
const entries = [];
|
|
8108
|
+
for (const line of raw.split("\n")) {
|
|
8109
|
+
const trimmed = line.trim();
|
|
8110
|
+
if (!trimmed) continue;
|
|
8111
|
+
try {
|
|
8112
|
+
entries.push(JSON.parse(trimmed));
|
|
8113
|
+
} catch {
|
|
8114
|
+
}
|
|
8115
|
+
}
|
|
8116
|
+
return entries.reverse().slice(0, MAX_CHECKPOINTS);
|
|
8117
|
+
}
|
|
8118
|
+
function restoreCheckpoint(entry) {
|
|
8119
|
+
const results = [];
|
|
8120
|
+
for (const rec of entry.backups) {
|
|
8121
|
+
if (!existsSync13(rec.backupPath)) {
|
|
8122
|
+
results.push(`Skip (backup file missing): ${rec.originalPath}`);
|
|
8123
|
+
continue;
|
|
8124
|
+
}
|
|
8125
|
+
try {
|
|
8126
|
+
copyFileSync(rec.backupPath, rec.originalPath);
|
|
8127
|
+
results.push(`Restored: ${rec.originalPath}`);
|
|
8128
|
+
} catch (e) {
|
|
8129
|
+
results.push(`Error restoring ${rec.originalPath}: ${e.message}`);
|
|
8130
|
+
}
|
|
8131
|
+
}
|
|
8132
|
+
return results;
|
|
8133
|
+
}
|
|
8134
|
+
function manifestPath() {
|
|
8135
|
+
return getManifestPath();
|
|
8136
|
+
}
|
|
8137
|
+
function backupDir() {
|
|
8138
|
+
return getBackupDir();
|
|
8139
|
+
}
|
|
8140
|
+
var MAX_CHECKPOINTS, _backupDirOverride, _openTurns, _turnCounter;
|
|
8141
|
+
var init_BackupIndex = __esm({
|
|
8142
|
+
"../core/src/BackupIndex.ts"() {
|
|
8143
|
+
"use strict";
|
|
8144
|
+
init_esm_shims();
|
|
8145
|
+
MAX_CHECKPOINTS = 50;
|
|
8146
|
+
_backupDirOverride = null;
|
|
8147
|
+
_openTurns = /* @__PURE__ */ new Map();
|
|
8148
|
+
_turnCounter = 0;
|
|
8149
|
+
}
|
|
8150
|
+
});
|
|
8151
|
+
|
|
7330
8152
|
// ../core/src/Storage/vault.ts
|
|
7331
|
-
import { join as
|
|
7332
|
-
import { chmodSync, existsSync as
|
|
8153
|
+
import { join as join19 } from "path";
|
|
8154
|
+
import { chmodSync, existsSync as existsSync14, renameSync as renameSync2, unlinkSync } from "fs";
|
|
7333
8155
|
import { writeFile as writeFile6, readFile as readFile13 } from "fs/promises";
|
|
7334
8156
|
import { createHash as createHash4 } from "crypto";
|
|
7335
8157
|
async function getKeytar() {
|
|
@@ -7343,7 +8165,7 @@ async function getKeytar() {
|
|
|
7343
8165
|
return _keytar;
|
|
7344
8166
|
}
|
|
7345
8167
|
async function saveToken(baseDir, token) {
|
|
7346
|
-
const filePath =
|
|
8168
|
+
const filePath = join19(baseDir, "vault", "token");
|
|
7347
8169
|
try {
|
|
7348
8170
|
const kt = await getKeytar();
|
|
7349
8171
|
if (!kt) throw new Error("keyring not loadable");
|
|
@@ -7352,12 +8174,12 @@ async function saveToken(baseDir, token) {
|
|
|
7352
8174
|
throw new KeychainUnavailableError(e instanceof Error ? e.message : String(e));
|
|
7353
8175
|
}
|
|
7354
8176
|
try {
|
|
7355
|
-
if (
|
|
8177
|
+
if (existsSync14(filePath)) unlinkSync(filePath);
|
|
7356
8178
|
} catch {
|
|
7357
8179
|
}
|
|
7358
8180
|
}
|
|
7359
8181
|
async function loadToken(baseDir) {
|
|
7360
|
-
const filePath =
|
|
8182
|
+
const filePath = join19(baseDir, "vault", "token");
|
|
7361
8183
|
let kt = null;
|
|
7362
8184
|
try {
|
|
7363
8185
|
kt = await getKeytar();
|
|
@@ -7368,7 +8190,7 @@ async function loadToken(baseDir) {
|
|
|
7368
8190
|
kt = null;
|
|
7369
8191
|
console.debug(`Keychain unavailable (${e instanceof Error ? e.message : String(e)})`);
|
|
7370
8192
|
}
|
|
7371
|
-
if (
|
|
8193
|
+
if (existsSync14(filePath)) {
|
|
7372
8194
|
const legacyToken = (await readFile13(filePath, "utf8")).trim();
|
|
7373
8195
|
if (!legacyToken) return null;
|
|
7374
8196
|
if (kt) {
|
|
@@ -7387,7 +8209,7 @@ async function loadToken(baseDir) {
|
|
|
7387
8209
|
return null;
|
|
7388
8210
|
}
|
|
7389
8211
|
async function clearToken(baseDir) {
|
|
7390
|
-
const filePath =
|
|
8212
|
+
const filePath = join19(baseDir, "vault", "token");
|
|
7391
8213
|
try {
|
|
7392
8214
|
const kt = await getKeytar();
|
|
7393
8215
|
if (kt) await kt.deletePassword(KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT);
|
|
@@ -7395,7 +8217,7 @@ async function clearToken(baseDir) {
|
|
|
7395
8217
|
console.debug(`Keychain unavailable for deletion (${e instanceof Error ? e.message : String(e)})`);
|
|
7396
8218
|
}
|
|
7397
8219
|
try {
|
|
7398
|
-
if (
|
|
8220
|
+
if (existsSync14(filePath)) {
|
|
7399
8221
|
const fs3 = await import("fs/promises");
|
|
7400
8222
|
await fs3.unlink(filePath);
|
|
7401
8223
|
}
|
|
@@ -7417,8 +8239,8 @@ async function getOrCreateJournalKey() {
|
|
|
7417
8239
|
const buf = Buffer.from(existing, "base64");
|
|
7418
8240
|
if (buf.length === 32) return buf;
|
|
7419
8241
|
}
|
|
7420
|
-
const { randomBytes:
|
|
7421
|
-
const key =
|
|
8242
|
+
const { randomBytes: randomBytes16 } = await import("crypto");
|
|
8243
|
+
const key = randomBytes16(32);
|
|
7422
8244
|
await kt.setPassword(KEYCHAIN_SERVICE, KEYCHAIN_JOURNAL_ACCOUNT, key.toString("base64"));
|
|
7423
8245
|
return key;
|
|
7424
8246
|
} catch (e) {
|
|
@@ -7428,8 +8250,8 @@ async function getOrCreateJournalKey() {
|
|
|
7428
8250
|
}
|
|
7429
8251
|
async function writeVaultRef(baseDir, label, value) {
|
|
7430
8252
|
const hash = createHash4("sha256").update(value, "utf8").digest("hex");
|
|
7431
|
-
const objectPath =
|
|
7432
|
-
const refPath =
|
|
8253
|
+
const objectPath = join19(baseDir, "vault", "objects", hash);
|
|
8254
|
+
const refPath = join19(baseDir, "vault", "refs", label);
|
|
7433
8255
|
const refTmp = `${refPath}.tmp`;
|
|
7434
8256
|
await writeFile6(objectPath, value, "utf8");
|
|
7435
8257
|
if (process.platform !== "win32") {
|
|
@@ -7440,11 +8262,11 @@ async function writeVaultRef(baseDir, label, value) {
|
|
|
7440
8262
|
return hash;
|
|
7441
8263
|
}
|
|
7442
8264
|
async function readVaultRef(baseDir, label) {
|
|
7443
|
-
const refPath =
|
|
7444
|
-
if (!
|
|
8265
|
+
const refPath = join19(baseDir, "vault", "refs", label);
|
|
8266
|
+
if (!existsSync14(refPath)) return null;
|
|
7445
8267
|
const hash = (await readFile13(refPath, "utf8")).trim();
|
|
7446
|
-
const objectPath =
|
|
7447
|
-
if (!
|
|
8268
|
+
const objectPath = join19(baseDir, "vault", "objects", hash);
|
|
8269
|
+
if (!existsSync14(objectPath)) return null;
|
|
7448
8270
|
return readFile13(objectPath, "utf8");
|
|
7449
8271
|
}
|
|
7450
8272
|
var _keytar, KEYCHAIN_SERVICE, KEYCHAIN_ACCOUNT, KeychainUnavailableError, KEYCHAIN_JOURNAL_ACCOUNT;
|
|
@@ -7468,20 +8290,20 @@ var init_vault = __esm({
|
|
|
7468
8290
|
});
|
|
7469
8291
|
|
|
7470
8292
|
// ../core/src/Storage/recipes.ts
|
|
7471
|
-
import { join as
|
|
7472
|
-
import { existsSync as
|
|
8293
|
+
import { join as join20 } from "path";
|
|
8294
|
+
import { existsSync as existsSync15, renameSync as renameSync3 } from "fs";
|
|
7473
8295
|
import { writeFile as writeFile7, readFile as readFile14 } from "fs/promises";
|
|
7474
8296
|
import { createHash as createHash5 } from "crypto";
|
|
7475
8297
|
async function registerRecipe(baseDir, name, content) {
|
|
7476
8298
|
const hash = createHash5("sha256").update(content, "utf8").digest("hex");
|
|
7477
|
-
const objectPath =
|
|
7478
|
-
const indexPath =
|
|
8299
|
+
const objectPath = join20(baseDir, "cache", "recipes", "objects", hash);
|
|
8300
|
+
const indexPath = join20(baseDir, "cache", "recipes", "index.json");
|
|
7479
8301
|
const indexTmp = `${indexPath}.tmp`;
|
|
7480
|
-
if (!
|
|
8302
|
+
if (!existsSync15(objectPath)) {
|
|
7481
8303
|
await writeFile7(objectPath, content, "utf8");
|
|
7482
8304
|
}
|
|
7483
8305
|
let index = {};
|
|
7484
|
-
if (
|
|
8306
|
+
if (existsSync15(indexPath)) {
|
|
7485
8307
|
try {
|
|
7486
8308
|
index = JSON.parse(await readFile14(indexPath, "utf8"));
|
|
7487
8309
|
} catch {
|
|
@@ -7494,15 +8316,15 @@ async function registerRecipe(baseDir, name, content) {
|
|
|
7494
8316
|
return hash;
|
|
7495
8317
|
}
|
|
7496
8318
|
async function resolveRecipe(baseDir, nameOrRef) {
|
|
7497
|
-
const indexPath =
|
|
8319
|
+
const indexPath = join20(baseDir, "cache", "recipes", "index.json");
|
|
7498
8320
|
const atIdx = nameOrRef.indexOf("@");
|
|
7499
8321
|
if (atIdx !== -1) {
|
|
7500
8322
|
const hash2 = nameOrRef.slice(atIdx + 1);
|
|
7501
|
-
const objectPath2 =
|
|
7502
|
-
if (!
|
|
8323
|
+
const objectPath2 = join20(baseDir, "cache", "recipes", "objects", hash2);
|
|
8324
|
+
if (!existsSync15(objectPath2)) return null;
|
|
7503
8325
|
return { hash: hash2, content: await readFile14(objectPath2, "utf8") };
|
|
7504
8326
|
}
|
|
7505
|
-
if (!
|
|
8327
|
+
if (!existsSync15(indexPath)) return null;
|
|
7506
8328
|
let index;
|
|
7507
8329
|
try {
|
|
7508
8330
|
index = JSON.parse(await readFile14(indexPath, "utf8"));
|
|
@@ -7511,8 +8333,8 @@ async function resolveRecipe(baseDir, nameOrRef) {
|
|
|
7511
8333
|
}
|
|
7512
8334
|
const hash = index[nameOrRef];
|
|
7513
8335
|
if (!hash) return null;
|
|
7514
|
-
const objectPath =
|
|
7515
|
-
if (!
|
|
8336
|
+
const objectPath = join20(baseDir, "cache", "recipes", "objects", hash);
|
|
8337
|
+
if (!existsSync15(objectPath)) return null;
|
|
7516
8338
|
return { hash, content: await readFile14(objectPath, "utf8") };
|
|
7517
8339
|
}
|
|
7518
8340
|
var init_recipes = __esm({
|
|
@@ -7523,25 +8345,25 @@ var init_recipes = __esm({
|
|
|
7523
8345
|
});
|
|
7524
8346
|
|
|
7525
8347
|
// ../core/src/Storage/history.ts
|
|
7526
|
-
import { join as
|
|
7527
|
-
import { existsSync as
|
|
8348
|
+
import { join as join21, basename } from "path";
|
|
8349
|
+
import { existsSync as existsSync16, renameSync as renameSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
7528
8350
|
import { writeFile as writeFile8, readFile as readFile15, appendFile } from "fs/promises";
|
|
7529
|
-
import { createHash as createHash6, randomBytes as
|
|
8351
|
+
import { createHash as createHash6, randomBytes as randomBytes11 } from "crypto";
|
|
7530
8352
|
function hashLine(line) {
|
|
7531
8353
|
return createHash6("sha256").update(line, "utf8").digest("hex");
|
|
7532
8354
|
}
|
|
7533
8355
|
async function appendHistoryEntry(baseDir, historyMutex, content) {
|
|
7534
|
-
const path2 =
|
|
8356
|
+
const path2 = join21(baseDir, "history", "shell_history.jsonl");
|
|
7535
8357
|
return historyMutex.run(async () => {
|
|
7536
8358
|
let release3 = null;
|
|
7537
8359
|
try {
|
|
7538
|
-
if (!
|
|
8360
|
+
if (!existsSync16(path2)) {
|
|
7539
8361
|
await writeFile8(path2, "", "utf8");
|
|
7540
8362
|
}
|
|
7541
8363
|
release3 = await lock(path2, { retries: 5, retryWait: 50 });
|
|
7542
8364
|
let prevHash = null;
|
|
7543
8365
|
let seq = 1;
|
|
7544
|
-
if (
|
|
8366
|
+
if (existsSync16(path2)) {
|
|
7545
8367
|
const raw = (await readFile15(path2, "utf8")).trimEnd();
|
|
7546
8368
|
if (raw.length > 0) {
|
|
7547
8369
|
const lines = raw.split("\n");
|
|
@@ -7574,8 +8396,8 @@ async function appendHistoryEntry(baseDir, historyMutex, content) {
|
|
|
7574
8396
|
});
|
|
7575
8397
|
}
|
|
7576
8398
|
async function loadHistoryEntries(baseDir) {
|
|
7577
|
-
const path2 =
|
|
7578
|
-
if (!
|
|
8399
|
+
const path2 = join21(baseDir, "history", "shell_history.jsonl");
|
|
8400
|
+
if (!existsSync16(path2)) return [];
|
|
7579
8401
|
const raw = await readFile15(path2, "utf8");
|
|
7580
8402
|
const entries = [];
|
|
7581
8403
|
for (const line of raw.split("\n")) {
|
|
@@ -7607,10 +8429,10 @@ async function verifyHistory(baseDir) {
|
|
|
7607
8429
|
return breaks.length === 0 ? { ok: true } : { ok: false, breaks };
|
|
7608
8430
|
}
|
|
7609
8431
|
async function saveHistory(baseDir, historyMutex, history) {
|
|
7610
|
-
const path2 =
|
|
8432
|
+
const path2 = join21(baseDir, "history", "shell_history.json");
|
|
7611
8433
|
let release3;
|
|
7612
8434
|
try {
|
|
7613
|
-
if (!
|
|
8435
|
+
if (!existsSync16(path2)) writeFileSync3(path2, "[]", "utf8");
|
|
7614
8436
|
release3 = await lock(path2, { realpath: false, retries: 5, retryWait: 50 });
|
|
7615
8437
|
await historyMutex.run(async () => {
|
|
7616
8438
|
const tmpPath = `${path2}.tmp`;
|
|
@@ -7620,7 +8442,7 @@ async function saveHistory(baseDir, historyMutex, history) {
|
|
|
7620
8442
|
renameSync4(tmpPath, path2);
|
|
7621
8443
|
} catch (e) {
|
|
7622
8444
|
try {
|
|
7623
|
-
if (
|
|
8445
|
+
if (existsSync16(tmpPath)) {
|
|
7624
8446
|
const fs3 = await import("fs/promises");
|
|
7625
8447
|
await fs3.unlink(tmpPath);
|
|
7626
8448
|
}
|
|
@@ -7640,21 +8462,21 @@ async function saveHistory(baseDir, historyMutex, history) {
|
|
|
7640
8462
|
}
|
|
7641
8463
|
}
|
|
7642
8464
|
async function loadHistory(baseDir, historyMutex) {
|
|
7643
|
-
const path2 =
|
|
7644
|
-
if (!
|
|
8465
|
+
const path2 = join21(baseDir, "history", "shell_history.json");
|
|
8466
|
+
if (!existsSync16(path2)) return [];
|
|
7645
8467
|
let release3;
|
|
7646
8468
|
try {
|
|
7647
8469
|
release3 = await lock(path2, { realpath: false, retries: 5, retryWait: 50 });
|
|
7648
8470
|
return historyMutex.run(async () => {
|
|
7649
|
-
if (
|
|
8471
|
+
if (existsSync16(path2)) {
|
|
7650
8472
|
const text = await readFile15(path2, "utf8");
|
|
7651
8473
|
try {
|
|
7652
8474
|
return JSON.parse(text);
|
|
7653
8475
|
} catch (parseErr) {
|
|
7654
8476
|
const filename = basename(path2) || "shell_history.json";
|
|
7655
8477
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
7656
|
-
const suffix =
|
|
7657
|
-
const corruptBackupPath =
|
|
8478
|
+
const suffix = randomBytes11(4).toString("hex");
|
|
8479
|
+
const corruptBackupPath = join21(
|
|
7658
8480
|
baseDir,
|
|
7659
8481
|
"history",
|
|
7660
8482
|
`${filename}.corrupt.${stamp}-${suffix}.bak`
|
|
@@ -7689,15 +8511,15 @@ var init_history = __esm({
|
|
|
7689
8511
|
});
|
|
7690
8512
|
|
|
7691
8513
|
// ../core/src/Storage/permissions.ts
|
|
7692
|
-
import { join as
|
|
7693
|
-
import { existsSync as
|
|
8514
|
+
import { join as join22 } from "path";
|
|
8515
|
+
import { existsSync as existsSync17, renameSync as renameSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
7694
8516
|
import { writeFile as writeFile9, readFile as readFile16 } from "fs/promises";
|
|
7695
|
-
import { randomBytes as
|
|
8517
|
+
import { randomBytes as randomBytes12 } from "crypto";
|
|
7696
8518
|
async function savePermissions(baseDir, permissionsMutex, permissions) {
|
|
7697
|
-
const path2 =
|
|
8519
|
+
const path2 = join22(baseDir, "vault", "permissions.json");
|
|
7698
8520
|
let release3;
|
|
7699
8521
|
try {
|
|
7700
|
-
if (!
|
|
8522
|
+
if (!existsSync17(path2)) writeFileSync4(path2, "{}", "utf8");
|
|
7701
8523
|
release3 = await lock(path2, { realpath: false, retries: 5, retryWait: 50 });
|
|
7702
8524
|
await permissionsMutex.run(async () => {
|
|
7703
8525
|
const tmpPath = `${path2}.tmp`;
|
|
@@ -7707,7 +8529,7 @@ async function savePermissions(baseDir, permissionsMutex, permissions) {
|
|
|
7707
8529
|
renameSync5(tmpPath, path2);
|
|
7708
8530
|
} catch (e) {
|
|
7709
8531
|
try {
|
|
7710
|
-
if (
|
|
8532
|
+
if (existsSync17(tmpPath)) {
|
|
7711
8533
|
const fs3 = await import("fs/promises");
|
|
7712
8534
|
await fs3.unlink(tmpPath);
|
|
7713
8535
|
}
|
|
@@ -7727,21 +8549,21 @@ async function savePermissions(baseDir, permissionsMutex, permissions) {
|
|
|
7727
8549
|
}
|
|
7728
8550
|
}
|
|
7729
8551
|
async function loadPermissions(baseDir, permissionsMutex) {
|
|
7730
|
-
const path2 =
|
|
7731
|
-
if (!
|
|
8552
|
+
const path2 = join22(baseDir, "vault", "permissions.json");
|
|
8553
|
+
if (!existsSync17(path2)) return { trustedCommands: [], trustedPaths: [] };
|
|
7732
8554
|
let release3;
|
|
7733
8555
|
try {
|
|
7734
8556
|
release3 = await lock(path2, { realpath: false, retries: 5, retryWait: 50 });
|
|
7735
8557
|
return permissionsMutex.run(async () => {
|
|
7736
|
-
if (
|
|
8558
|
+
if (existsSync17(path2)) {
|
|
7737
8559
|
const text = await readFile16(path2, "utf8");
|
|
7738
8560
|
try {
|
|
7739
8561
|
return JSON.parse(text);
|
|
7740
8562
|
} catch (parseErr) {
|
|
7741
8563
|
const filename = path2.split("/").pop() || "permissions.json";
|
|
7742
8564
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
7743
|
-
const suffix =
|
|
7744
|
-
const corruptBackupPath =
|
|
8565
|
+
const suffix = randomBytes12(4).toString("hex");
|
|
8566
|
+
const corruptBackupPath = join22(
|
|
7745
8567
|
baseDir,
|
|
7746
8568
|
"vault",
|
|
7747
8569
|
`${filename}.corrupt.${stamp}-${suffix}.bak`
|
|
@@ -7776,11 +8598,11 @@ var init_permissions = __esm({
|
|
|
7776
8598
|
});
|
|
7777
8599
|
|
|
7778
8600
|
// ../core/src/Storage.ts
|
|
7779
|
-
import { join as
|
|
7780
|
-
import { homedir as
|
|
8601
|
+
import { join as join23 } from "path";
|
|
8602
|
+
import { homedir as homedir12 } from "os";
|
|
7781
8603
|
import { chmodSync as chmodSync2 } from "fs";
|
|
7782
8604
|
import { mkdir as mkdir6, writeFile as writeFile10 } from "fs/promises";
|
|
7783
|
-
import { randomBytes as
|
|
8605
|
+
import { randomBytes as randomBytes13 } from "crypto";
|
|
7784
8606
|
var StorageManager;
|
|
7785
8607
|
var init_Storage = __esm({
|
|
7786
8608
|
"../core/src/Storage.ts"() {
|
|
@@ -7807,7 +8629,7 @@ var init_Storage = __esm({
|
|
|
7807
8629
|
*/
|
|
7808
8630
|
_ready;
|
|
7809
8631
|
constructor() {
|
|
7810
|
-
this.baseDir =
|
|
8632
|
+
this.baseDir = join23(homedir12(), ".msapling");
|
|
7811
8633
|
this._ready = this.ensureDirs();
|
|
7812
8634
|
}
|
|
7813
8635
|
async ensureDirs() {
|
|
@@ -7824,7 +8646,7 @@ var init_Storage = __esm({
|
|
|
7824
8646
|
"cache/recipes/objects"
|
|
7825
8647
|
];
|
|
7826
8648
|
for (const sub of subdirs) {
|
|
7827
|
-
await mkdir6(
|
|
8649
|
+
await mkdir6(join23(this.baseDir, sub), { recursive: true });
|
|
7828
8650
|
}
|
|
7829
8651
|
if (process.platform !== "win32") {
|
|
7830
8652
|
chmodSync2(this.baseDir, 448);
|
|
@@ -7901,8 +8723,8 @@ var init_Storage = __esm({
|
|
|
7901
8723
|
await this._ready;
|
|
7902
8724
|
const filename = filePath.split("/").pop() || "file";
|
|
7903
8725
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
7904
|
-
const suffix =
|
|
7905
|
-
const backupPath =
|
|
8726
|
+
const suffix = randomBytes13(4).toString("hex");
|
|
8727
|
+
const backupPath = join23(this.baseDir, "backups", `${filename}.backup-${stamp}-${suffix}.bak`);
|
|
7906
8728
|
await writeFile10(backupPath, content, "utf8");
|
|
7907
8729
|
if (process.platform !== "win32") {
|
|
7908
8730
|
chmodSync2(backupPath, 384);
|
|
@@ -7933,15 +8755,15 @@ var init_journalCrypto = __esm({
|
|
|
7933
8755
|
});
|
|
7934
8756
|
|
|
7935
8757
|
// ../core/src/Settings.ts
|
|
7936
|
-
import { homedir as
|
|
7937
|
-
import { join as
|
|
7938
|
-
import { existsSync as
|
|
8758
|
+
import { homedir as homedir13 } from "os";
|
|
8759
|
+
import { join as join24 } from "path";
|
|
8760
|
+
import { existsSync as existsSync18 } from "fs";
|
|
7939
8761
|
import * as fs from "fs";
|
|
7940
8762
|
import { readFile as readFile17 } from "fs/promises";
|
|
7941
|
-
import { randomBytes as
|
|
8763
|
+
import { randomBytes as randomBytes14 } from "crypto";
|
|
7942
8764
|
function backupStaleFile(p) {
|
|
7943
8765
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
7944
|
-
const suffix =
|
|
8766
|
+
const suffix = randomBytes14(4).toString("hex");
|
|
7945
8767
|
fs.renameSync(p, `${p}.broken-${stamp}-${suffix}`);
|
|
7946
8768
|
}
|
|
7947
8769
|
function ensureConfigDir(p) {
|
|
@@ -7964,7 +8786,7 @@ function ensureConfigDir(p) {
|
|
|
7964
8786
|
}
|
|
7965
8787
|
async function readJson(path2) {
|
|
7966
8788
|
try {
|
|
7967
|
-
if (!
|
|
8789
|
+
if (!existsSync18(path2)) return null;
|
|
7968
8790
|
const text = await readFile17(path2, "utf8");
|
|
7969
8791
|
if (!text.trim()) return null;
|
|
7970
8792
|
return JSON.parse(text);
|
|
@@ -8019,9 +8841,9 @@ function mergeSettings(base, override) {
|
|
|
8019
8841
|
}
|
|
8020
8842
|
async function loadSettings(cwd = process.cwd(), env = process.env, warn) {
|
|
8021
8843
|
const sources = [];
|
|
8022
|
-
const home = env.HOME || env.USERPROFILE || process.env.HOME || process.env.USERPROFILE ||
|
|
8023
|
-
const userPath =
|
|
8024
|
-
const projectPath =
|
|
8844
|
+
const home = env.HOME || env.USERPROFILE || process.env.HOME || process.env.USERPROFILE || homedir13() || ".";
|
|
8845
|
+
const userPath = join24(home, ".msapling", "settings.json");
|
|
8846
|
+
const projectPath = join24(cwd, ".msapling", "settings.json");
|
|
8025
8847
|
const [user, project] = await Promise.all([readJson(userPath), readJson(projectPath)]);
|
|
8026
8848
|
if (user) sources.push(userPath);
|
|
8027
8849
|
if (project) sources.push(projectPath);
|
|
@@ -8074,7 +8896,7 @@ var init_Settings = __esm({
|
|
|
8074
8896
|
});
|
|
8075
8897
|
|
|
8076
8898
|
// ../core/src/mcp/client.ts
|
|
8077
|
-
import { spawn as
|
|
8899
|
+
import { spawn as spawn9 } from "child_process";
|
|
8078
8900
|
var PROTOCOL_VERSION, CLIENT_INFO, MCPClientError, MCPClient, MCPRegistry;
|
|
8079
8901
|
var init_client = __esm({
|
|
8080
8902
|
"../core/src/mcp/client.ts"() {
|
|
@@ -8110,7 +8932,7 @@ var init_client = __esm({
|
|
|
8110
8932
|
if (this.proc) return;
|
|
8111
8933
|
const env = { ...process.env, ...this.config.env ?? {} };
|
|
8112
8934
|
try {
|
|
8113
|
-
this.proc =
|
|
8935
|
+
this.proc = spawn9(this.config.command, this.config.args ?? [], {
|
|
8114
8936
|
stdio: ["pipe", "pipe", "pipe"],
|
|
8115
8937
|
env
|
|
8116
8938
|
});
|
|
@@ -8184,7 +9006,7 @@ var init_client = __esm({
|
|
|
8184
9006
|
if (!this.proc) throw new MCPClientError(`MCP server "${this.name}" not started`);
|
|
8185
9007
|
const id = this.nextId++;
|
|
8186
9008
|
const frame = { jsonrpc: "2.0", id, method, params };
|
|
8187
|
-
return new Promise((
|
|
9009
|
+
return new Promise((resolve21, reject) => {
|
|
8188
9010
|
const timer = setTimeout(() => {
|
|
8189
9011
|
this.pending.delete(id);
|
|
8190
9012
|
reject(new MCPClientError(`MCP request ${method} timed out after ${timeoutMs}ms`));
|
|
@@ -8192,7 +9014,7 @@ var init_client = __esm({
|
|
|
8192
9014
|
this.pending.set(id, {
|
|
8193
9015
|
resolve: (v) => {
|
|
8194
9016
|
clearTimeout(timer);
|
|
8195
|
-
|
|
9017
|
+
resolve21(v);
|
|
8196
9018
|
},
|
|
8197
9019
|
reject: (e) => {
|
|
8198
9020
|
clearTimeout(timer);
|
|
@@ -8219,7 +9041,7 @@ var init_client = __esm({
|
|
|
8219
9041
|
if (!this.proc?.stdout) return;
|
|
8220
9042
|
const stdout = this.proc.stdout;
|
|
8221
9043
|
const decoder = new TextDecoder();
|
|
8222
|
-
return new Promise((
|
|
9044
|
+
return new Promise((resolve21) => {
|
|
8223
9045
|
stdout.on("data", (chunk) => {
|
|
8224
9046
|
this.buffer += decoder.decode(chunk, { stream: true });
|
|
8225
9047
|
let idx;
|
|
@@ -8230,8 +9052,8 @@ var init_client = __esm({
|
|
|
8230
9052
|
this.handleFrame(line);
|
|
8231
9053
|
}
|
|
8232
9054
|
});
|
|
8233
|
-
stdout.on("end", () =>
|
|
8234
|
-
stdout.on("error", () =>
|
|
9055
|
+
stdout.on("end", () => resolve21());
|
|
9056
|
+
stdout.on("error", () => resolve21());
|
|
8235
9057
|
});
|
|
8236
9058
|
}
|
|
8237
9059
|
handleFrame(line) {
|
|
@@ -8371,6 +9193,8 @@ __export(src_exports2, {
|
|
|
8371
9193
|
APPROVAL_GATED: () => APPROVAL_GATED,
|
|
8372
9194
|
Agent: () => Agent,
|
|
8373
9195
|
AsyncMutex: () => AsyncMutex,
|
|
9196
|
+
BackgroundShellRegistry: () => BackgroundShellRegistry,
|
|
9197
|
+
BashBackgroundTool: () => BashBackgroundTool,
|
|
8374
9198
|
BashTool: () => BashTool,
|
|
8375
9199
|
ContextBudget: () => ContextBudget,
|
|
8376
9200
|
DEFAULT_SETTINGS: () => DEFAULT_SETTINGS,
|
|
@@ -8382,6 +9206,8 @@ __export(src_exports2, {
|
|
|
8382
9206
|
GrepSearchTool: () => GrepSearchTool,
|
|
8383
9207
|
HookRunner: () => HookRunner,
|
|
8384
9208
|
KeychainUnavailableError: () => KeychainUnavailableError,
|
|
9209
|
+
KillBackgroundShellTool: () => KillBackgroundShellTool,
|
|
9210
|
+
ListBackgroundShellsTool: () => ListBackgroundShellsTool,
|
|
8385
9211
|
ListDirectoryTool: () => ListDirectoryTool,
|
|
8386
9212
|
MAX_PLAN_CHARS: () => MAX_PLAN_CHARS,
|
|
8387
9213
|
MCPClient: () => MCPClient,
|
|
@@ -8392,8 +9218,10 @@ __export(src_exports2, {
|
|
|
8392
9218
|
NotebookEditTool: () => NotebookEditTool,
|
|
8393
9219
|
NotebookReadTool: () => NotebookReadTool,
|
|
8394
9220
|
PatchFileTool: () => PatchFileTool,
|
|
9221
|
+
ReadBackgroundShellTool: () => ReadBackgroundShellTool,
|
|
8395
9222
|
StorageManager: () => StorageManager,
|
|
8396
9223
|
SwarmManager: () => SwarmManager,
|
|
9224
|
+
TOOL_NAME_ALIASES: () => TOOL_NAME_ALIASES,
|
|
8397
9225
|
TodoReadTool: () => TodoReadTool,
|
|
8398
9226
|
TodoStore: () => TodoStore,
|
|
8399
9227
|
TodoWriteTool: () => TodoWriteTool,
|
|
@@ -8402,16 +9230,31 @@ __export(src_exports2, {
|
|
|
8402
9230
|
WebFetchTool: () => WebFetchTool,
|
|
8403
9231
|
WebSearchTool: () => WebSearchTool,
|
|
8404
9232
|
WriteFileTool: () => WriteFileTool,
|
|
9233
|
+
_setBackupDirOverride: () => _setBackupDirOverride,
|
|
9234
|
+
backupDir: () => backupDir,
|
|
8405
9235
|
buildCompactionPrompt: () => buildCompactionPrompt,
|
|
8406
9236
|
buildHwContext: () => buildHwContext,
|
|
9237
|
+
closeTurn: () => closeTurn,
|
|
9238
|
+
discoverAgentFiles: () => discoverAgentFiles,
|
|
8407
9239
|
ensureConfigDir: () => ensureConfigDir,
|
|
9240
|
+
findNamedAgent: () => findNamedAgent,
|
|
9241
|
+
formatBackgroundShells: () => formatBackgroundShells,
|
|
8408
9242
|
formatCell: () => formatCell,
|
|
8409
9243
|
formatNotebookHeader: () => formatNotebookHeader,
|
|
8410
9244
|
formatTodos: () => formatTodos,
|
|
8411
9245
|
getOrCreateJournalKey: () => getOrCreateJournalKey,
|
|
8412
9246
|
initJournalEncryption: () => initJournalEncryption,
|
|
9247
|
+
listCheckpoints: () => listCheckpoints,
|
|
9248
|
+
loadNamedAgents: () => loadNamedAgents,
|
|
8413
9249
|
loadProjectConfig: () => loadProjectConfig,
|
|
8414
9250
|
loadSettings: () => loadSettings,
|
|
9251
|
+
manifestPath: () => manifestPath,
|
|
9252
|
+
normalizeToolName: () => normalizeToolName,
|
|
9253
|
+
openTurn: () => openTurn,
|
|
9254
|
+
parseAgentFile: () => parseAgentFile,
|
|
9255
|
+
parseToolsValue: () => parseToolsValue,
|
|
9256
|
+
recordBackup: () => recordBackup,
|
|
9257
|
+
restoreCheckpoint: () => restoreCheckpoint,
|
|
8415
9258
|
takeSnapshot: () => takeSnapshot
|
|
8416
9259
|
});
|
|
8417
9260
|
var init_src3 = __esm({
|
|
@@ -8420,9 +9263,11 @@ var init_src3 = __esm({
|
|
|
8420
9263
|
init_esm_shims();
|
|
8421
9264
|
init_ToolExecutor();
|
|
8422
9265
|
init_Agent();
|
|
9266
|
+
init_namedAgents();
|
|
8423
9267
|
init_HardwareMonitor();
|
|
8424
9268
|
init_TrustStore();
|
|
8425
9269
|
init_ContextBudget();
|
|
9270
|
+
init_BackupIndex();
|
|
8426
9271
|
init_Storage();
|
|
8427
9272
|
init_Storage();
|
|
8428
9273
|
init_journalCrypto();
|
|
@@ -8442,6 +9287,7 @@ var init_src3 = __esm({
|
|
|
8442
9287
|
init_WebSearchTool();
|
|
8443
9288
|
init_PlanModeTools();
|
|
8444
9289
|
init_BashTool();
|
|
9290
|
+
init_BackgroundShellTool();
|
|
8445
9291
|
init_NotebookReadTool();
|
|
8446
9292
|
init_NotebookEditTool();
|
|
8447
9293
|
init_MultiEditFileTool();
|
|
@@ -8466,7 +9312,7 @@ function setRawModeGuarded(stdin, mode) {
|
|
|
8466
9312
|
}
|
|
8467
9313
|
}
|
|
8468
9314
|
async function promptPassword(prompt4) {
|
|
8469
|
-
return new Promise((
|
|
9315
|
+
return new Promise((resolve21) => {
|
|
8470
9316
|
const stdin = process.stdin;
|
|
8471
9317
|
const stdout = process.stdout;
|
|
8472
9318
|
stdout.write(prompt4);
|
|
@@ -8480,7 +9326,7 @@ async function promptPassword(prompt4) {
|
|
|
8480
9326
|
setRawModeGuarded(stdin, wasRaw);
|
|
8481
9327
|
stdin.removeListener("data", onData);
|
|
8482
9328
|
stdout.write("\n");
|
|
8483
|
-
|
|
9329
|
+
resolve21(value);
|
|
8484
9330
|
};
|
|
8485
9331
|
const onData = (chunk) => {
|
|
8486
9332
|
try {
|
|
@@ -8582,7 +9428,7 @@ async function loginWithGithubDevice(context) {
|
|
|
8582
9428
|
const deadline = Date.now() + expires_in * 1e3;
|
|
8583
9429
|
let githubToken = null;
|
|
8584
9430
|
while (Date.now() < deadline) {
|
|
8585
|
-
await new Promise((
|
|
9431
|
+
await new Promise((resolve21) => setTimeout(resolve21, pollMs));
|
|
8586
9432
|
let tokenResp;
|
|
8587
9433
|
try {
|
|
8588
9434
|
tokenResp = await fetch(GITHUB_TOKEN_URL, {
|
|
@@ -8606,7 +9452,7 @@ async function loginWithGithubDevice(context) {
|
|
|
8606
9452
|
if (tokenData.error === "authorization_pending") continue;
|
|
8607
9453
|
if (tokenData.error === "slow_down") {
|
|
8608
9454
|
pollMs += 5e3;
|
|
8609
|
-
await new Promise((
|
|
9455
|
+
await new Promise((resolve21) => setTimeout(resolve21, 5e3));
|
|
8610
9456
|
continue;
|
|
8611
9457
|
}
|
|
8612
9458
|
context.addMessage("system", `GitHub auth error: ${tokenData.error_description || tokenData.error}`);
|
|
@@ -8951,9 +9797,9 @@ var init_unlock = __esm({
|
|
|
8951
9797
|
});
|
|
8952
9798
|
|
|
8953
9799
|
// src/commands/doctor.ts
|
|
8954
|
-
import { homedir as
|
|
8955
|
-
import { join as
|
|
8956
|
-
import { existsSync as
|
|
9800
|
+
import { homedir as homedir14 } from "os";
|
|
9801
|
+
import { join as join25 } from "path";
|
|
9802
|
+
import { existsSync as existsSync20 } from "fs";
|
|
8957
9803
|
import { readFile as readFile18 } from "fs/promises";
|
|
8958
9804
|
async function checkApiHealth(client) {
|
|
8959
9805
|
try {
|
|
@@ -8979,9 +9825,9 @@ async function checkAuthStatus(client) {
|
|
|
8979
9825
|
}
|
|
8980
9826
|
}
|
|
8981
9827
|
async function checkSettingsFile() {
|
|
8982
|
-
const settingsPath =
|
|
9828
|
+
const settingsPath = join25(homedir14(), ".msapling", "settings.json");
|
|
8983
9829
|
try {
|
|
8984
|
-
if (!
|
|
9830
|
+
if (!existsSync20(settingsPath)) {
|
|
8985
9831
|
return { ok: false, message: `Not found: ${settingsPath}` };
|
|
8986
9832
|
}
|
|
8987
9833
|
const text = await readFile18(settingsPath, "utf8");
|
|
@@ -9656,7 +10502,7 @@ function setRawModeGuarded2(stdin, mode) {
|
|
|
9656
10502
|
}
|
|
9657
10503
|
}
|
|
9658
10504
|
async function promptSecret(prompt4) {
|
|
9659
|
-
return new Promise((
|
|
10505
|
+
return new Promise((resolve21) => {
|
|
9660
10506
|
const stdin = process.stdin;
|
|
9661
10507
|
const stdout = process.stdout;
|
|
9662
10508
|
stdout.write(prompt4);
|
|
@@ -9669,12 +10515,12 @@ async function promptSecret(prompt4) {
|
|
|
9669
10515
|
setRawModeGuarded2(stdin, wasRaw);
|
|
9670
10516
|
stdin.removeListener("data", onData);
|
|
9671
10517
|
stdout.write("\n");
|
|
9672
|
-
|
|
10518
|
+
resolve21(secret);
|
|
9673
10519
|
} else if (char === "") {
|
|
9674
10520
|
setRawModeGuarded2(stdin, wasRaw);
|
|
9675
10521
|
stdin.removeListener("data", onData);
|
|
9676
10522
|
stdout.write("\n");
|
|
9677
|
-
|
|
10523
|
+
resolve21("");
|
|
9678
10524
|
} else if (char === "\x7F" || char === "\b") {
|
|
9679
10525
|
secret = secret.slice(0, -1);
|
|
9680
10526
|
} else if (char >= " " && char <= "~") {
|
|
@@ -9834,8 +10680,8 @@ var init_memories = __esm({
|
|
|
9834
10680
|
|
|
9835
10681
|
// src/commands/mdrive.ts
|
|
9836
10682
|
import { readFile as readFile19, writeFile as writeFile11 } from "fs/promises";
|
|
9837
|
-
import { existsSync as
|
|
9838
|
-
import { basename as basename2, resolve as
|
|
10683
|
+
import { existsSync as existsSync21 } from "fs";
|
|
10684
|
+
import { basename as basename2, resolve as resolve15, relative as relative15, isAbsolute as isAbsolute15, sep as sep3 } from "path";
|
|
9839
10685
|
function formatBytes(b) {
|
|
9840
10686
|
if (!b) return "0";
|
|
9841
10687
|
if (b < 1024) return `${b}`;
|
|
@@ -9843,10 +10689,10 @@ function formatBytes(b) {
|
|
|
9843
10689
|
return `${(b / 1024 / 1024).toFixed(1)}M`;
|
|
9844
10690
|
}
|
|
9845
10691
|
function containLocalPath(targetPath, root = process.cwd()) {
|
|
9846
|
-
const resolvedRoot =
|
|
9847
|
-
const resolved =
|
|
9848
|
-
const rel =
|
|
9849
|
-
if (rel === ".." || rel.startsWith(`..${sep3}`) || rel.startsWith("../") ||
|
|
10692
|
+
const resolvedRoot = resolve15(root);
|
|
10693
|
+
const resolved = isAbsolute15(targetPath) ? resolve15(targetPath) : resolve15(resolvedRoot, targetPath);
|
|
10694
|
+
const rel = relative15(resolvedRoot, resolved);
|
|
10695
|
+
if (rel === ".." || rel.startsWith(`..${sep3}`) || rel.startsWith("../") || isAbsolute15(rel)) {
|
|
9850
10696
|
return null;
|
|
9851
10697
|
}
|
|
9852
10698
|
return resolved;
|
|
@@ -9927,7 +10773,7 @@ var init_mdrive = __esm({
|
|
|
9927
10773
|
context.addMessage("error", `Refusing to read local file outside the working directory: ${local}`);
|
|
9928
10774
|
return;
|
|
9929
10775
|
}
|
|
9930
|
-
if (!
|
|
10776
|
+
if (!existsSync21(absLocal)) {
|
|
9931
10777
|
context.addMessage("error", `Local file not found: ${absLocal}`);
|
|
9932
10778
|
return;
|
|
9933
10779
|
}
|
|
@@ -10048,14 +10894,14 @@ var init_clear = __esm({
|
|
|
10048
10894
|
});
|
|
10049
10895
|
|
|
10050
10896
|
// src/commands/mode.ts
|
|
10051
|
-
import { homedir as
|
|
10052
|
-
import { join as
|
|
10053
|
-
import { existsSync as
|
|
10897
|
+
import { homedir as homedir15 } from "os";
|
|
10898
|
+
import { join as join26 } from "path";
|
|
10899
|
+
import { existsSync as existsSync22 } from "fs";
|
|
10054
10900
|
import { readFile as readFile20, writeFile as writeFile12, mkdir as mkdir7 } from "fs/promises";
|
|
10055
10901
|
async function persistApprovalMode(mode, ttlMs) {
|
|
10056
10902
|
try {
|
|
10057
10903
|
let existing = {};
|
|
10058
|
-
if (
|
|
10904
|
+
if (existsSync22(SETTINGS_PATH)) {
|
|
10059
10905
|
const text = await readFile20(SETTINGS_PATH, "utf8");
|
|
10060
10906
|
if (text.trim()) {
|
|
10061
10907
|
existing = JSON.parse(text);
|
|
@@ -10067,8 +10913,8 @@ async function persistApprovalMode(mode, ttlMs) {
|
|
|
10067
10913
|
...ttlMs && { ttlMs }
|
|
10068
10914
|
};
|
|
10069
10915
|
existing.approvalMode = entry;
|
|
10070
|
-
const settingsDir =
|
|
10071
|
-
if (!
|
|
10916
|
+
const settingsDir = join26(homedir15(), ".msapling");
|
|
10917
|
+
if (!existsSync22(settingsDir)) {
|
|
10072
10918
|
await mkdir7(settingsDir, { recursive: true });
|
|
10073
10919
|
}
|
|
10074
10920
|
await writeFile12(SETTINGS_PATH, JSON.stringify(existing, null, 2), "utf8");
|
|
@@ -10080,7 +10926,7 @@ var init_mode = __esm({
|
|
|
10080
10926
|
"src/commands/mode.ts"() {
|
|
10081
10927
|
"use strict";
|
|
10082
10928
|
init_esm_shims();
|
|
10083
|
-
SETTINGS_PATH =
|
|
10929
|
+
SETTINGS_PATH = join26(homedir15(), ".msapling", "settings.json");
|
|
10084
10930
|
modeCommand = {
|
|
10085
10931
|
name: "mode",
|
|
10086
10932
|
args: "[default|plan|acceptEdits|bypassPermissions] [...options]",
|
|
@@ -10502,8 +11348,8 @@ var init_compact = __esm({
|
|
|
10502
11348
|
});
|
|
10503
11349
|
|
|
10504
11350
|
// src/commands/init.ts
|
|
10505
|
-
import { join as
|
|
10506
|
-
import { existsSync as
|
|
11351
|
+
import { join as join27 } from "path";
|
|
11352
|
+
import { existsSync as existsSync23 } from "fs";
|
|
10507
11353
|
import { writeFile as writeFile13 } from "fs/promises";
|
|
10508
11354
|
var initCommand;
|
|
10509
11355
|
var init_init = __esm({
|
|
@@ -10517,8 +11363,8 @@ var init_init = __esm({
|
|
|
10517
11363
|
handler: async (args2, context) => {
|
|
10518
11364
|
try {
|
|
10519
11365
|
const cwd = process.cwd();
|
|
10520
|
-
const path2 =
|
|
10521
|
-
if (
|
|
11366
|
+
const path2 = join27(cwd, "MSAPLING.md");
|
|
11367
|
+
if (existsSync23(path2)) {
|
|
10522
11368
|
context.addMessage("error", "MSAPLING.md already exists in current directory.");
|
|
10523
11369
|
return;
|
|
10524
11370
|
}
|
|
@@ -10544,7 +11390,7 @@ var init_init = __esm({
|
|
|
10544
11390
|
});
|
|
10545
11391
|
|
|
10546
11392
|
// src/commands/review.ts
|
|
10547
|
-
import { existsSync as
|
|
11393
|
+
import { existsSync as existsSync24 } from "fs";
|
|
10548
11394
|
import { readFile as readFile21 } from "fs/promises";
|
|
10549
11395
|
var reviewCommand;
|
|
10550
11396
|
var init_review = __esm({
|
|
@@ -10564,7 +11410,7 @@ var init_review = __esm({
|
|
|
10564
11410
|
}
|
|
10565
11411
|
let content = "";
|
|
10566
11412
|
try {
|
|
10567
|
-
if (
|
|
11413
|
+
if (existsSync24(target)) {
|
|
10568
11414
|
content = await readFile21(target, "utf8");
|
|
10569
11415
|
} else {
|
|
10570
11416
|
content = `Review target: ${target}`;
|
|
@@ -10658,15 +11504,15 @@ var init_swarm = __esm({
|
|
|
10658
11504
|
|
|
10659
11505
|
// src/commands/recipe.ts
|
|
10660
11506
|
import { parse as parseYaml } from "yaml";
|
|
10661
|
-
import { existsSync as
|
|
11507
|
+
import { existsSync as existsSync25 } from "fs";
|
|
10662
11508
|
import { readFile as readFile22 } from "fs/promises";
|
|
10663
|
-
import { join as
|
|
11509
|
+
import { join as join28 } from "path";
|
|
10664
11510
|
function findRecipe(name, cwd) {
|
|
10665
11511
|
for (const dir of RECIPE_DIRS) {
|
|
10666
11512
|
for (const suffix of NAME_SUFFIXES) {
|
|
10667
11513
|
for (const ext of FILE_EXTS) {
|
|
10668
|
-
const p =
|
|
10669
|
-
if (
|
|
11514
|
+
const p = join28(cwd, dir, `${name}${suffix}${ext}`);
|
|
11515
|
+
if (existsSync25(p)) return p;
|
|
10670
11516
|
}
|
|
10671
11517
|
}
|
|
10672
11518
|
}
|
|
@@ -10779,13 +11625,13 @@ ${rendered}` : rendered;
|
|
|
10779
11625
|
});
|
|
10780
11626
|
|
|
10781
11627
|
// src/commands/skill.ts
|
|
10782
|
-
import { existsSync as
|
|
11628
|
+
import { existsSync as existsSync26, readdirSync as readdirSync3, statSync as statSync7 } from "fs";
|
|
10783
11629
|
import { readFile as readFile23 } from "fs/promises";
|
|
10784
|
-
import { join as
|
|
11630
|
+
import { join as join29, resolve as resolve16 } from "path";
|
|
10785
11631
|
function findSkillsRoot(cwd) {
|
|
10786
11632
|
for (const candidate of SKILLS_DIRS) {
|
|
10787
|
-
const full =
|
|
10788
|
-
if (
|
|
11633
|
+
const full = resolve16(cwd, candidate);
|
|
11634
|
+
if (existsSync26(full) && statSync7(full).isDirectory()) return full;
|
|
10789
11635
|
}
|
|
10790
11636
|
return null;
|
|
10791
11637
|
}
|
|
@@ -10793,28 +11639,28 @@ function listAllSkills(root) {
|
|
|
10793
11639
|
const out = [];
|
|
10794
11640
|
let domains;
|
|
10795
11641
|
try {
|
|
10796
|
-
domains =
|
|
11642
|
+
domains = readdirSync3(root);
|
|
10797
11643
|
} catch {
|
|
10798
11644
|
return out;
|
|
10799
11645
|
}
|
|
10800
11646
|
for (const domain of domains) {
|
|
10801
|
-
const dir =
|
|
11647
|
+
const dir = join29(root, domain);
|
|
10802
11648
|
let s;
|
|
10803
11649
|
try {
|
|
10804
|
-
s =
|
|
11650
|
+
s = statSync7(dir);
|
|
10805
11651
|
} catch {
|
|
10806
11652
|
continue;
|
|
10807
11653
|
}
|
|
10808
11654
|
if (!s.isDirectory()) continue;
|
|
10809
11655
|
let files;
|
|
10810
11656
|
try {
|
|
10811
|
-
files =
|
|
11657
|
+
files = readdirSync3(dir);
|
|
10812
11658
|
} catch {
|
|
10813
11659
|
continue;
|
|
10814
11660
|
}
|
|
10815
11661
|
for (const f of files) {
|
|
10816
11662
|
if (!f.endsWith(".md")) continue;
|
|
10817
|
-
out.push({ domain, name: f.slice(0, -3), path:
|
|
11663
|
+
out.push({ domain, name: f.slice(0, -3), path: join29(dir, f) });
|
|
10818
11664
|
}
|
|
10819
11665
|
}
|
|
10820
11666
|
return out.sort(
|
|
@@ -10903,9 +11749,9 @@ ${prompt4}`;
|
|
|
10903
11749
|
});
|
|
10904
11750
|
|
|
10905
11751
|
// src/commands/benchmark.ts
|
|
10906
|
-
import { homedir as
|
|
10907
|
-
import { join as
|
|
10908
|
-
import { mkdirSync as
|
|
11752
|
+
import { homedir as homedir16 } from "os";
|
|
11753
|
+
import { join as join30 } from "path";
|
|
11754
|
+
import { mkdirSync as mkdirSync5 } from "fs";
|
|
10909
11755
|
import * as fs2 from "fs";
|
|
10910
11756
|
function parseArgs(args2) {
|
|
10911
11757
|
let models = null;
|
|
@@ -11024,10 +11870,10 @@ HW at start: ${hw.cores}-core ${hw.platform} | CPU ${hw.cpuPct}% | RAM ${hw.ramP
|
|
|
11024
11870
|
`[HW at run time: CPU ${hwAtEnd.cpuPct}% / RAM ${hwAtEnd.ramPct}% | ${hw.ramGiB} GiB RAM, ${hw.cores} cores]`
|
|
11025
11871
|
);
|
|
11026
11872
|
try {
|
|
11027
|
-
const dir =
|
|
11028
|
-
|
|
11873
|
+
const dir = join30(homedir16(), ".msapling", "benchmarks");
|
|
11874
|
+
mkdirSync5(dir, { recursive: true });
|
|
11029
11875
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 16);
|
|
11030
|
-
const file =
|
|
11876
|
+
const file = join30(dir, `${ts}.json`);
|
|
11031
11877
|
const run = {
|
|
11032
11878
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11033
11879
|
rounds,
|
|
@@ -11273,22 +12119,22 @@ var init_theme = __esm({
|
|
|
11273
12119
|
});
|
|
11274
12120
|
|
|
11275
12121
|
// src/commands/theme.ts
|
|
11276
|
-
import { join as
|
|
11277
|
-
import { homedir as
|
|
11278
|
-
import { existsSync as
|
|
12122
|
+
import { join as join31 } from "path";
|
|
12123
|
+
import { homedir as homedir17 } from "os";
|
|
12124
|
+
import { existsSync as existsSync27 } from "fs";
|
|
11279
12125
|
import { readFile as readFile24, writeFile as writeFile14 } from "fs/promises";
|
|
11280
12126
|
async function persistTheme(storage, themeName) {
|
|
11281
|
-
const settingsPath =
|
|
12127
|
+
const settingsPath = join31(homedir17(), ".msapling", "settings.json");
|
|
11282
12128
|
let existing = {};
|
|
11283
12129
|
try {
|
|
11284
|
-
if (
|
|
12130
|
+
if (existsSync27(settingsPath)) {
|
|
11285
12131
|
const text = await readFile24(settingsPath, "utf8");
|
|
11286
12132
|
if (text.trim()) existing = JSON.parse(text);
|
|
11287
12133
|
}
|
|
11288
12134
|
} catch {
|
|
11289
12135
|
}
|
|
11290
12136
|
existing["theme"] = themeName;
|
|
11291
|
-
ensureConfigDir(
|
|
12137
|
+
ensureConfigDir(join31(homedir17(), ".msapling"));
|
|
11292
12138
|
await writeFile14(settingsPath, JSON.stringify(existing, null, 2), "utf8");
|
|
11293
12139
|
}
|
|
11294
12140
|
var VALID_THEMES, themeCommand;
|
|
@@ -11360,7 +12206,7 @@ var init_version = __esm({
|
|
|
11360
12206
|
description: "Show version information for CLI and core packages",
|
|
11361
12207
|
category: "debug",
|
|
11362
12208
|
handler: async (_args, context) => {
|
|
11363
|
-
const cliVersion = true ? "2.3.6-beta.
|
|
12209
|
+
const cliVersion = true ? "2.3.6-beta.48" : "(dev)";
|
|
11364
12210
|
const coreVersion = true ? "2.3.6-beta.43" : "(dev)";
|
|
11365
12211
|
const runtime = process.version;
|
|
11366
12212
|
context.addMessage("system", "MSapling Version Info");
|
|
@@ -11369,7 +12215,7 @@ var init_version = __esm({
|
|
|
11369
12215
|
context.addMessage("system", row2("Core (@msapling/core)", coreVersion));
|
|
11370
12216
|
context.addMessage("system", row2("Runtime (Node/Bun)", runtime));
|
|
11371
12217
|
try {
|
|
11372
|
-
const ts = "2026-06-
|
|
12218
|
+
const ts = "2026-06-20T08:17:10.331Z";
|
|
11373
12219
|
if (ts && ts !== "__BUILD_TIMESTAMP__") {
|
|
11374
12220
|
context.addMessage("system", row2("Build Timestamp", ts));
|
|
11375
12221
|
}
|
|
@@ -11382,14 +12228,14 @@ var init_version = __esm({
|
|
|
11382
12228
|
});
|
|
11383
12229
|
|
|
11384
12230
|
// src/commands/feedback.ts
|
|
11385
|
-
import { join as
|
|
11386
|
-
import { existsSync as
|
|
12231
|
+
import { join as join32 } from "path";
|
|
12232
|
+
import { existsSync as existsSync28 } from "fs";
|
|
11387
12233
|
import { readFile as readFile25 } from "fs/promises";
|
|
11388
12234
|
async function readCliVersion() {
|
|
11389
12235
|
try {
|
|
11390
12236
|
const baseDir = typeof __dirname !== "undefined" ? __dirname : process.cwd();
|
|
11391
|
-
const pkgPath =
|
|
11392
|
-
if (!
|
|
12237
|
+
const pkgPath = join32(baseDir, "..", "..", "package.json");
|
|
12238
|
+
if (!existsSync28(pkgPath)) return "unknown";
|
|
11393
12239
|
const text = await readFile25(pkgPath, "utf8");
|
|
11394
12240
|
const json = JSON.parse(text);
|
|
11395
12241
|
return json.version ?? "unknown";
|
|
@@ -11430,8 +12276,8 @@ var init_feedback = __esm({
|
|
|
11430
12276
|
});
|
|
11431
12277
|
|
|
11432
12278
|
// src/commands/export.ts
|
|
11433
|
-
import { homedir as
|
|
11434
|
-
import { join as
|
|
12279
|
+
import { homedir as homedir18 } from "os";
|
|
12280
|
+
import { join as join33 } from "path";
|
|
11435
12281
|
import { writeFile as writeFile15, mkdir as mkdir8 } from "fs/promises";
|
|
11436
12282
|
function formatTimestamp(date) {
|
|
11437
12283
|
return date.toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, 19);
|
|
@@ -11481,10 +12327,10 @@ var init_export = __esm({
|
|
|
11481
12327
|
let outputPath;
|
|
11482
12328
|
let content;
|
|
11483
12329
|
if (arg === "" || arg === "json") {
|
|
11484
|
-
outputPath =
|
|
12330
|
+
outputPath = join33(homedir18(), `msapling-export-${timestamp}.json`);
|
|
11485
12331
|
content = buildJsonExport(history);
|
|
11486
12332
|
} else if (arg === "markdown" || arg === "md") {
|
|
11487
|
-
outputPath =
|
|
12333
|
+
outputPath = join33(homedir18(), `msapling-export-${timestamp}.md`);
|
|
11488
12334
|
content = buildMarkdownExport(history);
|
|
11489
12335
|
} else {
|
|
11490
12336
|
outputPath = arg;
|
|
@@ -11496,7 +12342,7 @@ var init_export = __esm({
|
|
|
11496
12342
|
}
|
|
11497
12343
|
}
|
|
11498
12344
|
try {
|
|
11499
|
-
const dir =
|
|
12345
|
+
const dir = join33(outputPath, "..");
|
|
11500
12346
|
await mkdir8(dir, { recursive: true });
|
|
11501
12347
|
await writeFile15(outputPath, content, "utf8");
|
|
11502
12348
|
context.addMessage("system", `Exported to: ${outputPath}`);
|
|
@@ -11691,16 +12537,16 @@ var init_plan = __esm({
|
|
|
11691
12537
|
});
|
|
11692
12538
|
|
|
11693
12539
|
// src/commands/note.ts
|
|
11694
|
-
import { homedir as
|
|
11695
|
-
import { join as
|
|
11696
|
-
import { existsSync as
|
|
12540
|
+
import { homedir as homedir19 } from "os";
|
|
12541
|
+
import { join as join34 } from "path";
|
|
12542
|
+
import { existsSync as existsSync29 } from "fs";
|
|
11697
12543
|
import { readFile as readFile26, writeFile as writeFile16 } from "fs/promises";
|
|
11698
12544
|
function getNotesFilePath() {
|
|
11699
|
-
return
|
|
12545
|
+
return join34(homedir19(), ".msapling", "notes.json");
|
|
11700
12546
|
}
|
|
11701
12547
|
async function readNotes(filePath = getNotesFilePath()) {
|
|
11702
12548
|
try {
|
|
11703
|
-
if (!
|
|
12549
|
+
if (!existsSync29(filePath)) return [];
|
|
11704
12550
|
const raw = await readFile26(filePath, "utf8");
|
|
11705
12551
|
const parsed = JSON.parse(raw);
|
|
11706
12552
|
if (!Array.isArray(parsed)) return [];
|
|
@@ -11710,7 +12556,7 @@ async function readNotes(filePath = getNotesFilePath()) {
|
|
|
11710
12556
|
}
|
|
11711
12557
|
}
|
|
11712
12558
|
async function writeNotes(notes, filePath = getNotesFilePath()) {
|
|
11713
|
-
const dir =
|
|
12559
|
+
const dir = join34(homedir19(), ".msapling");
|
|
11714
12560
|
ensureConfigDir(dir);
|
|
11715
12561
|
await writeFile16(filePath, JSON.stringify(notes, null, 2), "utf8");
|
|
11716
12562
|
}
|
|
@@ -11856,17 +12702,17 @@ var init_todo = __esm({
|
|
|
11856
12702
|
});
|
|
11857
12703
|
|
|
11858
12704
|
// src/commands/outputStyle.ts
|
|
11859
|
-
import { homedir as
|
|
11860
|
-
import { join as
|
|
11861
|
-
import { existsSync as
|
|
12705
|
+
import { homedir as homedir20 } from "os";
|
|
12706
|
+
import { join as join35, basename as basename3, extname as extname3 } from "path";
|
|
12707
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync6, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
11862
12708
|
function resolveHome() {
|
|
11863
|
-
return process.env.HOME || process.env.USERPROFILE ||
|
|
12709
|
+
return process.env.HOME || process.env.USERPROFILE || homedir20();
|
|
11864
12710
|
}
|
|
11865
12711
|
function stylesDir() {
|
|
11866
|
-
return
|
|
12712
|
+
return join35(resolveHome(), ".msapling", "output-styles");
|
|
11867
12713
|
}
|
|
11868
12714
|
function activeFile() {
|
|
11869
|
-
return
|
|
12715
|
+
return join35(stylesDir(), ".active");
|
|
11870
12716
|
}
|
|
11871
12717
|
function parseStyleFile(text) {
|
|
11872
12718
|
const fm = text.match(/^---\s*\n([\s\S]*?)\n---\s*\n?/);
|
|
@@ -11887,13 +12733,13 @@ function parseStyleFile(text) {
|
|
|
11887
12733
|
}
|
|
11888
12734
|
function listUserStyles() {
|
|
11889
12735
|
const dir = stylesDir();
|
|
11890
|
-
if (!
|
|
12736
|
+
if (!existsSync30(dir)) return [];
|
|
11891
12737
|
const out = [];
|
|
11892
|
-
for (const entry of
|
|
12738
|
+
for (const entry of readdirSync4(dir)) {
|
|
11893
12739
|
if (extname3(entry).toLowerCase() !== ".md") continue;
|
|
11894
|
-
const full =
|
|
12740
|
+
const full = join35(dir, entry);
|
|
11895
12741
|
try {
|
|
11896
|
-
const text =
|
|
12742
|
+
const text = readFileSync4(full, "utf8");
|
|
11897
12743
|
const { description, body } = parseStyleFile(text);
|
|
11898
12744
|
out.push({
|
|
11899
12745
|
name: basename3(entry, ".md"),
|
|
@@ -11919,15 +12765,15 @@ function findStyle(name) {
|
|
|
11919
12765
|
function getActiveStyleName() {
|
|
11920
12766
|
try {
|
|
11921
12767
|
const f = activeFile();
|
|
11922
|
-
if (!
|
|
11923
|
-
return
|
|
12768
|
+
if (!existsSync30(f)) return "default";
|
|
12769
|
+
return readFileSync4(f, "utf8").trim() || "default";
|
|
11924
12770
|
} catch {
|
|
11925
12771
|
return "default";
|
|
11926
12772
|
}
|
|
11927
12773
|
}
|
|
11928
12774
|
function setActiveStyleName(name) {
|
|
11929
12775
|
const dir = stylesDir();
|
|
11930
|
-
if (!
|
|
12776
|
+
if (!existsSync30(dir)) mkdirSync6(dir, { recursive: true });
|
|
11931
12777
|
writeFileSync5(activeFile(), `${name}
|
|
11932
12778
|
`, "utf8");
|
|
11933
12779
|
}
|
|
@@ -11940,8 +12786,8 @@ function createUserStyle(name, description, body) {
|
|
|
11940
12786
|
throw new Error(`Invalid style name "${name}" \u2014 use letters, digits, _ and - only.`);
|
|
11941
12787
|
}
|
|
11942
12788
|
const dir = stylesDir();
|
|
11943
|
-
if (!
|
|
11944
|
-
const target =
|
|
12789
|
+
if (!existsSync30(dir)) mkdirSync6(dir, { recursive: true });
|
|
12790
|
+
const target = join35(dir, `${name}.md`);
|
|
11945
12791
|
const frontmatter = `---
|
|
11946
12792
|
description: ${description.replace(/\n/g, " ")}
|
|
11947
12793
|
---
|
|
@@ -13105,6 +13951,259 @@ Note: server may return a [server notice] caveat for v1-scaffold features.`
|
|
|
13105
13951
|
}
|
|
13106
13952
|
});
|
|
13107
13953
|
|
|
13954
|
+
// src/commands/rewind.ts
|
|
13955
|
+
var rewindCommand;
|
|
13956
|
+
var init_rewind = __esm({
|
|
13957
|
+
"src/commands/rewind.ts"() {
|
|
13958
|
+
"use strict";
|
|
13959
|
+
init_esm_shims();
|
|
13960
|
+
init_src3();
|
|
13961
|
+
rewindCommand = {
|
|
13962
|
+
name: "rewind",
|
|
13963
|
+
aliases: ["checkpoint", "restore"],
|
|
13964
|
+
args: "[n] [--confirm]",
|
|
13965
|
+
description: "List file-edit checkpoints or restore files to a prior snapshot. Usage: /rewind (list) \xB7 /rewind <n> (preview) \xB7 /rewind <n> --confirm (restore).",
|
|
13966
|
+
category: "chat",
|
|
13967
|
+
handler(args2, context) {
|
|
13968
|
+
const checkpoints = listCheckpoints();
|
|
13969
|
+
if (args2.length === 0) {
|
|
13970
|
+
if (checkpoints.length === 0) {
|
|
13971
|
+
context.addMessage(
|
|
13972
|
+
"system",
|
|
13973
|
+
"No checkpoints found. File edits made by the agent will be listed here."
|
|
13974
|
+
);
|
|
13975
|
+
return;
|
|
13976
|
+
}
|
|
13977
|
+
const lines = [
|
|
13978
|
+
`${checkpoints.length} checkpoint(s) \u2014 most recent first:`,
|
|
13979
|
+
""
|
|
13980
|
+
];
|
|
13981
|
+
checkpoints.forEach((cp, idx) => {
|
|
13982
|
+
const num = idx + 1;
|
|
13983
|
+
const fileCount = cp.backups.length;
|
|
13984
|
+
const when = new Date(cp.timestamp).toLocaleString();
|
|
13985
|
+
lines.push(
|
|
13986
|
+
` ${num}. [${when}] ${cp.label} (${fileCount} file${fileCount !== 1 ? "s" : ""})`
|
|
13987
|
+
);
|
|
13988
|
+
});
|
|
13989
|
+
lines.push("");
|
|
13990
|
+
lines.push("To preview a restore: /rewind <n>");
|
|
13991
|
+
lines.push("To restore: /rewind <n> --confirm");
|
|
13992
|
+
context.addMessage("system", lines.join("\n"));
|
|
13993
|
+
return;
|
|
13994
|
+
}
|
|
13995
|
+
const positional = args2.filter((a) => !a.startsWith("-"));
|
|
13996
|
+
const hasConfirm = args2.includes("--confirm") || args2.includes("-y") || args2.includes("--yes");
|
|
13997
|
+
if (positional.length === 0) {
|
|
13998
|
+
context.addMessage("system", "Usage: /rewind <n> [--confirm]");
|
|
13999
|
+
return;
|
|
14000
|
+
}
|
|
14001
|
+
const rawN = positional[0];
|
|
14002
|
+
const n = parseInt(rawN, 10);
|
|
14003
|
+
if (isNaN(n) || n < 1) {
|
|
14004
|
+
context.addMessage(
|
|
14005
|
+
"error",
|
|
14006
|
+
`Invalid checkpoint number: "${rawN}". Run /rewind to list available checkpoints.`
|
|
14007
|
+
);
|
|
14008
|
+
return;
|
|
14009
|
+
}
|
|
14010
|
+
if (checkpoints.length === 0) {
|
|
14011
|
+
context.addMessage("system", "No checkpoints available to restore.");
|
|
14012
|
+
return;
|
|
14013
|
+
}
|
|
14014
|
+
if (n > checkpoints.length) {
|
|
14015
|
+
context.addMessage(
|
|
14016
|
+
"error",
|
|
14017
|
+
`Checkpoint #${n} does not exist. ${checkpoints.length} checkpoint(s) available. Run /rewind to list them.`
|
|
14018
|
+
);
|
|
14019
|
+
return;
|
|
14020
|
+
}
|
|
14021
|
+
const target = checkpoints[n - 1];
|
|
14022
|
+
if (!hasConfirm) {
|
|
14023
|
+
const when = new Date(target.timestamp).toLocaleString();
|
|
14024
|
+
const lines = [
|
|
14025
|
+
`Checkpoint #${n}: ${target.label} [${when}]`,
|
|
14026
|
+
""
|
|
14027
|
+
];
|
|
14028
|
+
if (target.backups.length === 0) {
|
|
14029
|
+
lines.push(" (no file edits recorded in this checkpoint)");
|
|
14030
|
+
} else {
|
|
14031
|
+
lines.push("Files that will be reverted:");
|
|
14032
|
+
for (const rec of target.backups) {
|
|
14033
|
+
lines.push(` - ${rec.originalPath}`);
|
|
14034
|
+
}
|
|
14035
|
+
}
|
|
14036
|
+
lines.push("");
|
|
14037
|
+
lines.push(`To restore these files, run: /rewind ${n} --confirm`);
|
|
14038
|
+
lines.push(
|
|
14039
|
+
"Note: only file contents are restored (chat history is NOT truncated)."
|
|
14040
|
+
);
|
|
14041
|
+
context.addMessage("system", lines.join("\n"));
|
|
14042
|
+
return;
|
|
14043
|
+
}
|
|
14044
|
+
if (target.backups.length === 0) {
|
|
14045
|
+
context.addMessage(
|
|
14046
|
+
"system",
|
|
14047
|
+
`Checkpoint #${n} has no file edits \u2014 nothing to restore.`
|
|
14048
|
+
);
|
|
14049
|
+
return;
|
|
14050
|
+
}
|
|
14051
|
+
const results = restoreCheckpoint(target);
|
|
14052
|
+
const restored = results.filter((r) => r.startsWith("Restored:"));
|
|
14053
|
+
const skipped = results.filter((r) => r.startsWith("Skip"));
|
|
14054
|
+
const errors = results.filter((r) => r.startsWith("Error"));
|
|
14055
|
+
const summary = [
|
|
14056
|
+
`Rewind to checkpoint #${n} complete.`,
|
|
14057
|
+
"",
|
|
14058
|
+
...results
|
|
14059
|
+
];
|
|
14060
|
+
if (errors.length > 0) {
|
|
14061
|
+
summary.push("");
|
|
14062
|
+
summary.push(
|
|
14063
|
+
`${errors.length} error(s) occurred. Check that the backup files in ~/.msapling/backups/ are accessible.`
|
|
14064
|
+
);
|
|
14065
|
+
}
|
|
14066
|
+
if (restored.length > 0 && errors.length === 0) {
|
|
14067
|
+
summary.push("");
|
|
14068
|
+
summary.push(
|
|
14069
|
+
`${restored.length} file(s) restored successfully. Note: chat history was NOT truncated (follow-up: REWIND-FOLLOW-UP-01).`
|
|
14070
|
+
);
|
|
14071
|
+
}
|
|
14072
|
+
context.addMessage("system", summary.join("\n"));
|
|
14073
|
+
}
|
|
14074
|
+
};
|
|
14075
|
+
}
|
|
14076
|
+
});
|
|
14077
|
+
|
|
14078
|
+
// src/commands/bashes.ts
|
|
14079
|
+
var bashesCommand;
|
|
14080
|
+
var init_bashes = __esm({
|
|
14081
|
+
"src/commands/bashes.ts"() {
|
|
14082
|
+
"use strict";
|
|
14083
|
+
init_esm_shims();
|
|
14084
|
+
init_src3();
|
|
14085
|
+
bashesCommand = {
|
|
14086
|
+
name: "bashes",
|
|
14087
|
+
aliases: ["bg", "background"],
|
|
14088
|
+
args: "[read <id> [n] | kill <id|all> | <id>]",
|
|
14089
|
+
description: "List, inspect, or kill background shells",
|
|
14090
|
+
category: "debug",
|
|
14091
|
+
handler(args2, context) {
|
|
14092
|
+
const sub = (args2[0] ?? "").toLowerCase();
|
|
14093
|
+
if (sub === "kill") {
|
|
14094
|
+
const target = args2[1] ?? "";
|
|
14095
|
+
if (!target) {
|
|
14096
|
+
context.addMessage("error", "Usage: /bashes kill <id|all>");
|
|
14097
|
+
return;
|
|
14098
|
+
}
|
|
14099
|
+
if (target.toLowerCase() === "all") {
|
|
14100
|
+
const running = BackgroundShellRegistry.list().filter((s) => s.status === "running");
|
|
14101
|
+
BackgroundShellRegistry.killAll();
|
|
14102
|
+
context.addMessage("system", `Killed ${running.length} running background shell(s).`);
|
|
14103
|
+
return;
|
|
14104
|
+
}
|
|
14105
|
+
const result = BackgroundShellRegistry.kill(target);
|
|
14106
|
+
if (result === "killed") context.addMessage("system", `Killed background shell ${target}.`);
|
|
14107
|
+
else if (result === "already")
|
|
14108
|
+
context.addMessage("system", `Background shell ${target} had already finished.`);
|
|
14109
|
+
else context.addMessage("error", `No background shell with id "${target}".`);
|
|
14110
|
+
return;
|
|
14111
|
+
}
|
|
14112
|
+
const explicitRead = sub === "read";
|
|
14113
|
+
const id = explicitRead ? args2[1] : args2[0];
|
|
14114
|
+
if (id) {
|
|
14115
|
+
const lineArg = explicitRead ? args2[2] : args2[1];
|
|
14116
|
+
const lines = lineArg && /^\d+$/.test(lineArg) ? parseInt(lineArg, 10) : void 0;
|
|
14117
|
+
const read = BackgroundShellRegistry.readOutput(id, lines);
|
|
14118
|
+
const info = BackgroundShellRegistry.get(id);
|
|
14119
|
+
if (!read || !info) {
|
|
14120
|
+
context.addMessage("error", `No background shell with id "${id}".`);
|
|
14121
|
+
return;
|
|
14122
|
+
}
|
|
14123
|
+
const header = `[${info.id}] ${info.status.toUpperCase()}` + (info.exitCode !== void 0 && info.exitCode !== null ? ` exit=${info.exitCode}` : "") + ` $ ${info.command}
|
|
14124
|
+
`;
|
|
14125
|
+
const body = read.output.trim() === "" ? "(no output yet)" : read.output;
|
|
14126
|
+
const trunc = read.truncated ? "\n... [output buffer truncated to most recent 256 KB]" : "";
|
|
14127
|
+
context.addMessage("system", header + body + trunc);
|
|
14128
|
+
return;
|
|
14129
|
+
}
|
|
14130
|
+
context.addMessage("system", formatBackgroundShells(BackgroundShellRegistry.list()));
|
|
14131
|
+
}
|
|
14132
|
+
};
|
|
14133
|
+
}
|
|
14134
|
+
});
|
|
14135
|
+
|
|
14136
|
+
// src/commands/agents.ts
|
|
14137
|
+
import { homedir as homedir21 } from "os";
|
|
14138
|
+
function resolveHome2() {
|
|
14139
|
+
return process.env.HOME || process.env.USERPROFILE || homedir21();
|
|
14140
|
+
}
|
|
14141
|
+
function describeTools(agent) {
|
|
14142
|
+
if (agent.tools === void 0) return "all (inherits full tool set)";
|
|
14143
|
+
if (agent.tools.length === 0) return "none (read/reason only)";
|
|
14144
|
+
return agent.tools.join(", ");
|
|
14145
|
+
}
|
|
14146
|
+
var agentsCommand;
|
|
14147
|
+
var init_agents = __esm({
|
|
14148
|
+
"src/commands/agents.ts"() {
|
|
14149
|
+
"use strict";
|
|
14150
|
+
init_esm_shims();
|
|
14151
|
+
init_src3();
|
|
14152
|
+
agentsCommand = {
|
|
14153
|
+
name: "agents",
|
|
14154
|
+
aliases: ["agent"],
|
|
14155
|
+
args: "[name]",
|
|
14156
|
+
description: "List named subagents (.msapling/agents/*.md), or show one. Named agents have their own system prompt, model, and tool allowlist.",
|
|
14157
|
+
category: "swarm",
|
|
14158
|
+
handler: (args2, context) => {
|
|
14159
|
+
const cwd = process.cwd();
|
|
14160
|
+
const home = resolveHome2();
|
|
14161
|
+
if (args2.length >= 1 && args2[0] !== "list" && args2[0] !== "ls") {
|
|
14162
|
+
const ref = args2[0];
|
|
14163
|
+
const agent = findNamedAgent(ref, cwd, home);
|
|
14164
|
+
if (!agent) {
|
|
14165
|
+
context.addMessage(
|
|
14166
|
+
"error",
|
|
14167
|
+
`No named agent "${ref}". Run /agents (no args) to list available agents.`
|
|
14168
|
+
);
|
|
14169
|
+
return;
|
|
14170
|
+
}
|
|
14171
|
+
context.addMessage("system", `Agent: ${agent.name} [${agent.scope}]`);
|
|
14172
|
+
context.addMessage("system", ` description: ${agent.description}`);
|
|
14173
|
+
context.addMessage("system", ` model: ${agent.model ?? "(inherit session model)"}`);
|
|
14174
|
+
context.addMessage("system", ` tools: ${describeTools(agent)}`);
|
|
14175
|
+
context.addMessage("system", ` source: ${agent.path}`);
|
|
14176
|
+
context.addMessage("system", " \u2500\u2500\u2500 system prompt \u2500\u2500\u2500");
|
|
14177
|
+
context.addMessage("system", agent.systemPrompt || "(empty)");
|
|
14178
|
+
context.addMessage(
|
|
14179
|
+
"system",
|
|
14180
|
+
`Run it: the model can call dispatch_agent with agent="${agent.name}".`
|
|
14181
|
+
);
|
|
14182
|
+
return;
|
|
14183
|
+
}
|
|
14184
|
+
const agents = loadNamedAgents(cwd, home);
|
|
14185
|
+
if (agents.length === 0) {
|
|
14186
|
+
context.addMessage(
|
|
14187
|
+
"system",
|
|
14188
|
+
"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."
|
|
14189
|
+
);
|
|
14190
|
+
return;
|
|
14191
|
+
}
|
|
14192
|
+
context.addMessage("system", `Named subagents (${agents.length}):`);
|
|
14193
|
+
for (const a of agents) {
|
|
14194
|
+
const model = a.model ? ` model=${a.model}` : "";
|
|
14195
|
+
const toolCount = a.tools === void 0 ? "tools=all" : `tools=${a.tools.length}`;
|
|
14196
|
+
context.addMessage(
|
|
14197
|
+
"system",
|
|
14198
|
+
` ${a.name.padEnd(20)} [${a.scope}] ${toolCount}${model} \u2014 ${a.description}`
|
|
14199
|
+
);
|
|
14200
|
+
}
|
|
14201
|
+
context.addMessage("system", "Run /agents <name> to see an agent in full.");
|
|
14202
|
+
}
|
|
14203
|
+
};
|
|
14204
|
+
}
|
|
14205
|
+
});
|
|
14206
|
+
|
|
13108
14207
|
// src/commands/index.ts
|
|
13109
14208
|
var commands_exports = {};
|
|
13110
14209
|
__export(commands_exports, {
|
|
@@ -13176,6 +14275,9 @@ var init_commands = __esm({
|
|
|
13176
14275
|
init_diag();
|
|
13177
14276
|
init_parallel();
|
|
13178
14277
|
init_remoteAgent();
|
|
14278
|
+
init_rewind();
|
|
14279
|
+
init_bashes();
|
|
14280
|
+
init_agents();
|
|
13179
14281
|
commands = [
|
|
13180
14282
|
loginCommand,
|
|
13181
14283
|
logoutCommand,
|
|
@@ -13239,7 +14341,10 @@ var init_commands = __esm({
|
|
|
13239
14341
|
syncCommand,
|
|
13240
14342
|
diagCommand,
|
|
13241
14343
|
parallelCommand,
|
|
13242
|
-
remoteAgentCommand
|
|
14344
|
+
remoteAgentCommand,
|
|
14345
|
+
rewindCommand,
|
|
14346
|
+
bashesCommand,
|
|
14347
|
+
agentsCommand
|
|
13243
14348
|
];
|
|
13244
14349
|
}
|
|
13245
14350
|
});
|
|
@@ -13309,15 +14414,15 @@ var exec_exports = {};
|
|
|
13309
14414
|
__export(exec_exports, {
|
|
13310
14415
|
runExec: () => runExec
|
|
13311
14416
|
});
|
|
13312
|
-
import { existsSync as
|
|
14417
|
+
import { existsSync as existsSync33 } from "fs";
|
|
13313
14418
|
import { readFile as readFile29 } from "fs/promises";
|
|
13314
|
-
import { homedir as
|
|
13315
|
-
import { join as
|
|
14419
|
+
import { homedir as homedir23 } from "os";
|
|
14420
|
+
import { join as join37 } from "path";
|
|
13316
14421
|
async function loadPersistedSettings() {
|
|
13317
14422
|
const out = { mode: "default", theme: null };
|
|
13318
14423
|
try {
|
|
13319
|
-
const p =
|
|
13320
|
-
if (!
|
|
14424
|
+
const p = join37(homedir23(), ".msapling", "settings.json");
|
|
14425
|
+
if (!existsSync33(p)) return out;
|
|
13321
14426
|
const raw = JSON.parse(await readFile29(p, "utf8"));
|
|
13322
14427
|
const parsed = parseApprovalMode(raw, Date.now());
|
|
13323
14428
|
if (parsed.kind === "ok") out.mode = parsed.mode;
|
|
@@ -13553,7 +14658,7 @@ var init_format = __esm({
|
|
|
13553
14658
|
});
|
|
13554
14659
|
|
|
13555
14660
|
// src/commands/billing/open-browser.ts
|
|
13556
|
-
import { spawn as
|
|
14661
|
+
import { spawn as spawn11 } from "child_process";
|
|
13557
14662
|
async function openBrowser(url) {
|
|
13558
14663
|
const platform5 = process.platform;
|
|
13559
14664
|
let cmd;
|
|
@@ -13568,13 +14673,13 @@ async function openBrowser(url) {
|
|
|
13568
14673
|
cmd = "xdg-open";
|
|
13569
14674
|
args2 = [url];
|
|
13570
14675
|
}
|
|
13571
|
-
return new Promise((
|
|
14676
|
+
return new Promise((resolve21) => {
|
|
13572
14677
|
try {
|
|
13573
|
-
const child =
|
|
14678
|
+
const child = spawn11(cmd, args2, { stdio: "ignore", detached: true });
|
|
13574
14679
|
child.unref();
|
|
13575
14680
|
} catch {
|
|
13576
14681
|
}
|
|
13577
|
-
|
|
14682
|
+
resolve21();
|
|
13578
14683
|
});
|
|
13579
14684
|
}
|
|
13580
14685
|
var init_open_browser = __esm({
|
|
@@ -13664,7 +14769,7 @@ var init_checkout = __esm({
|
|
|
13664
14769
|
// src/commands/billing/sub.ts
|
|
13665
14770
|
import * as readline from "readline";
|
|
13666
14771
|
function prompt(rl, question) {
|
|
13667
|
-
return new Promise((
|
|
14772
|
+
return new Promise((resolve21) => rl.question(question, resolve21));
|
|
13668
14773
|
}
|
|
13669
14774
|
async function runSub(argv) {
|
|
13670
14775
|
const subCmd = argv[0] ?? "";
|
|
@@ -13790,11 +14895,11 @@ var init_sub = __esm({
|
|
|
13790
14895
|
// src/commands/billing/topup.ts
|
|
13791
14896
|
import * as readline2 from "readline";
|
|
13792
14897
|
function prompt2(rl, question) {
|
|
13793
|
-
return new Promise((
|
|
14898
|
+
return new Promise((resolve21) => rl.question(question, resolve21));
|
|
13794
14899
|
}
|
|
13795
14900
|
function promptDefault(rl, question, defaultVal) {
|
|
13796
14901
|
return new Promise(
|
|
13797
|
-
(
|
|
14902
|
+
(resolve21) => rl.question(`${question} [${defaultVal}]: `, (ans) => resolve21(ans.trim() || defaultVal))
|
|
13798
14903
|
);
|
|
13799
14904
|
}
|
|
13800
14905
|
async function runTopup(argv) {
|
|
@@ -13929,7 +15034,7 @@ var init_redeem = __esm({
|
|
|
13929
15034
|
// src/commands/billing/gift.ts
|
|
13930
15035
|
import * as readline3 from "readline";
|
|
13931
15036
|
function prompt3(rl, question) {
|
|
13932
|
-
return new Promise((
|
|
15037
|
+
return new Promise((resolve21) => rl.question(question, resolve21));
|
|
13933
15038
|
}
|
|
13934
15039
|
async function runGift(argv) {
|
|
13935
15040
|
const subCmd = argv[0] ?? "";
|
|
@@ -14175,11 +15280,11 @@ var doctor_exports = {};
|
|
|
14175
15280
|
__export(doctor_exports, {
|
|
14176
15281
|
runDoctor: () => runDoctor
|
|
14177
15282
|
});
|
|
14178
|
-
import { homedir as
|
|
14179
|
-
import { join as
|
|
14180
|
-
import { existsSync as
|
|
15283
|
+
import { homedir as homedir24, platform as platform4, tmpdir } from "os";
|
|
15284
|
+
import { join as join38 } from "path";
|
|
15285
|
+
import { existsSync as existsSync34, statSync as statSync8 } from "fs";
|
|
14181
15286
|
import { readdir as readdir3, mkdir as mkdir9, rm as rm3 } from "fs/promises";
|
|
14182
|
-
import { exec } from "child_process";
|
|
15287
|
+
import { exec as exec2 } from "child_process";
|
|
14183
15288
|
import { promisify } from "util";
|
|
14184
15289
|
async function checkNodeVersion() {
|
|
14185
15290
|
const version = process.version;
|
|
@@ -14200,8 +15305,8 @@ async function checkNodeVersion() {
|
|
|
14200
15305
|
};
|
|
14201
15306
|
}
|
|
14202
15307
|
async function checkConfigDir() {
|
|
14203
|
-
const configDir =
|
|
14204
|
-
if (!
|
|
15308
|
+
const configDir = join38(homedir24(), ".msapling");
|
|
15309
|
+
if (!existsSync34(configDir)) {
|
|
14205
15310
|
return {
|
|
14206
15311
|
name: "Config directory",
|
|
14207
15312
|
status: "WARN",
|
|
@@ -14209,7 +15314,7 @@ async function checkConfigDir() {
|
|
|
14209
15314
|
remediation: `mkdir -p "${configDir}" && chmod 700 "${configDir}"`
|
|
14210
15315
|
};
|
|
14211
15316
|
}
|
|
14212
|
-
const stats =
|
|
15317
|
+
const stats = statSync8(configDir);
|
|
14213
15318
|
if (!stats.isDirectory()) {
|
|
14214
15319
|
return {
|
|
14215
15320
|
name: "Config directory",
|
|
@@ -14270,7 +15375,7 @@ async function checkPathConflicts() {
|
|
|
14270
15375
|
const timedOutDirs = [];
|
|
14271
15376
|
const DIR_TIMEOUT_MS = 1500;
|
|
14272
15377
|
for (const dir of paths) {
|
|
14273
|
-
if (!dir || !
|
|
15378
|
+
if (!dir || !existsSync34(dir)) continue;
|
|
14274
15379
|
try {
|
|
14275
15380
|
const files = await Promise.race([
|
|
14276
15381
|
readdir3(dir),
|
|
@@ -14283,7 +15388,7 @@ async function checkPathConflicts() {
|
|
|
14283
15388
|
]);
|
|
14284
15389
|
for (const file of files) {
|
|
14285
15390
|
if (file === "msapling" || file === "msapling.exe" || file === "msapling.py") {
|
|
14286
|
-
const fullPath =
|
|
15391
|
+
const fullPath = join38(dir, file);
|
|
14287
15392
|
conflicts.push(fullPath);
|
|
14288
15393
|
}
|
|
14289
15394
|
}
|
|
@@ -14401,9 +15506,9 @@ async function checkTokenValidity() {
|
|
|
14401
15506
|
}
|
|
14402
15507
|
async function checkOsSpecific() {
|
|
14403
15508
|
if (platform4() === "win32") {
|
|
14404
|
-
const testDir =
|
|
15509
|
+
const testDir = join38(tmpdir(), `msapling-longpath-test-${Date.now()}`);
|
|
14405
15510
|
const longDirName = "A".repeat(260);
|
|
14406
|
-
const testPath =
|
|
15511
|
+
const testPath = join38(testDir, longDirName);
|
|
14407
15512
|
try {
|
|
14408
15513
|
await mkdir9(testDir, { recursive: true });
|
|
14409
15514
|
try {
|
|
@@ -14557,7 +15662,7 @@ var init_doctor2 = __esm({
|
|
|
14557
15662
|
"use strict";
|
|
14558
15663
|
init_esm_shims();
|
|
14559
15664
|
init_doctorRedact();
|
|
14560
|
-
execAsync = promisify(
|
|
15665
|
+
execAsync = promisify(exec2);
|
|
14561
15666
|
}
|
|
14562
15667
|
});
|
|
14563
15668
|
|
|
@@ -14603,21 +15708,21 @@ var init_registry_merger = __esm({
|
|
|
14603
15708
|
});
|
|
14604
15709
|
|
|
14605
15710
|
// ../core/src/mcp/local_tools.ts
|
|
14606
|
-
import { spawn as
|
|
15711
|
+
import { spawn as spawn12 } from "child_process";
|
|
14607
15712
|
import { readdir as readdir4, stat as stat4, realpath as realpath3 } from "fs/promises";
|
|
14608
|
-
import { resolve as
|
|
15713
|
+
import { resolve as resolve18 } from "path";
|
|
14609
15714
|
function asResult(text, isError = false) {
|
|
14610
15715
|
return { content: [{ type: "text", text }], ...isError ? { isError: true } : {} };
|
|
14611
15716
|
}
|
|
14612
15717
|
async function runCommand(command, cwd) {
|
|
14613
|
-
return new Promise((
|
|
15718
|
+
return new Promise((resolve21) => {
|
|
14614
15719
|
let p;
|
|
14615
15720
|
const timeout = setTimeout(() => {
|
|
14616
15721
|
if (p) p.kill();
|
|
14617
|
-
|
|
15722
|
+
resolve21({ stdout: "", stderr: "Command timed out after 30s", exit_code: -1 });
|
|
14618
15723
|
}, 3e4);
|
|
14619
15724
|
try {
|
|
14620
|
-
p =
|
|
15725
|
+
p = spawn12("sh", ["-c", command], {
|
|
14621
15726
|
cwd: cwd || process.cwd(),
|
|
14622
15727
|
stdio: ["ignore", "pipe", "pipe"],
|
|
14623
15728
|
timeout: 3e4
|
|
@@ -14632,15 +15737,15 @@ async function runCommand(command, cwd) {
|
|
|
14632
15737
|
});
|
|
14633
15738
|
p.on("error", (e) => {
|
|
14634
15739
|
clearTimeout(timeout);
|
|
14635
|
-
|
|
15740
|
+
resolve21({ stdout, stderr: stderr + (e?.message ?? ""), exit_code: -1 });
|
|
14636
15741
|
});
|
|
14637
15742
|
p.on("exit", (code) => {
|
|
14638
15743
|
clearTimeout(timeout);
|
|
14639
|
-
|
|
15744
|
+
resolve21({ stdout, stderr, exit_code: code });
|
|
14640
15745
|
});
|
|
14641
15746
|
} catch (e) {
|
|
14642
15747
|
clearTimeout(timeout);
|
|
14643
|
-
|
|
15748
|
+
resolve21({
|
|
14644
15749
|
stdout: "",
|
|
14645
15750
|
stderr: e?.message ?? "Failed to spawn process",
|
|
14646
15751
|
exit_code: -1
|
|
@@ -14707,7 +15812,7 @@ async function callLocalTool(name, args2, projectRoot) {
|
|
|
14707
15812
|
const command = String(args2.command ?? "");
|
|
14708
15813
|
let cwd = projectRoot;
|
|
14709
15814
|
if (args2.cwd) {
|
|
14710
|
-
cwd =
|
|
15815
|
+
cwd = resolve18(projectRoot, String(args2.cwd));
|
|
14711
15816
|
try {
|
|
14712
15817
|
const resolvedCwd = await realpath3(cwd);
|
|
14713
15818
|
const resolvedRoot = await realpath3(projectRoot);
|
|
@@ -14744,7 +15849,7 @@ ${res.stderr}`
|
|
|
14744
15849
|
return asResult("path is required", true);
|
|
14745
15850
|
}
|
|
14746
15851
|
try {
|
|
14747
|
-
const resolvedPath = await realpath3(
|
|
15852
|
+
const resolvedPath = await realpath3(resolve18(projectRoot, pathArg));
|
|
14748
15853
|
const resolvedRoot = await realpath3(projectRoot);
|
|
14749
15854
|
if (!resolvedPath.startsWith(resolvedRoot)) {
|
|
14750
15855
|
return asResult("Error: path attempts to escape project root", true);
|
|
@@ -14763,7 +15868,7 @@ ${res.stderr}`
|
|
|
14763
15868
|
}
|
|
14764
15869
|
case "local_glob": {
|
|
14765
15870
|
const pattern = String(args2.pattern ?? "");
|
|
14766
|
-
let cwd = args2.cwd ?
|
|
15871
|
+
let cwd = args2.cwd ? resolve18(projectRoot, String(args2.cwd)) : projectRoot;
|
|
14767
15872
|
if (!pattern) {
|
|
14768
15873
|
return asResult("pattern is required", true);
|
|
14769
15874
|
}
|
|
@@ -14792,7 +15897,7 @@ ${res.stderr}`
|
|
|
14792
15897
|
}
|
|
14793
15898
|
if (path2) {
|
|
14794
15899
|
try {
|
|
14795
|
-
const resolvedPath = await realpath3(
|
|
15900
|
+
const resolvedPath = await realpath3(resolve18(projectRoot, path2));
|
|
14796
15901
|
const resolvedRoot = await realpath3(projectRoot);
|
|
14797
15902
|
if (!resolvedPath.startsWith(resolvedRoot)) {
|
|
14798
15903
|
return asResult("Error: path attempts to escape project root", true);
|
|
@@ -14812,7 +15917,7 @@ ${res.stderr}`
|
|
|
14812
15917
|
return asResult("cwd is required", true);
|
|
14813
15918
|
}
|
|
14814
15919
|
try {
|
|
14815
|
-
const resolvedCwd = await realpath3(
|
|
15920
|
+
const resolvedCwd = await realpath3(resolve18(projectRoot, cwdArg));
|
|
14816
15921
|
const resolvedRoot = await realpath3(projectRoot);
|
|
14817
15922
|
if (!resolvedCwd.startsWith(resolvedRoot)) {
|
|
14818
15923
|
return asResult("Error: cwd attempts to escape project root", true);
|
|
@@ -14838,7 +15943,7 @@ ${status2.porcelain || "(clean)"}`
|
|
|
14838
15943
|
return asResult("cwd is required", true);
|
|
14839
15944
|
}
|
|
14840
15945
|
try {
|
|
14841
|
-
const resolvedCwd = await realpath3(
|
|
15946
|
+
const resolvedCwd = await realpath3(resolve18(projectRoot, cwdArg));
|
|
14842
15947
|
const resolvedRoot = await realpath3(projectRoot);
|
|
14843
15948
|
if (!resolvedCwd.startsWith(resolvedRoot)) {
|
|
14844
15949
|
return asResult("Error: cwd attempts to escape project root", true);
|
|
@@ -15032,13 +16137,13 @@ var init_base = __esm({
|
|
|
15032
16137
|
editLength++;
|
|
15033
16138
|
};
|
|
15034
16139
|
if (callback) {
|
|
15035
|
-
(function
|
|
16140
|
+
(function exec3() {
|
|
15036
16141
|
setTimeout(function() {
|
|
15037
16142
|
if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {
|
|
15038
16143
|
return callback(void 0);
|
|
15039
16144
|
}
|
|
15040
16145
|
if (!execEditLength()) {
|
|
15041
|
-
|
|
16146
|
+
exec3();
|
|
15042
16147
|
}
|
|
15043
16148
|
}, 0);
|
|
15044
16149
|
})();
|
|
@@ -16080,8 +17185,8 @@ var init_libesm = __esm({
|
|
|
16080
17185
|
});
|
|
16081
17186
|
|
|
16082
17187
|
// ../core/src/mcp/catalog.ts
|
|
16083
|
-
import { readdirSync as
|
|
16084
|
-
import { join as
|
|
17188
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync5, statSync as statSync9 } from "fs";
|
|
17189
|
+
import { join as join39, relative as relative16 } from "path";
|
|
16085
17190
|
function buildFileTree(root, maxFiles) {
|
|
16086
17191
|
const SKIP_DIRS2 = /* @__PURE__ */ new Set(["node_modules", ".git", "build", "dist", ".venv", "venv", ".next", "__pycache__", ".dart_tool", ".bun", "target"]);
|
|
16087
17192
|
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 +17196,17 @@ function buildFileTree(root, maxFiles) {
|
|
|
16091
17196
|
const dir = queue.shift();
|
|
16092
17197
|
let entries;
|
|
16093
17198
|
try {
|
|
16094
|
-
entries =
|
|
17199
|
+
entries = readdirSync5(dir);
|
|
16095
17200
|
} catch {
|
|
16096
17201
|
continue;
|
|
16097
17202
|
}
|
|
16098
17203
|
for (const name of entries) {
|
|
16099
17204
|
if (out.length >= maxFiles) break;
|
|
16100
17205
|
if (SKIP_DIRS2.has(name)) continue;
|
|
16101
|
-
const full =
|
|
17206
|
+
const full = join39(dir, name);
|
|
16102
17207
|
let s;
|
|
16103
17208
|
try {
|
|
16104
|
-
s =
|
|
17209
|
+
s = statSync9(full);
|
|
16105
17210
|
} catch {
|
|
16106
17211
|
continue;
|
|
16107
17212
|
}
|
|
@@ -16122,12 +17227,12 @@ function readFilesAsContext(root, files, maxKB) {
|
|
|
16122
17227
|
for (const f of files) {
|
|
16123
17228
|
let body;
|
|
16124
17229
|
try {
|
|
16125
|
-
body =
|
|
17230
|
+
body = readFileSync5(f, "utf8");
|
|
16126
17231
|
} catch {
|
|
16127
17232
|
continue;
|
|
16128
17233
|
}
|
|
16129
17234
|
if (body.length > cap) body = body.slice(0, cap) + "\n[...truncated]";
|
|
16130
|
-
const rel =
|
|
17235
|
+
const rel = relative16(root, f).replace(/\\/g, "/");
|
|
16131
17236
|
parts.push(`### ${rel}
|
|
16132
17237
|
|
|
16133
17238
|
\`\`\`
|
|
@@ -16329,7 +17434,7 @@ var init_types = __esm({
|
|
|
16329
17434
|
});
|
|
16330
17435
|
|
|
16331
17436
|
// ../core/src/mcp/handlers.ts
|
|
16332
|
-
import { resolve as
|
|
17437
|
+
import { resolve as resolve20 } from "path";
|
|
16333
17438
|
async function callTool(name, args2, client, getIsProCached) {
|
|
16334
17439
|
switch (name) {
|
|
16335
17440
|
case "msapling_chat": {
|
|
@@ -16427,7 +17532,7 @@ ${r.response ?? ""}`;
|
|
|
16427
17532
|
return asResult2(JSON.stringify(result));
|
|
16428
17533
|
}
|
|
16429
17534
|
case "msapling_project_context": {
|
|
16430
|
-
const root =
|
|
17535
|
+
const root = resolve20(String(args2.path ?? "."));
|
|
16431
17536
|
const maxFiles = Number.isFinite(args2.max_files) ? Number(args2.max_files) : 30;
|
|
16432
17537
|
const maxKB = Number.isFinite(args2.max_file_size_kb) ? Number(args2.max_file_size_kb) : 50;
|
|
16433
17538
|
const files = buildFileTree(root, maxFiles);
|
|
@@ -16682,13 +17787,13 @@ var init_server = __esm({
|
|
|
16682
17787
|
if (inflight === 0) {
|
|
16683
17788
|
return [];
|
|
16684
17789
|
}
|
|
16685
|
-
const forcedResponses = await new Promise((
|
|
16686
|
-
this._drainResolve = () =>
|
|
17790
|
+
const forcedResponses = await new Promise((resolve21) => {
|
|
17791
|
+
this._drainResolve = () => resolve21([]);
|
|
16687
17792
|
setTimeout(() => {
|
|
16688
17793
|
this._drainResolve = null;
|
|
16689
17794
|
const remaining = Array.from(this._inflightCalls.values());
|
|
16690
17795
|
if (remaining.length === 0) {
|
|
16691
|
-
|
|
17796
|
+
resolve21([]);
|
|
16692
17797
|
return;
|
|
16693
17798
|
}
|
|
16694
17799
|
process.stderr.write(
|
|
@@ -16704,7 +17809,7 @@ var init_server = __esm({
|
|
|
16704
17809
|
}
|
|
16705
17810
|
}));
|
|
16706
17811
|
this._inflightCalls.clear();
|
|
16707
|
-
|
|
17812
|
+
resolve21(errorResponses);
|
|
16708
17813
|
}, DRAIN_TIMEOUT_MS);
|
|
16709
17814
|
});
|
|
16710
17815
|
return forcedResponses;
|
|
@@ -16904,7 +18009,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
16904
18009
|
var Header = () => /* @__PURE__ */ jsxs(Box, { borderStyle: "single", borderColor: "cyan", paddingX: 1, marginBottom: 1, children: [
|
|
16905
18010
|
/* @__PURE__ */ jsxs(Text, { bold: true, color: "cyan", children: [
|
|
16906
18011
|
"\u25CF MSapling CLI v",
|
|
16907
|
-
"2.3.6-beta.
|
|
18012
|
+
"2.3.6-beta.48"
|
|
16908
18013
|
] }),
|
|
16909
18014
|
/* @__PURE__ */ jsx(Box, { marginLeft: 2, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "Platinum Tier Architecture" }) })
|
|
16910
18015
|
] });
|
|
@@ -17309,19 +18414,19 @@ function createIdleAwarePoll(pollingIntervalRef, lastActivityRef, client, setUse
|
|
|
17309
18414
|
init_esm_shims();
|
|
17310
18415
|
init_commands();
|
|
17311
18416
|
init_plan();
|
|
17312
|
-
import { spawn as
|
|
18417
|
+
import { spawn as spawn10 } from "child_process";
|
|
17313
18418
|
|
|
17314
18419
|
// src/state/persistentState.ts
|
|
17315
18420
|
init_esm_shims();
|
|
17316
|
-
import { homedir as
|
|
17317
|
-
import { join as
|
|
17318
|
-
import { existsSync as
|
|
18421
|
+
import { homedir as homedir22 } from "os";
|
|
18422
|
+
import { join as join36, dirname as dirname5 } from "path";
|
|
18423
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync7 } from "fs";
|
|
17319
18424
|
import { readFile as readFile27, writeFile as writeFile17, rename as rename3 } from "fs/promises";
|
|
17320
|
-
import { randomBytes as
|
|
17321
|
-
var STATE_PATH =
|
|
18425
|
+
import { randomBytes as randomBytes15 } from "crypto";
|
|
18426
|
+
var STATE_PATH = join36(homedir22(), ".msapling", "state.json");
|
|
17322
18427
|
async function loadPersistentState(statePath = STATE_PATH) {
|
|
17323
18428
|
try {
|
|
17324
|
-
if (!
|
|
18429
|
+
if (!existsSync31(statePath)) return { version: 1 };
|
|
17325
18430
|
const text = await readFile27(statePath, "utf8");
|
|
17326
18431
|
const parsed = JSON.parse(text);
|
|
17327
18432
|
if (parsed.version !== 1) return { version: 1 };
|
|
@@ -17337,7 +18442,7 @@ async function loadPersistentState(statePath = STATE_PATH) {
|
|
|
17337
18442
|
async function savePersistentState(state, statePath = STATE_PATH) {
|
|
17338
18443
|
try {
|
|
17339
18444
|
const dir = dirname5(statePath);
|
|
17340
|
-
if (!
|
|
18445
|
+
if (!existsSync31(dir)) mkdirSync7(dir, { recursive: true });
|
|
17341
18446
|
const existing = await loadPersistentState(statePath);
|
|
17342
18447
|
const merged = {
|
|
17343
18448
|
version: 1,
|
|
@@ -17345,7 +18450,7 @@ async function savePersistentState(state, statePath = STATE_PATH) {
|
|
|
17345
18450
|
lastChatId: state.lastChatId ?? existing.lastChatId
|
|
17346
18451
|
};
|
|
17347
18452
|
const pid = process.pid;
|
|
17348
|
-
const rand =
|
|
18453
|
+
const rand = randomBytes15(4).toString("hex");
|
|
17349
18454
|
const tmp = `${statePath}.tmp.${pid}.${rand}`;
|
|
17350
18455
|
await writeFile17(tmp, JSON.stringify(merged, null, 2), "utf8");
|
|
17351
18456
|
await rename3(tmp, statePath);
|
|
@@ -17432,7 +18537,7 @@ ${prompt4}` : prompt4;
|
|
|
17432
18537
|
ctx.addMessage("system", "\u26A0 Local shell command executed without MCP/tool-level safety controls. Ensure command is trusted.");
|
|
17433
18538
|
try {
|
|
17434
18539
|
const shellArgv = process.platform === "win32" ? ["cmd.exe", "/c", execCmd] : ["sh", "-c", execCmd];
|
|
17435
|
-
const proc =
|
|
18540
|
+
const proc = spawn10(shellArgv[0], shellArgv.slice(1), {
|
|
17436
18541
|
stdio: ["inherit", "pipe", "pipe"]
|
|
17437
18542
|
});
|
|
17438
18543
|
let stdout = "";
|
|
@@ -17443,9 +18548,9 @@ ${prompt4}` : prompt4;
|
|
|
17443
18548
|
if (proc.stderr) proc.stderr.on("data", (chunk) => {
|
|
17444
18549
|
stderr += chunk.toString();
|
|
17445
18550
|
});
|
|
17446
|
-
await new Promise((
|
|
18551
|
+
await new Promise((resolve21, reject) => {
|
|
17447
18552
|
proc.on("close", (code) => {
|
|
17448
|
-
if (code === 0 || code === null)
|
|
18553
|
+
if (code === 0 || code === null) resolve21();
|
|
17449
18554
|
else reject(new Error(`Process exited with code ${code}`));
|
|
17450
18555
|
});
|
|
17451
18556
|
proc.on("error", reject);
|
|
@@ -17484,9 +18589,9 @@ ${prompt4}` : prompt4;
|
|
|
17484
18589
|
for (const mention of fileMentions) {
|
|
17485
18590
|
const filePath = mention.slice(1);
|
|
17486
18591
|
try {
|
|
17487
|
-
const { existsSync:
|
|
18592
|
+
const { existsSync: existsSync35 } = await import("fs");
|
|
17488
18593
|
const { readFile: readFile30 } = await import("fs/promises");
|
|
17489
|
-
if (
|
|
18594
|
+
if (existsSync35(filePath)) {
|
|
17490
18595
|
const content = await readFile30(filePath, "utf8");
|
|
17491
18596
|
const MAX_LEN = 32768;
|
|
17492
18597
|
const truncated = content.length > MAX_LEN ? content.slice(0, MAX_LEN) + "\n...[TRUNCATED]" : content;
|
|
@@ -17548,7 +18653,7 @@ init_src3();
|
|
|
17548
18653
|
init_src();
|
|
17549
18654
|
init_parseApprovalMode();
|
|
17550
18655
|
import { readFile as readFile28 } from "fs/promises";
|
|
17551
|
-
import { existsSync as
|
|
18656
|
+
import { existsSync as existsSync32 } from "fs";
|
|
17552
18657
|
async function initSession(ctx) {
|
|
17553
18658
|
try {
|
|
17554
18659
|
const journalEncrypted = await initJournalEncryption();
|
|
@@ -17573,10 +18678,10 @@ async function initSession(ctx) {
|
|
|
17573
18678
|
ctx.setShellEscapeEnabled(settings.shellEscapeEnabled !== false);
|
|
17574
18679
|
}
|
|
17575
18680
|
try {
|
|
17576
|
-
const { homedir:
|
|
17577
|
-
const { join:
|
|
17578
|
-
const userSettingsPath =
|
|
17579
|
-
if (
|
|
18681
|
+
const { homedir: homedir25 } = await import("os");
|
|
18682
|
+
const { join: join41 } = await import("path");
|
|
18683
|
+
const userSettingsPath = join41(homedir25(), ".msapling", "settings.json");
|
|
18684
|
+
if (existsSync32(userSettingsPath)) {
|
|
17580
18685
|
const userText = await readFile28(userSettingsPath, "utf8");
|
|
17581
18686
|
let parsed;
|
|
17582
18687
|
try {
|
|
@@ -17734,8 +18839,8 @@ var App = ({ compact: compact2 = false, continueSession: continueSession2 = fals
|
|
|
17734
18839
|
{ kind: "approval-request", tool: request.tool, command: request.command, reason: request.reason },
|
|
17735
18840
|
request.tool
|
|
17736
18841
|
);
|
|
17737
|
-
return new Promise((
|
|
17738
|
-
setPendingApproval({ request, resolve:
|
|
18842
|
+
return new Promise((resolve21) => {
|
|
18843
|
+
setPendingApproval({ request, resolve: resolve21 });
|
|
17739
18844
|
});
|
|
17740
18845
|
}, []);
|
|
17741
18846
|
const agent = useRef(new Agent(client, process.cwd(), requestApproval)).current;
|
|
@@ -17993,14 +19098,14 @@ var App = ({ compact: compact2 = false, continueSession: continueSession2 = fals
|
|
|
17993
19098
|
|
|
17994
19099
|
// src/runtime/bootstrap.ts
|
|
17995
19100
|
init_esm_shims();
|
|
17996
|
-
import { readFileSync as
|
|
19101
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
17997
19102
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
17998
|
-
import { dirname as dirname6, join as
|
|
19103
|
+
import { dirname as dirname6, join as join40 } from "path";
|
|
17999
19104
|
function readCliVersion2() {
|
|
18000
19105
|
const here = dirname6(fileURLToPath2(import.meta.url));
|
|
18001
19106
|
for (const rel of ["../package.json", "../../package.json"]) {
|
|
18002
19107
|
try {
|
|
18003
|
-
const pkg = JSON.parse(
|
|
19108
|
+
const pkg = JSON.parse(readFileSync6(join40(here, rel), "utf8"));
|
|
18004
19109
|
if (pkg.name && pkg.version) {
|
|
18005
19110
|
return { name: pkg.name, version: pkg.version };
|
|
18006
19111
|
}
|
|
@@ -18106,6 +19211,7 @@ function handleCliArgs(args2) {
|
|
|
18106
19211
|
}
|
|
18107
19212
|
|
|
18108
19213
|
// src/index.tsx
|
|
19214
|
+
init_src3();
|
|
18109
19215
|
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
18110
19216
|
var index_default = App;
|
|
18111
19217
|
function restoreTerminalMode() {
|
|
@@ -18131,6 +19237,12 @@ msapling: fatal ${label}: ${msg}`);
|
|
|
18131
19237
|
if (!process.env.NODE_ENV?.includes("test")) {
|
|
18132
19238
|
process.on("uncaughtException", (err) => handleFatal("uncaught exception", err));
|
|
18133
19239
|
process.on("unhandledRejection", (reason) => handleFatal("unhandled rejection", reason));
|
|
19240
|
+
process.on("exit", () => {
|
|
19241
|
+
try {
|
|
19242
|
+
BackgroundShellRegistry.killAll();
|
|
19243
|
+
} catch {
|
|
19244
|
+
}
|
|
19245
|
+
});
|
|
18134
19246
|
}
|
|
18135
19247
|
var args = process.argv.slice(2);
|
|
18136
19248
|
var compact = args.includes("--compact");
|