jinzd-ai-cli 0.4.234 → 0.4.236
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/README.md +5 -5
- package/dist/{auth-FSTAKSYF.js → auth-PE3Z5OHS.js} +1 -1
- package/dist/{batch-T3FRX4B6.js → batch-HZI7QAE7.js} +2 -2
- package/dist/{chunk-AFT6VFMI.js → chunk-7VAE3I3D.js} +1 -1
- package/dist/{chunk-3KTSJ2EL.js → chunk-CGQARJMP.js} +1 -1
- package/dist/{chunk-ETJ5X6V7.js → chunk-MFWAL5S2.js} +1 -1
- package/dist/{chunk-6MJMHHRK.js → chunk-O4QYY6SF.js} +3 -3
- package/dist/{chunk-5DTEEWYC.js → chunk-QDP3NUPX.js} +3 -3
- package/dist/{chunk-JVKAL5Q3.js → chunk-RYQBBEMM.js} +33 -3
- package/dist/{chunk-E5RLXMSU.js → chunk-SBSWH2EG.js} +89 -56
- package/dist/{chunk-TAXIG7HH.js → chunk-SCFGLRB3.js} +1 -1
- package/dist/{chunk-II6RZRTF.js → chunk-T6EIBUKW.js} +115 -18
- package/dist/{chunk-5VPRZMZB.js → chunk-TT7YURG3.js} +3 -3
- package/dist/{chunk-L5PNAQ6O.js → chunk-VBAW4ZPY.js} +1 -1
- package/dist/{ci-GJ53GPLA.js → ci-FYLCHRN3.js} +4 -4
- package/dist/{ci-format-AXE4QCI6.js → ci-format-TH2ZTFKU.js} +2 -2
- package/dist/{constants-DC4EXFSB.js → constants-BF6YNKRW.js} +1 -1
- package/dist/{doctor-cli-HKREND6Q.js → doctor-cli-B6GE4BYJ.js} +4 -4
- package/dist/electron-server.js +312 -151
- package/dist/{hub-LTTKRGZC.js → hub-QBQCKEPE.js} +1 -1
- package/dist/index.js +2439 -2437
- package/dist/{pr-4VODJVRV.js → pr-KV2WK7SU.js} +4 -4
- package/dist/{run-tests-QR2PYR2P.js → run-tests-DI6WEYHX.js} +1 -1
- package/dist/{run-tests-G5BNTMWQ.js → run-tests-MEHC73SQ.js} +2 -2
- package/dist/{server-EO6VQHKL.js → server-ETIGAZX5.js} +4 -4
- package/dist/{server-64WZVZZN.js → server-KZAP5JN3.js} +34 -33
- package/dist/{task-orchestrator-4INCZMLT.js → task-orchestrator-WS724BUX.js} +4 -4
- package/dist/{usage-T3HFMNNV.js → usage-DHZR5PG7.js} +2 -2
- package/package.json +2 -4
package/dist/electron-server.js
CHANGED
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
VERSION,
|
|
39
39
|
buildUserIdentityPrompt,
|
|
40
40
|
runTestsTool
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-MFWAL5S2.js";
|
|
42
42
|
import {
|
|
43
43
|
hasSemanticIndex,
|
|
44
44
|
semanticSearch
|
|
@@ -70,8 +70,8 @@ import {
|
|
|
70
70
|
import express from "express";
|
|
71
71
|
import { createServer } from "http";
|
|
72
72
|
import { WebSocketServer } from "ws";
|
|
73
|
-
import { join as join28, dirname as dirname11, resolve as resolve13, relative as
|
|
74
|
-
import { existsSync as existsSync33, readFileSync as
|
|
73
|
+
import { join as join28, dirname as dirname11, resolve as resolve13, relative as relative6, sep as sep4 } from "path";
|
|
74
|
+
import { existsSync as existsSync33, readFileSync as readFileSync25, readdirSync as readdirSync16, statSync as statSync14, realpathSync as realpathSync3 } from "fs";
|
|
75
75
|
import { networkInterfaces } from "os";
|
|
76
76
|
|
|
77
77
|
// src/config/config-manager.ts
|
|
@@ -1908,12 +1908,12 @@ function findPhantomClaims(content, extraMessages) {
|
|
|
1908
1908
|
const claimed = extractClaimedFilePaths(content);
|
|
1909
1909
|
if (claimed.length === 0) return [];
|
|
1910
1910
|
const normalize = (p) => p.replace(/\\/g, "/").toLowerCase().replace(/^\.\//, "");
|
|
1911
|
-
const
|
|
1911
|
+
const basename8 = (p) => {
|
|
1912
1912
|
const parts = normalize(p).split("/");
|
|
1913
1913
|
return parts[parts.length - 1] ?? "";
|
|
1914
1914
|
};
|
|
1915
1915
|
const written = extractWrittenFilePaths(extraMessages).map(normalize);
|
|
1916
|
-
const writtenBases = new Set(written.map(
|
|
1916
|
+
const writtenBases = new Set(written.map(basename8));
|
|
1917
1917
|
const writtenFull = new Set(written);
|
|
1918
1918
|
const bashText = extractBashCommands(extraMessages).map((c) => c.replace(/\\/g, "/").toLowerCase()).join("\n");
|
|
1919
1919
|
return claimed.filter((raw) => {
|
|
@@ -1922,8 +1922,8 @@ function findPhantomClaims(content, extraMessages) {
|
|
|
1922
1922
|
for (const w of writtenFull) {
|
|
1923
1923
|
if (w.endsWith("/" + norm2) || norm2.endsWith("/" + w)) return false;
|
|
1924
1924
|
}
|
|
1925
|
-
if (writtenBases.has(
|
|
1926
|
-
const base =
|
|
1925
|
+
if (writtenBases.has(basename8(norm2))) return false;
|
|
1926
|
+
const base = basename8(norm2);
|
|
1927
1927
|
if (base && bashText.includes(base)) return false;
|
|
1928
1928
|
return true;
|
|
1929
1929
|
});
|
|
@@ -5937,11 +5937,11 @@ ${content}`;
|
|
|
5937
5937
|
|
|
5938
5938
|
// src/tools/builtin/write-file.ts
|
|
5939
5939
|
import { appendFileSync, mkdirSync as mkdirSync6 } from "fs";
|
|
5940
|
-
import { dirname as
|
|
5940
|
+
import { dirname as dirname5 } from "path";
|
|
5941
5941
|
|
|
5942
5942
|
// src/tools/executor.ts
|
|
5943
5943
|
import chalk3 from "chalk";
|
|
5944
|
-
import { existsSync as
|
|
5944
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
|
|
5945
5945
|
import { tmpdir } from "os";
|
|
5946
5946
|
|
|
5947
5947
|
// src/core/readline-internal.ts
|
|
@@ -6310,7 +6310,8 @@ function runHook(template, vars) {
|
|
|
6310
6310
|
}
|
|
6311
6311
|
|
|
6312
6312
|
// src/tools/permissions.ts
|
|
6313
|
-
import {
|
|
6313
|
+
import { existsSync as existsSync8, realpathSync } from "fs";
|
|
6314
|
+
import { basename as basename2, dirname as dirname3, isAbsolute, resolve as resolve3 } from "path";
|
|
6314
6315
|
function checkPermission(toolName, args, dangerLevel, rules, defaultAction = "confirm") {
|
|
6315
6316
|
for (const rule of rules) {
|
|
6316
6317
|
if (rule.tool !== "*" && rule.tool !== toolName) continue;
|
|
@@ -6355,10 +6356,28 @@ var READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
|
|
|
6355
6356
|
function normalizePathForPermission(value) {
|
|
6356
6357
|
return value.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
6357
6358
|
}
|
|
6359
|
+
function canonicalizeWithNearestParent(value) {
|
|
6360
|
+
const missing = [];
|
|
6361
|
+
let cursor = resolve3(value);
|
|
6362
|
+
while (!existsSync8(cursor)) {
|
|
6363
|
+
const parent = dirname3(cursor);
|
|
6364
|
+
if (parent === cursor) return null;
|
|
6365
|
+
missing.unshift(basename2(cursor));
|
|
6366
|
+
cursor = parent;
|
|
6367
|
+
}
|
|
6368
|
+
try {
|
|
6369
|
+
return resolve3(realpathSync(cursor), ...missing);
|
|
6370
|
+
} catch {
|
|
6371
|
+
return null;
|
|
6372
|
+
}
|
|
6373
|
+
}
|
|
6358
6374
|
function isInsidePath(root, target) {
|
|
6359
6375
|
const absoluteTarget = isAbsolute(target) ? target : resolve3(root, target);
|
|
6360
|
-
const
|
|
6361
|
-
const
|
|
6376
|
+
const canonicalRoot = canonicalizeWithNearestParent(root);
|
|
6377
|
+
const canonicalTarget = canonicalizeWithNearestParent(absoluteTarget);
|
|
6378
|
+
if (!canonicalRoot || !canonicalTarget) return false;
|
|
6379
|
+
const r = normalizePathForPermission(canonicalRoot);
|
|
6380
|
+
const t = normalizePathForPermission(canonicalTarget);
|
|
6362
6381
|
return t === r || t.startsWith(`${r}/`);
|
|
6363
6382
|
}
|
|
6364
6383
|
function extractPathArg(toolName, args) {
|
|
@@ -6410,19 +6429,31 @@ function profileHardDecision(profileName, toolName, args, dangerLevel, ctx) {
|
|
|
6410
6429
|
return null;
|
|
6411
6430
|
}
|
|
6412
6431
|
if (profileName === "workspace-write") {
|
|
6413
|
-
if (dangerLevel === "destructive")
|
|
6432
|
+
if (dangerLevel === "destructive") {
|
|
6433
|
+
return { action: "confirm", reason: "destructive tools always require confirmation", profileName, hardBoundary: true };
|
|
6434
|
+
}
|
|
6414
6435
|
if (dangerLevel === "write" && isExplicitFileWriteTool(toolName)) {
|
|
6415
6436
|
const target = extractPathArg(toolName, args);
|
|
6416
|
-
if (!target)
|
|
6437
|
+
if (!target) {
|
|
6438
|
+
return { action: "confirm", reason: "file write target is not explicit", profileName, hardBoundary: true };
|
|
6439
|
+
}
|
|
6417
6440
|
const allowedRoots = getAllowedWriteRoots(ctx);
|
|
6418
6441
|
if (allowedRoots.length === 0 || !allowedRoots.some((root) => isInsidePath(root, target))) {
|
|
6419
|
-
return { action: "deny", reason: "file write target is outside workspace/temp roots", profileName };
|
|
6442
|
+
return { action: "deny", reason: "file write target is outside workspace/temp roots", profileName, hardBoundary: true };
|
|
6420
6443
|
}
|
|
6421
6444
|
}
|
|
6445
|
+
if (toolName === "bash" || toolName === "run_interactive" || toolName === "task_create") {
|
|
6446
|
+
return {
|
|
6447
|
+
action: "confirm",
|
|
6448
|
+
reason: "shell/process writes cannot be proven to stay inside workspace/temp roots",
|
|
6449
|
+
profileName,
|
|
6450
|
+
hardBoundary: true
|
|
6451
|
+
};
|
|
6452
|
+
}
|
|
6422
6453
|
return null;
|
|
6423
6454
|
}
|
|
6424
6455
|
if (profileName === "danger-full-access") {
|
|
6425
|
-
if (dangerLevel === "destructive") return { action: "confirm", reason: "destructive tools still require confirmation", profileName };
|
|
6456
|
+
if (dangerLevel === "destructive") return { action: "confirm", reason: "destructive tools still require confirmation", profileName, hardBoundary: true };
|
|
6426
6457
|
return null;
|
|
6427
6458
|
}
|
|
6428
6459
|
return null;
|
|
@@ -6434,7 +6465,8 @@ function checkPermissionWithProfile(toolName, args, dangerLevel, rules, defaultA
|
|
|
6434
6465
|
const combinedRules = [...getProfileRules(profileContext), ...rules];
|
|
6435
6466
|
const profileDefault = profileName === "legacy" ? defaultAction : getProfileDefaultAction(profileName, profileContext);
|
|
6436
6467
|
const action = checkPermission(toolName, args, dangerLevel, combinedRules, profileDefault);
|
|
6437
|
-
if (
|
|
6468
|
+
if (action === "deny") return { action, profileName };
|
|
6469
|
+
if (hard?.action === "confirm") return hard;
|
|
6438
6470
|
return { action, profileName };
|
|
6439
6471
|
}
|
|
6440
6472
|
function formatPermissionProfileWarning(profileName) {
|
|
@@ -6692,8 +6724,8 @@ var theme = new Proxy(DARK_THEME, {
|
|
|
6692
6724
|
});
|
|
6693
6725
|
|
|
6694
6726
|
// src/diagnostics/tool-stats.ts
|
|
6695
|
-
import { existsSync as
|
|
6696
|
-
import { join as join5, dirname as
|
|
6727
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7, mkdirSync as mkdirSync5 } from "fs";
|
|
6728
|
+
import { join as join5, dirname as dirname4 } from "path";
|
|
6697
6729
|
import { homedir as homedir3 } from "os";
|
|
6698
6730
|
var STATS_FILE_NAME = "tool-stats.json";
|
|
6699
6731
|
var STATS_VERSION = 1;
|
|
@@ -6708,7 +6740,7 @@ function statsFilePath() {
|
|
|
6708
6740
|
}
|
|
6709
6741
|
function load() {
|
|
6710
6742
|
const path3 = statsFilePath();
|
|
6711
|
-
if (!
|
|
6743
|
+
if (!existsSync9(path3)) {
|
|
6712
6744
|
return { version: STATS_VERSION, startedAt: (/* @__PURE__ */ new Date()).toISOString(), entries: {} };
|
|
6713
6745
|
}
|
|
6714
6746
|
try {
|
|
@@ -6770,7 +6802,7 @@ function flush() {
|
|
|
6770
6802
|
if (!dirty || state === null) return;
|
|
6771
6803
|
const path3 = statsFilePath();
|
|
6772
6804
|
try {
|
|
6773
|
-
mkdirSync5(
|
|
6805
|
+
mkdirSync5(dirname4(path3), { recursive: true });
|
|
6774
6806
|
atomicWriteFileSync(path3, JSON.stringify(state, null, 2));
|
|
6775
6807
|
dirty = false;
|
|
6776
6808
|
pendingWrites = 0;
|
|
@@ -6794,7 +6826,7 @@ function getTopUsedTools(limit = 5) {
|
|
|
6794
6826
|
}
|
|
6795
6827
|
|
|
6796
6828
|
// src/tools/action-classifier.ts
|
|
6797
|
-
import { existsSync as
|
|
6829
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
|
|
6798
6830
|
import { isAbsolute as isAbsolute2, join as join6, resolve as resolve4 } from "path";
|
|
6799
6831
|
var RECENT_DENIED_LIMIT = 50;
|
|
6800
6832
|
var recentlyDenied = [];
|
|
@@ -6837,7 +6869,7 @@ function splitShellWords(command) {
|
|
|
6837
6869
|
}
|
|
6838
6870
|
function readPackageManifest(root) {
|
|
6839
6871
|
const p = join6(root, "package.json");
|
|
6840
|
-
if (!
|
|
6872
|
+
if (!existsSync10(p)) return null;
|
|
6841
6873
|
try {
|
|
6842
6874
|
return JSON.parse(readFileSync8(p, "utf-8"));
|
|
6843
6875
|
} catch {
|
|
@@ -6856,7 +6888,7 @@ function manifestHasDependency(root, name) {
|
|
|
6856
6888
|
function lockfileMentions(root, name) {
|
|
6857
6889
|
for (const file of ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"]) {
|
|
6858
6890
|
const p = join6(root, file);
|
|
6859
|
-
if (!
|
|
6891
|
+
if (!existsSync10(p)) continue;
|
|
6860
6892
|
try {
|
|
6861
6893
|
if (readFileSync8(p, "utf-8").includes(name)) return true;
|
|
6862
6894
|
} catch {
|
|
@@ -7132,14 +7164,15 @@ var ToolExecutor = class {
|
|
|
7132
7164
|
return { callId: call.id, content: message, isError: true };
|
|
7133
7165
|
}
|
|
7134
7166
|
}
|
|
7135
|
-
|
|
7167
|
+
const requiresHardConfirmation = permission.action === "confirm" && permission.hardBoundary === true;
|
|
7168
|
+
if ((networkPermission?.action === "confirm" || requiresHardConfirmation) && dangerLevel === "safe") {
|
|
7136
7169
|
this.printToolCall(call);
|
|
7137
7170
|
toolCallAlreadyPrinted = true;
|
|
7138
7171
|
const confirmed = await this.confirm(call, "write");
|
|
7139
7172
|
if (!confirmed) {
|
|
7140
7173
|
return {
|
|
7141
7174
|
callId: call.id,
|
|
7142
|
-
content: `[User cancelled] The user declined
|
|
7175
|
+
content: `[User cancelled] The user declined permission for ${call.name}. Do not retry without asking.`,
|
|
7143
7176
|
isError: true
|
|
7144
7177
|
};
|
|
7145
7178
|
}
|
|
@@ -7157,7 +7190,7 @@ var ToolExecutor = class {
|
|
|
7157
7190
|
isError: true
|
|
7158
7191
|
};
|
|
7159
7192
|
}
|
|
7160
|
-
if (classification.decision === "auto-approve") {
|
|
7193
|
+
if (classification.decision === "auto-approve" && !requiresHardConfirmation) {
|
|
7161
7194
|
this.printToolCall(call);
|
|
7162
7195
|
if (dangerLevel === "write") this.printDiffPreview(call);
|
|
7163
7196
|
console.log(theme.warning(` \u26A1 Auto-approved (/auto: ${classification.ruleId})`));
|
|
@@ -7178,7 +7211,7 @@ var ToolExecutor = class {
|
|
|
7178
7211
|
}
|
|
7179
7212
|
}
|
|
7180
7213
|
}
|
|
7181
|
-
if (this.sessionAutoApprove && dangerLevel === "write") {
|
|
7214
|
+
if (this.sessionAutoApprove && dangerLevel === "write" && !requiresHardConfirmation) {
|
|
7182
7215
|
this.printToolCall(call);
|
|
7183
7216
|
if (dangerLevel === "write") this.printDiffPreview(call);
|
|
7184
7217
|
console.log(theme.warning(" \u26A1 Auto-approved (session /yolo mode)"));
|
|
@@ -7422,7 +7455,7 @@ var ToolExecutor = class {
|
|
|
7422
7455
|
const filePath = String(call.arguments["path"] ?? "");
|
|
7423
7456
|
const newContent = String(call.arguments["content"] ?? "");
|
|
7424
7457
|
if (!filePath) return;
|
|
7425
|
-
if (
|
|
7458
|
+
if (existsSync11(filePath)) {
|
|
7426
7459
|
let oldContent;
|
|
7427
7460
|
try {
|
|
7428
7461
|
oldContent = readFileSync9(filePath, "utf-8");
|
|
@@ -7448,7 +7481,7 @@ var ToolExecutor = class {
|
|
|
7448
7481
|
}
|
|
7449
7482
|
} else if (call.name === "edit_file") {
|
|
7450
7483
|
const filePath = String(call.arguments["path"] ?? "");
|
|
7451
|
-
if (!filePath || !
|
|
7484
|
+
if (!filePath || !existsSync11(filePath)) return;
|
|
7452
7485
|
const oldStr = call.arguments["old_str"];
|
|
7453
7486
|
const newStr = call.arguments["new_str"];
|
|
7454
7487
|
if (oldStr !== void 0) {
|
|
@@ -7567,7 +7600,7 @@ var ToolExecutor = class {
|
|
|
7567
7600
|
};
|
|
7568
7601
|
|
|
7569
7602
|
// src/tools/sensitive-paths.ts
|
|
7570
|
-
import { resolve as resolve5, sep as sep2, basename as
|
|
7603
|
+
import { resolve as resolve5, sep as sep2, basename as basename3 } from "path";
|
|
7571
7604
|
import { homedir as homedir4 } from "os";
|
|
7572
7605
|
var home = homedir4();
|
|
7573
7606
|
function norm(p) {
|
|
@@ -7586,7 +7619,7 @@ function homeRel(p) {
|
|
|
7586
7619
|
function classifyWritePath(path3) {
|
|
7587
7620
|
if (!path3) return { sensitive: false };
|
|
7588
7621
|
const abs = norm(path3);
|
|
7589
|
-
const base =
|
|
7622
|
+
const base = basename3(abs);
|
|
7590
7623
|
const rel = homeRel(path3);
|
|
7591
7624
|
if (rel) {
|
|
7592
7625
|
const shellRc = /* @__PURE__ */ new Set([
|
|
@@ -7710,7 +7743,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
7710
7743
|
}
|
|
7711
7744
|
undoStack.push(filePath, `write_file${appendMode ? " (append)" : ""}: ${filePath}`);
|
|
7712
7745
|
fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
|
|
7713
|
-
mkdirSync6(
|
|
7746
|
+
mkdirSync6(dirname5(filePath), { recursive: true });
|
|
7714
7747
|
if (appendMode) {
|
|
7715
7748
|
appendFileSync(filePath, content, encoding);
|
|
7716
7749
|
} else {
|
|
@@ -7730,7 +7763,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
7730
7763
|
};
|
|
7731
7764
|
|
|
7732
7765
|
// src/tools/builtin/edit-file.ts
|
|
7733
|
-
import { readFileSync as readFileSync10, existsSync as
|
|
7766
|
+
import { readFileSync as readFileSync10, existsSync as existsSync12 } from "fs";
|
|
7734
7767
|
|
|
7735
7768
|
// src/tools/builtin/patch-apply.ts
|
|
7736
7769
|
function parseUnifiedDiff(patch) {
|
|
@@ -8091,7 +8124,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
8091
8124
|
const filePath = String(args["path"] ?? args["file"] ?? "");
|
|
8092
8125
|
const encoding = args["encoding"] ?? "utf-8";
|
|
8093
8126
|
if (!filePath) throw new ToolError("edit_file", "path is required");
|
|
8094
|
-
if (!
|
|
8127
|
+
if (!existsSync12(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
|
|
8095
8128
|
const verdict = classifyWritePath(filePath);
|
|
8096
8129
|
if (verdict.sensitive && subAgentGuard.active) {
|
|
8097
8130
|
throw new ToolError(
|
|
@@ -8257,8 +8290,8 @@ function truncatePreview(str, maxLen = 80) {
|
|
|
8257
8290
|
}
|
|
8258
8291
|
|
|
8259
8292
|
// src/tools/builtin/list-dir.ts
|
|
8260
|
-
import { readdirSync as readdirSync4, statSync as statSync4, existsSync as
|
|
8261
|
-
import { join as join7, basename as
|
|
8293
|
+
import { readdirSync as readdirSync4, statSync as statSync4, existsSync as existsSync13 } from "fs";
|
|
8294
|
+
import { join as join7, basename as basename4 } from "path";
|
|
8262
8295
|
var listDirTool = {
|
|
8263
8296
|
definition: {
|
|
8264
8297
|
name: "list_dir",
|
|
@@ -8280,8 +8313,8 @@ var listDirTool = {
|
|
|
8280
8313
|
async execute(args) {
|
|
8281
8314
|
const dirPath = String(args["path"] ?? process.cwd());
|
|
8282
8315
|
const recursive = Boolean(args["recursive"] ?? false);
|
|
8283
|
-
if (!
|
|
8284
|
-
const targetName =
|
|
8316
|
+
if (!existsSync13(dirPath)) {
|
|
8317
|
+
const targetName = basename4(dirPath).toLowerCase();
|
|
8285
8318
|
const cwd = process.cwd();
|
|
8286
8319
|
const suggestions = [];
|
|
8287
8320
|
try {
|
|
@@ -8358,7 +8391,7 @@ function formatSize(bytes) {
|
|
|
8358
8391
|
}
|
|
8359
8392
|
|
|
8360
8393
|
// src/tools/builtin/grep-files.ts
|
|
8361
|
-
import { readdirSync as readdirSync5, readFileSync as readFileSync11, statSync as statSync5, existsSync as
|
|
8394
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync11, statSync as statSync5, existsSync as existsSync14 } from "fs";
|
|
8362
8395
|
import { readFile } from "fs/promises";
|
|
8363
8396
|
import { join as join8, relative } from "path";
|
|
8364
8397
|
var grepFilesTool = {
|
|
@@ -8411,7 +8444,7 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
|
|
|
8411
8444
|
const contextLines = Math.max(0, Number(args["context_lines"] ?? 0));
|
|
8412
8445
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 50));
|
|
8413
8446
|
if (!pattern) throw new ToolError("grep_files", "pattern is required");
|
|
8414
|
-
if (!
|
|
8447
|
+
if (!existsSync14(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
|
|
8415
8448
|
const MAX_PATTERN_LENGTH = 1e3;
|
|
8416
8449
|
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
8417
8450
|
throw new ToolError("grep_files", `Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`);
|
|
@@ -8595,8 +8628,8 @@ function searchInFile(fullPath, displayPath2, regex, contextLines, maxResults, r
|
|
|
8595
8628
|
}
|
|
8596
8629
|
|
|
8597
8630
|
// src/tools/builtin/glob-files.ts
|
|
8598
|
-
import { readdirSync as readdirSync6, statSync as statSync6, existsSync as
|
|
8599
|
-
import { join as join9, relative as relative2, basename as
|
|
8631
|
+
import { readdirSync as readdirSync6, statSync as statSync6, existsSync as existsSync15 } from "fs";
|
|
8632
|
+
import { join as join9, relative as relative2, basename as basename5 } from "path";
|
|
8600
8633
|
var globFilesTool = {
|
|
8601
8634
|
definition: {
|
|
8602
8635
|
name: "glob_files",
|
|
@@ -8629,7 +8662,7 @@ Results sorted by most recent modification time. Automatically skips node_module
|
|
|
8629
8662
|
const rootPath = String(args["path"] ?? process.cwd());
|
|
8630
8663
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 100));
|
|
8631
8664
|
if (!pattern) throw new ToolError("glob_files", "pattern is required");
|
|
8632
|
-
if (!
|
|
8665
|
+
if (!existsSync15(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
|
|
8633
8666
|
const regex = globToRegex(pattern);
|
|
8634
8667
|
const matches = [];
|
|
8635
8668
|
collectMatchingFiles(rootPath, rootPath, regex, matches, maxResults);
|
|
@@ -8712,7 +8745,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
|
|
|
8712
8745
|
collectMatchingFiles(fullPath, rootPath, regex, results, maxResults);
|
|
8713
8746
|
} else if (entry.isFile()) {
|
|
8714
8747
|
const relPath = relative2(rootPath, fullPath).replace(/\\/g, "/");
|
|
8715
|
-
if (regex.test(relPath) || regex.test(
|
|
8748
|
+
if (regex.test(relPath) || regex.test(basename5(relPath))) {
|
|
8716
8749
|
try {
|
|
8717
8750
|
const stat = statSync6(fullPath);
|
|
8718
8751
|
results.push({ relPath, absPath: fullPath, mtime: stat.mtimeMs });
|
|
@@ -9447,7 +9480,7 @@ ${preamble}`;
|
|
|
9447
9480
|
|
|
9448
9481
|
// src/tools/builtin/save-last-response.ts
|
|
9449
9482
|
import { mkdirSync as mkdirSync7, unlinkSync as unlinkSync4, rmdirSync as rmdirSync2 } from "fs";
|
|
9450
|
-
import { dirname as
|
|
9483
|
+
import { dirname as dirname6 } from "path";
|
|
9451
9484
|
var lastResponseStore = { content: "" };
|
|
9452
9485
|
function cleanupRejectedTeeFile(filePath) {
|
|
9453
9486
|
try {
|
|
@@ -9455,7 +9488,7 @@ function cleanupRejectedTeeFile(filePath) {
|
|
|
9455
9488
|
} catch {
|
|
9456
9489
|
}
|
|
9457
9490
|
try {
|
|
9458
|
-
rmdirSync2(
|
|
9491
|
+
rmdirSync2(dirname6(filePath));
|
|
9459
9492
|
} catch {
|
|
9460
9493
|
}
|
|
9461
9494
|
}
|
|
@@ -9496,7 +9529,7 @@ Any of these triggers means use save_last_response, NOT write_file:
|
|
|
9496
9529
|
throw new ToolError("save_last_response", "No content to save: AI has not produced any response yet, or the last response was empty.");
|
|
9497
9530
|
}
|
|
9498
9531
|
undoStack.push(filePath, `save_last_response: ${filePath}`);
|
|
9499
|
-
mkdirSync7(
|
|
9532
|
+
mkdirSync7(dirname6(filePath), { recursive: true });
|
|
9500
9533
|
atomicWriteFileSync(filePath, content);
|
|
9501
9534
|
const lines = content.split("\n").length;
|
|
9502
9535
|
return `File saved: ${filePath} (${lines} lines, ${content.length} bytes)`;
|
|
@@ -9508,13 +9541,13 @@ import { join as join12 } from "path";
|
|
|
9508
9541
|
import { homedir as homedir5 } from "os";
|
|
9509
9542
|
|
|
9510
9543
|
// src/memory/persistent-memory.ts
|
|
9511
|
-
import { existsSync as
|
|
9544
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync12 } from "fs";
|
|
9512
9545
|
import { randomUUID, createHash as createHash2 } from "crypto";
|
|
9513
|
-
import { dirname as
|
|
9546
|
+
import { dirname as dirname7, join as join11, resolve as resolve6 } from "path";
|
|
9514
9547
|
|
|
9515
9548
|
// src/tools/git-context.ts
|
|
9516
9549
|
import { execSync as execSync2 } from "child_process";
|
|
9517
|
-
import { existsSync as
|
|
9550
|
+
import { existsSync as existsSync16 } from "fs";
|
|
9518
9551
|
import { join as join10 } from "path";
|
|
9519
9552
|
function runGit(cmd, cwd) {
|
|
9520
9553
|
try {
|
|
@@ -9532,7 +9565,7 @@ function getGitRoot(cwd = process.cwd()) {
|
|
|
9532
9565
|
return runGit("rev-parse --show-toplevel", cwd);
|
|
9533
9566
|
}
|
|
9534
9567
|
function getGitContext(cwd = process.cwd()) {
|
|
9535
|
-
if (!
|
|
9568
|
+
if (!existsSync16(join10(cwd, ".git"))) {
|
|
9536
9569
|
const result = runGit("rev-parse --git-dir", cwd);
|
|
9537
9570
|
if (!result) return null;
|
|
9538
9571
|
}
|
|
@@ -9643,7 +9676,7 @@ function normalizeEntry(value) {
|
|
|
9643
9676
|
}
|
|
9644
9677
|
function legacyEntriesFromMarkdown(configDir) {
|
|
9645
9678
|
const file = memoryMarkdownPath(configDir);
|
|
9646
|
-
if (!
|
|
9679
|
+
if (!existsSync17(file)) return [];
|
|
9647
9680
|
const content = readFileSync12(file, "utf-8").trim();
|
|
9648
9681
|
if (!content) return [];
|
|
9649
9682
|
const parts = content.split(/\n(?=##\s+\d{4}-\d{2}-\d{2})/g);
|
|
@@ -9660,7 +9693,7 @@ ${chunk}`).digest("hex").slice(0, 12);
|
|
|
9660
9693
|
}
|
|
9661
9694
|
function loadMemoryEntries(configDir) {
|
|
9662
9695
|
const file = memoryStorePath(configDir);
|
|
9663
|
-
if (!
|
|
9696
|
+
if (!existsSync17(file)) return legacyEntriesFromMarkdown(configDir);
|
|
9664
9697
|
const text = readFileSync12(file, "utf-8").trim();
|
|
9665
9698
|
if (!text) return [];
|
|
9666
9699
|
const entries = [];
|
|
@@ -10108,13 +10141,13 @@ function formatResults2(query, data, _requested) {
|
|
|
10108
10141
|
}
|
|
10109
10142
|
|
|
10110
10143
|
// src/agents/agent-config.ts
|
|
10111
|
-
import { existsSync as
|
|
10144
|
+
import { existsSync as existsSync19, readdirSync as readdirSync8, readFileSync as readFileSync14 } from "fs";
|
|
10112
10145
|
import { join as join14 } from "path";
|
|
10113
10146
|
import { homedir as homedir6 } from "os";
|
|
10114
10147
|
|
|
10115
10148
|
// src/plugins/plugin-manager.ts
|
|
10116
|
-
import { existsSync as
|
|
10117
|
-
import { basename as
|
|
10149
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync9, readdirSync as readdirSync7, readFileSync as readFileSync13, realpathSync as realpathSync2, cpSync, rmSync, statSync as statSync7 } from "fs";
|
|
10150
|
+
import { basename as basename6, dirname as dirname8, join as join13, relative as relative3, resolve as resolve7, sep as sep3 } from "path";
|
|
10118
10151
|
import { createHash as createHash3 } from "crypto";
|
|
10119
10152
|
import { z as z2 } from "zod";
|
|
10120
10153
|
var MANIFEST_RELATIVE = ".aicli-plugin/plugin.json";
|
|
@@ -10184,30 +10217,127 @@ function statePath(configDir) {
|
|
|
10184
10217
|
}
|
|
10185
10218
|
function loadState(configDir) {
|
|
10186
10219
|
const file = statePath(configDir);
|
|
10187
|
-
if (!
|
|
10220
|
+
if (!existsSync18(file)) return { version: 2, plugins: [] };
|
|
10188
10221
|
try {
|
|
10189
10222
|
const parsed = JSON.parse(readFileSync13(file, "utf-8"));
|
|
10190
|
-
|
|
10223
|
+
const rawPlugins = Array.isArray(parsed.plugins) ? parsed.plugins : [];
|
|
10224
|
+
return { version: 2, plugins: rawPlugins.map((p) => migrateEntry(p)) };
|
|
10191
10225
|
} catch {
|
|
10192
|
-
return { version:
|
|
10226
|
+
return { version: 2, plugins: [] };
|
|
10193
10227
|
}
|
|
10194
10228
|
}
|
|
10229
|
+
function migrateEntry(entry) {
|
|
10230
|
+
const name = String(entry.name ?? "");
|
|
10231
|
+
const contentHash = typeof entry.contentHash === "string" ? entry.contentHash : void 0;
|
|
10232
|
+
const legacyHash = typeof entry.hash === "string" ? entry.hash : void 0;
|
|
10233
|
+
const trusted = contentHash ? entry.trusted === true : false;
|
|
10234
|
+
return {
|
|
10235
|
+
name,
|
|
10236
|
+
enabled: entry.enabled === true,
|
|
10237
|
+
trusted,
|
|
10238
|
+
contentHash: contentHash ?? legacyHash,
|
|
10239
|
+
installedAt: String(entry.installedAt ?? (/* @__PURE__ */ new Date(0)).toISOString()),
|
|
10240
|
+
trustedAt: trusted && entry.trustedAt ? String(entry.trustedAt) : void 0,
|
|
10241
|
+
enabledAt: entry.enabledAt ? String(entry.enabledAt) : void 0
|
|
10242
|
+
};
|
|
10243
|
+
}
|
|
10195
10244
|
function saveState(configDir, state2) {
|
|
10196
10245
|
mkdirSync9(pluginRoot(configDir), { recursive: true });
|
|
10197
10246
|
atomicWriteFileSync(statePath(configDir), JSON.stringify(state2, null, 2));
|
|
10198
10247
|
}
|
|
10199
|
-
function
|
|
10200
|
-
|
|
10248
|
+
function normalizeForHash(buf) {
|
|
10249
|
+
const str = buf.toString("utf-8").replaceAll("\r\n", "\n");
|
|
10250
|
+
return Buffer.from(str, "utf-8");
|
|
10251
|
+
}
|
|
10252
|
+
function extractCommandPaths(command, pluginDir) {
|
|
10253
|
+
const resolvedDir = resolve7(pluginDir);
|
|
10254
|
+
const refs = [];
|
|
10255
|
+
for (const m of command.matchAll(/\$PLUGIN_DIR[\\/]([^"'\s]+)/g)) {
|
|
10256
|
+
refs.push(m[1]);
|
|
10257
|
+
}
|
|
10258
|
+
for (const m of command.matchAll(/\$\{pluginDir\}[\\/]([^"'\s}]+)/g)) {
|
|
10259
|
+
refs.push(m[1]);
|
|
10260
|
+
}
|
|
10261
|
+
for (const m of command.matchAll(/(?<=["'\s])(\.?[.\\/][^"'\s]+)/g)) {
|
|
10262
|
+
refs.push(m[0]);
|
|
10263
|
+
}
|
|
10264
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10265
|
+
const valid = [];
|
|
10266
|
+
for (const ref of refs) {
|
|
10267
|
+
if (seen.has(ref)) continue;
|
|
10268
|
+
seen.add(ref);
|
|
10269
|
+
try {
|
|
10270
|
+
const abs = resolve7(resolvedDir, ref);
|
|
10271
|
+
if (!abs.startsWith(resolvedDir + sep3)) continue;
|
|
10272
|
+
if (existsSync18(abs)) {
|
|
10273
|
+
const real = realpathSync2(abs);
|
|
10274
|
+
if (!real.startsWith(resolvedDir + sep3)) continue;
|
|
10275
|
+
valid.push(relative3(resolvedDir, real));
|
|
10276
|
+
}
|
|
10277
|
+
} catch {
|
|
10278
|
+
}
|
|
10279
|
+
}
|
|
10280
|
+
return valid;
|
|
10281
|
+
}
|
|
10282
|
+
function hashPluginContent(pluginDir, manifest) {
|
|
10283
|
+
const resolvedDir = resolve7(pluginDir);
|
|
10284
|
+
const assetRels = /* @__PURE__ */ new Set(
|
|
10285
|
+
[...manifest.skills, ...manifest.commands, ...manifest.agents]
|
|
10286
|
+
);
|
|
10287
|
+
for (const raw of Object.values(manifest.hooks?.events ?? {})) {
|
|
10288
|
+
const items = Array.isArray(raw) ? raw : [raw];
|
|
10289
|
+
for (const item of items) {
|
|
10290
|
+
const cmd = typeof item === "string" ? item : item.command;
|
|
10291
|
+
if (cmd) {
|
|
10292
|
+
for (const p of extractCommandPaths(cmd, pluginDir)) assetRels.add(p);
|
|
10293
|
+
}
|
|
10294
|
+
}
|
|
10295
|
+
}
|
|
10296
|
+
for (const server of Object.values(manifest.mcpServers ?? {})) {
|
|
10297
|
+
for (const p of extractCommandPaths(server.command, pluginDir)) assetRels.add(p);
|
|
10298
|
+
for (const arg of server.args ?? []) {
|
|
10299
|
+
for (const p of extractCommandPaths(arg, pluginDir)) assetRels.add(p);
|
|
10300
|
+
}
|
|
10301
|
+
}
|
|
10302
|
+
const sorted = [...assetRels].sort();
|
|
10303
|
+
const hasher = createHash3("sha256");
|
|
10304
|
+
const manifestPath = join13(resolvedDir, MANIFEST_RELATIVE);
|
|
10305
|
+
hasher.update(normalizeForHash(readFileSync13(manifestPath)));
|
|
10306
|
+
for (const rel of sorted) {
|
|
10307
|
+
const abs = resolve7(resolvedDir, rel);
|
|
10308
|
+
if (!abs.startsWith(resolvedDir + sep3)) {
|
|
10309
|
+
throw new Error(`plugin asset escapes plugin dir: ${rel}`);
|
|
10310
|
+
}
|
|
10311
|
+
let realPath;
|
|
10312
|
+
try {
|
|
10313
|
+
realPath = realpathSync2(abs);
|
|
10314
|
+
} catch {
|
|
10315
|
+
continue;
|
|
10316
|
+
}
|
|
10317
|
+
try {
|
|
10318
|
+
if (statSync7(realPath).isDirectory()) continue;
|
|
10319
|
+
} catch {
|
|
10320
|
+
continue;
|
|
10321
|
+
}
|
|
10322
|
+
if (!realPath.startsWith(resolvedDir + sep3)) {
|
|
10323
|
+
throw new Error(`plugin asset symlink escapes plugin dir: ${rel} \u2192 ${realPath}`);
|
|
10324
|
+
}
|
|
10325
|
+
hasher.update(normalizeForHash(readFileSync13(abs)));
|
|
10326
|
+
}
|
|
10327
|
+
return hasher.digest("hex");
|
|
10328
|
+
}
|
|
10329
|
+
function effectiveHash(entry) {
|
|
10330
|
+
return entry.contentHash ?? entry.hash ?? "";
|
|
10201
10331
|
}
|
|
10202
10332
|
function readPluginManifest(manifestPath) {
|
|
10203
10333
|
return PluginManifestSchema.parse(JSON.parse(readFileSync13(manifestPath, "utf-8")));
|
|
10204
10334
|
}
|
|
10205
10335
|
function findPluginSourceDir(inputPath) {
|
|
10206
10336
|
const abs = resolve7(inputPath);
|
|
10207
|
-
const direct = statSync7(abs).isDirectory() ? abs :
|
|
10208
|
-
if (
|
|
10337
|
+
const direct = statSync7(abs).isDirectory() ? abs : dirname8(abs);
|
|
10338
|
+
if (existsSync18(pluginManifestPath(direct))) return direct;
|
|
10209
10339
|
const nested = join13(direct, ".aicli-plugin");
|
|
10210
|
-
if (
|
|
10340
|
+
if (existsSync18(join13(nested, "plugin.json"))) return direct;
|
|
10211
10341
|
throw new Error(`plugin manifest not found: ${MANIFEST_RELATIVE}`);
|
|
10212
10342
|
}
|
|
10213
10343
|
function installPlugin(configDir, inputPath) {
|
|
@@ -10215,22 +10345,22 @@ function installPlugin(configDir, inputPath) {
|
|
|
10215
10345
|
const manifest = readPluginManifest(pluginManifestPath(sourceDir));
|
|
10216
10346
|
const targetDir = join13(pluginRoot(configDir), manifest.name);
|
|
10217
10347
|
mkdirSync9(pluginRoot(configDir), { recursive: true });
|
|
10218
|
-
if (
|
|
10348
|
+
if (existsSync18(targetDir)) rmSync(targetDir, { recursive: true, force: true });
|
|
10219
10349
|
cpSync(sourceDir, targetDir, { recursive: true });
|
|
10220
10350
|
const manifestPath = pluginManifestPath(targetDir);
|
|
10221
|
-
const
|
|
10351
|
+
const contentHash = hashPluginContent(targetDir, manifest);
|
|
10222
10352
|
const state2 = loadState(configDir);
|
|
10223
10353
|
const previous = state2.plugins.find((p) => p.name === manifest.name);
|
|
10224
10354
|
const next = {
|
|
10225
10355
|
name: manifest.name,
|
|
10226
10356
|
enabled: previous?.enabled ?? false,
|
|
10227
10357
|
trusted: false,
|
|
10228
|
-
|
|
10358
|
+
contentHash,
|
|
10229
10359
|
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10230
10360
|
};
|
|
10231
10361
|
state2.plugins = [...state2.plugins.filter((p) => p.name !== manifest.name), next];
|
|
10232
10362
|
saveState(configDir, state2);
|
|
10233
|
-
return { name: manifest.name, dir: targetDir, manifestPath, manifest, hash, enabled: next.enabled, trusted: false, valid: true };
|
|
10363
|
+
return { name: manifest.name, dir: targetDir, manifestPath, manifest, hash: contentHash, enabled: next.enabled, trusted: false, valid: true };
|
|
10234
10364
|
}
|
|
10235
10365
|
function setPluginEnabled(configDir, name, enabled) {
|
|
10236
10366
|
const plugin = getInstalledPlugin(configDir, name);
|
|
@@ -10240,10 +10370,10 @@ function setPluginEnabled(configDir, name, enabled) {
|
|
|
10240
10370
|
name: plugin.name,
|
|
10241
10371
|
enabled: false,
|
|
10242
10372
|
trusted: false,
|
|
10243
|
-
|
|
10373
|
+
contentHash: plugin.hash,
|
|
10244
10374
|
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10245
10375
|
};
|
|
10246
|
-
const next = { ...current, enabled,
|
|
10376
|
+
const next = { ...current, enabled, contentHash: plugin.hash, enabledAt: enabled ? (/* @__PURE__ */ new Date()).toISOString() : current.enabledAt };
|
|
10247
10377
|
state2.plugins = [...state2.plugins.filter((p) => p.name !== plugin.name), next];
|
|
10248
10378
|
saveState(configDir, state2);
|
|
10249
10379
|
return next;
|
|
@@ -10256,10 +10386,10 @@ function trustPlugin(configDir, name) {
|
|
|
10256
10386
|
name: plugin.name,
|
|
10257
10387
|
enabled: false,
|
|
10258
10388
|
trusted: false,
|
|
10259
|
-
|
|
10389
|
+
contentHash: plugin.hash,
|
|
10260
10390
|
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10261
10391
|
};
|
|
10262
|
-
const next = { ...current, trusted: true,
|
|
10392
|
+
const next = { ...current, trusted: true, contentHash: plugin.hash, trustedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
10263
10393
|
state2.plugins = [...state2.plugins.filter((p) => p.name !== plugin.name), next];
|
|
10264
10394
|
saveState(configDir, state2);
|
|
10265
10395
|
return next;
|
|
@@ -10273,7 +10403,7 @@ function getInstalledPlugin(configDir, name) {
|
|
|
10273
10403
|
}
|
|
10274
10404
|
function listInstalledPlugins(configDir) {
|
|
10275
10405
|
const root = pluginRoot(configDir);
|
|
10276
|
-
if (!
|
|
10406
|
+
if (!existsSync18(root)) return [];
|
|
10277
10407
|
const state2 = loadState(configDir);
|
|
10278
10408
|
const out = [];
|
|
10279
10409
|
for (const entry of readdirSync7(root)) {
|
|
@@ -10284,18 +10414,18 @@ function listInstalledPlugins(configDir) {
|
|
|
10284
10414
|
continue;
|
|
10285
10415
|
}
|
|
10286
10416
|
const manifestPath = pluginManifestPath(dir);
|
|
10287
|
-
if (!
|
|
10417
|
+
if (!existsSync18(manifestPath)) continue;
|
|
10288
10418
|
try {
|
|
10289
10419
|
const manifest = readPluginManifest(manifestPath);
|
|
10290
|
-
const
|
|
10420
|
+
const contentHash = hashPluginContent(dir, manifest);
|
|
10291
10421
|
const st = state2.plugins.find((p) => p.name === manifest.name);
|
|
10292
|
-
const trusted = st?.trusted === true && st
|
|
10422
|
+
const trusted = st?.trusted === true && effectiveHash(st) === contentHash;
|
|
10293
10423
|
out.push({
|
|
10294
10424
|
name: manifest.name,
|
|
10295
10425
|
dir,
|
|
10296
10426
|
manifestPath,
|
|
10297
10427
|
manifest,
|
|
10298
|
-
hash,
|
|
10428
|
+
hash: contentHash,
|
|
10299
10429
|
enabled: st?.enabled === true,
|
|
10300
10430
|
trusted,
|
|
10301
10431
|
valid: true
|
|
@@ -10324,7 +10454,7 @@ function uniqueDirs(plugin, rels) {
|
|
|
10324
10454
|
for (const rel of rels) {
|
|
10325
10455
|
const abs = resolve7(plugin.dir, rel);
|
|
10326
10456
|
if (!abs.startsWith(resolve7(plugin.dir))) continue;
|
|
10327
|
-
if (
|
|
10457
|
+
if (existsSync18(abs)) dirs.add(statSync7(abs).isDirectory() ? abs : dirname8(abs));
|
|
10328
10458
|
}
|
|
10329
10459
|
return [...dirs];
|
|
10330
10460
|
}
|
|
@@ -10487,7 +10617,7 @@ function parseAgentConfig(raw, source, path3) {
|
|
|
10487
10617
|
return cfg;
|
|
10488
10618
|
}
|
|
10489
10619
|
function loadAgentDir(dir, source) {
|
|
10490
|
-
if (!
|
|
10620
|
+
if (!existsSync19(dir)) return [];
|
|
10491
10621
|
const out = [];
|
|
10492
10622
|
for (const file of readdirSync8(dir)) {
|
|
10493
10623
|
if (!file.endsWith(".json")) continue;
|
|
@@ -11219,13 +11349,13 @@ var taskStopTool = {
|
|
|
11219
11349
|
|
|
11220
11350
|
// src/tools/builtin/git-tools.ts
|
|
11221
11351
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
11222
|
-
import { existsSync as
|
|
11352
|
+
import { existsSync as existsSync20 } from "fs";
|
|
11223
11353
|
import { join as join15 } from "path";
|
|
11224
11354
|
function assertGitRepo(cwd) {
|
|
11225
11355
|
let dir = cwd;
|
|
11226
11356
|
const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
|
|
11227
11357
|
while (dir && dir !== root) {
|
|
11228
|
-
if (
|
|
11358
|
+
if (existsSync20(join15(dir, ".git"))) return;
|
|
11229
11359
|
const parent = join15(dir, "..");
|
|
11230
11360
|
if (parent === dir) break;
|
|
11231
11361
|
dir = parent;
|
|
@@ -11491,7 +11621,7 @@ ${commitOutput.trim()}`;
|
|
|
11491
11621
|
};
|
|
11492
11622
|
|
|
11493
11623
|
// src/tools/builtin/notebook-edit.ts
|
|
11494
|
-
import { readFileSync as readFileSync15, existsSync as
|
|
11624
|
+
import { readFileSync as readFileSync15, existsSync as existsSync21 } from "fs";
|
|
11495
11625
|
import { writeFile } from "fs/promises";
|
|
11496
11626
|
import { resolve as resolve8, extname as extname2 } from "path";
|
|
11497
11627
|
var notebookEditTool = {
|
|
@@ -11546,7 +11676,7 @@ var notebookEditTool = {
|
|
|
11546
11676
|
if (extname2(absPath).toLowerCase() !== ".ipynb") {
|
|
11547
11677
|
throw new ToolError("notebook_edit", "path must point to a .ipynb file");
|
|
11548
11678
|
}
|
|
11549
|
-
if (!
|
|
11679
|
+
if (!existsSync21(absPath)) {
|
|
11550
11680
|
throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
|
|
11551
11681
|
}
|
|
11552
11682
|
const raw = readFileSync15(absPath, "utf-8");
|
|
@@ -11966,7 +12096,7 @@ function estimateToolDefinitionTokens(def) {
|
|
|
11966
12096
|
|
|
11967
12097
|
// src/tools/registry.ts
|
|
11968
12098
|
import { pathToFileURL } from "url";
|
|
11969
|
-
import { existsSync as
|
|
12099
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync10, readdirSync as readdirSync9 } from "fs";
|
|
11970
12100
|
import { join as join16 } from "path";
|
|
11971
12101
|
var ToolRegistry = class {
|
|
11972
12102
|
tools = /* @__PURE__ */ new Map();
|
|
@@ -12128,7 +12258,7 @@ var ToolRegistry = class {
|
|
|
12128
12258
|
* Returns the number of successfully loaded plugins.
|
|
12129
12259
|
*/
|
|
12130
12260
|
async loadPlugins(pluginsDir, allowPlugins = false) {
|
|
12131
|
-
if (!
|
|
12261
|
+
if (!existsSync22(pluginsDir)) {
|
|
12132
12262
|
try {
|
|
12133
12263
|
mkdirSync10(pluginsDir, { recursive: true });
|
|
12134
12264
|
} catch {
|
|
@@ -12705,12 +12835,12 @@ var McpManager = class {
|
|
|
12705
12835
|
};
|
|
12706
12836
|
|
|
12707
12837
|
// src/skills/manager.ts
|
|
12708
|
-
import { existsSync as
|
|
12838
|
+
import { existsSync as existsSync23, readdirSync as readdirSync10, mkdirSync as mkdirSync11, statSync as statSync8 } from "fs";
|
|
12709
12839
|
import { join as join17 } from "path";
|
|
12710
12840
|
|
|
12711
12841
|
// src/skills/types.ts
|
|
12712
12842
|
import { readFileSync as readFileSync16 } from "fs";
|
|
12713
|
-
import { basename as
|
|
12843
|
+
import { basename as basename7 } from "path";
|
|
12714
12844
|
function parseSimpleYaml(yaml) {
|
|
12715
12845
|
const result = {};
|
|
12716
12846
|
for (const line of yaml.split("\n")) {
|
|
@@ -12739,7 +12869,7 @@ function parseSkillFile(filePath) {
|
|
|
12739
12869
|
if (!frontmatterMatch) {
|
|
12740
12870
|
return {
|
|
12741
12871
|
meta: {
|
|
12742
|
-
name:
|
|
12872
|
+
name: basename7(filePath, ".md"),
|
|
12743
12873
|
description: ""
|
|
12744
12874
|
},
|
|
12745
12875
|
content: raw.trim(),
|
|
@@ -12750,7 +12880,7 @@ function parseSkillFile(filePath) {
|
|
|
12750
12880
|
const parsed = parseSimpleYaml(yaml);
|
|
12751
12881
|
return {
|
|
12752
12882
|
meta: {
|
|
12753
|
-
name: parsed["name"] ??
|
|
12883
|
+
name: parsed["name"] ?? basename7(filePath, ".md"),
|
|
12754
12884
|
description: parsed["description"] ?? "",
|
|
12755
12885
|
tools: parsed["tools"] ? parseYamlArray(parsed["tools"]) : void 0
|
|
12756
12886
|
},
|
|
@@ -12779,7 +12909,7 @@ var SkillManager = class {
|
|
|
12779
12909
|
return this.skills.size;
|
|
12780
12910
|
}
|
|
12781
12911
|
loadSkillDir(dir, createIfMissing) {
|
|
12782
|
-
if (!
|
|
12912
|
+
if (!existsSync23(dir)) {
|
|
12783
12913
|
if (createIfMissing) {
|
|
12784
12914
|
try {
|
|
12785
12915
|
mkdirSync11(dir, { recursive: true });
|
|
@@ -12803,7 +12933,7 @@ var SkillManager = class {
|
|
|
12803
12933
|
try {
|
|
12804
12934
|
if (statSync8(fullPath).isDirectory()) {
|
|
12805
12935
|
const skillMd = join17(fullPath, "SKILL.md");
|
|
12806
|
-
if (
|
|
12936
|
+
if (existsSync23(skillMd)) filePath = skillMd;
|
|
12807
12937
|
else continue;
|
|
12808
12938
|
} else {
|
|
12809
12939
|
continue;
|
|
@@ -12862,7 +12992,7 @@ var SkillManager = class {
|
|
|
12862
12992
|
// src/web/tool-executor-web.ts
|
|
12863
12993
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
12864
12994
|
import { tmpdir as tmpdir2 } from "os";
|
|
12865
|
-
import { existsSync as
|
|
12995
|
+
import { existsSync as existsSync24, readFileSync as readFileSync17 } from "fs";
|
|
12866
12996
|
var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
12867
12997
|
constructor(registry, ws) {
|
|
12868
12998
|
this.registry = registry;
|
|
@@ -13001,7 +13131,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
13001
13131
|
if (call.name === "write_file") {
|
|
13002
13132
|
const filePath = String(call.arguments["path"] ?? "");
|
|
13003
13133
|
const newContent = String(call.arguments["content"] ?? "");
|
|
13004
|
-
if (filePath &&
|
|
13134
|
+
if (filePath && existsSync24(filePath)) {
|
|
13005
13135
|
try {
|
|
13006
13136
|
const old = readFileSync17(filePath, "utf-8");
|
|
13007
13137
|
return renderDiff(old, newContent, { filePath });
|
|
@@ -13159,12 +13289,13 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
13159
13289
|
return { callId: call.id, content: message, isError: true };
|
|
13160
13290
|
}
|
|
13161
13291
|
}
|
|
13162
|
-
|
|
13292
|
+
const requiresHardConfirmation = permission.action === "confirm" && permission.hardBoundary === true;
|
|
13293
|
+
if ((networkPermission?.action === "confirm" || requiresHardConfirmation) && dangerLevel === "safe") {
|
|
13163
13294
|
this.sendToolCallStart(call);
|
|
13164
13295
|
toolCallStarted = true;
|
|
13165
13296
|
const confirmed = await this.confirm(call, "write");
|
|
13166
13297
|
if (!confirmed) {
|
|
13167
|
-
const rejectionMsg = `[User cancelled] The user declined
|
|
13298
|
+
const rejectionMsg = `[User cancelled] The user declined permission for ${call.name}. Do not retry without asking.`;
|
|
13168
13299
|
this.sendToolCallResult(call, rejectionMsg, true);
|
|
13169
13300
|
return { callId: call.id, content: rejectionMsg, isError: true };
|
|
13170
13301
|
}
|
|
@@ -13181,7 +13312,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
13181
13312
|
this.sendToolCallResult(call, rejectionMsg, true);
|
|
13182
13313
|
return { callId: call.id, content: rejectionMsg, isError: true };
|
|
13183
13314
|
}
|
|
13184
|
-
if (classification.decision === "auto-approve") {
|
|
13315
|
+
if (classification.decision === "auto-approve" && !requiresHardConfirmation) {
|
|
13185
13316
|
this.send({ type: "info", message: `\u26A1 Auto-approved (/auto: ${classification.ruleId})` });
|
|
13186
13317
|
try {
|
|
13187
13318
|
const rawContent = await runTool(tool, call.arguments, call.name);
|
|
@@ -13199,7 +13330,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
13199
13330
|
}
|
|
13200
13331
|
}
|
|
13201
13332
|
}
|
|
13202
|
-
if (this.sessionAutoApprove && dangerLevel === "write") {
|
|
13333
|
+
if (this.sessionAutoApprove && dangerLevel === "write" && !requiresHardConfirmation) {
|
|
13203
13334
|
try {
|
|
13204
13335
|
const rawContent = await runTool(tool, call.arguments, call.name);
|
|
13205
13336
|
const content = truncateOutput(rawContent, call.name);
|
|
@@ -13306,7 +13437,7 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
13306
13437
|
};
|
|
13307
13438
|
|
|
13308
13439
|
// src/repl/dev-state.ts
|
|
13309
|
-
import { existsSync as
|
|
13440
|
+
import { existsSync as existsSync25, readFileSync as readFileSync18, unlinkSync as unlinkSync5, mkdirSync as mkdirSync12 } from "fs";
|
|
13310
13441
|
import { join as join18 } from "path";
|
|
13311
13442
|
import { homedir as homedir7 } from "os";
|
|
13312
13443
|
function getDevStatePath() {
|
|
@@ -13314,7 +13445,7 @@ function getDevStatePath() {
|
|
|
13314
13445
|
}
|
|
13315
13446
|
function loadDevState() {
|
|
13316
13447
|
const path3 = getDevStatePath();
|
|
13317
|
-
if (!
|
|
13448
|
+
if (!existsSync25(path3)) return null;
|
|
13318
13449
|
const content = readFileSync18(path3, "utf-8").trim();
|
|
13319
13450
|
return content || null;
|
|
13320
13451
|
}
|
|
@@ -13572,8 +13703,8 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
|
|
|
13572
13703
|
}
|
|
13573
13704
|
|
|
13574
13705
|
// src/core/context-files.ts
|
|
13575
|
-
import { existsSync as
|
|
13576
|
-
import { join as join19, relative as
|
|
13706
|
+
import { existsSync as existsSync26, readFileSync as readFileSync19 } from "fs";
|
|
13707
|
+
import { join as join19, relative as relative4, resolve as resolve9 } from "path";
|
|
13577
13708
|
function uniqueNonEmpty(values) {
|
|
13578
13709
|
const seen = /* @__PURE__ */ new Set();
|
|
13579
13710
|
const out = [];
|
|
@@ -13589,14 +13720,14 @@ function buildContextFileCandidates(fallbackFilenames = []) {
|
|
|
13589
13720
|
return uniqueNonEmpty([...CONTEXT_FILE_CANDIDATES, ...fallbackFilenames]);
|
|
13590
13721
|
}
|
|
13591
13722
|
function displayPath(filePath, cwd) {
|
|
13592
|
-
const rel =
|
|
13723
|
+
const rel = relative4(cwd, filePath);
|
|
13593
13724
|
if (rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\")) {
|
|
13594
13725
|
return rel;
|
|
13595
13726
|
}
|
|
13596
13727
|
return filePath;
|
|
13597
13728
|
}
|
|
13598
13729
|
function isInsideOrEqual(parent, child) {
|
|
13599
|
-
const rel =
|
|
13730
|
+
const rel = relative4(resolve9(parent), resolve9(child));
|
|
13600
13731
|
return rel === "" || !!rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\");
|
|
13601
13732
|
}
|
|
13602
13733
|
function readContextFile(level, filePath, cwd, maxBytes) {
|
|
@@ -13651,7 +13782,7 @@ function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
|
13651
13782
|
const skipped = [];
|
|
13652
13783
|
for (const candidate of candidates) {
|
|
13653
13784
|
const filePath = join19(dir, candidate);
|
|
13654
|
-
if (!
|
|
13785
|
+
if (!existsSync26(filePath)) continue;
|
|
13655
13786
|
const result = readContextFile(level, filePath, cwd, maxBytes);
|
|
13656
13787
|
if (result.skipped) skipped.push(result.skipped);
|
|
13657
13788
|
if (result.layer) return { layer: result.layer, skipped };
|
|
@@ -13678,7 +13809,7 @@ function loadContextFiles(options) {
|
|
|
13678
13809
|
});
|
|
13679
13810
|
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
13680
13811
|
}
|
|
13681
|
-
if (!
|
|
13812
|
+
if (!existsSync26(filePath)) {
|
|
13682
13813
|
skipped.push({
|
|
13683
13814
|
level: "single",
|
|
13684
13815
|
filePath,
|
|
@@ -13729,11 +13860,11 @@ function loadContextFiles(options) {
|
|
|
13729
13860
|
}
|
|
13730
13861
|
|
|
13731
13862
|
// src/web/session-handler.ts
|
|
13732
|
-
import { mkdirSync as
|
|
13863
|
+
import { mkdirSync as mkdirSync15, createWriteStream as createWriteStream2 } from "fs";
|
|
13733
13864
|
import { join as join26, dirname as dirname10 } from "path";
|
|
13734
13865
|
|
|
13735
13866
|
// src/repl/commands/project-init.ts
|
|
13736
|
-
import { existsSync as
|
|
13867
|
+
import { existsSync as existsSync27, readFileSync as readFileSync20, readdirSync as readdirSync11, statSync as statSync9 } from "fs";
|
|
13737
13868
|
import { join as join20 } from "path";
|
|
13738
13869
|
var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
13739
13870
|
"node_modules",
|
|
@@ -13817,7 +13948,7 @@ function scanProject(cwd) {
|
|
|
13817
13948
|
configFiles: [],
|
|
13818
13949
|
directoryStructure: ""
|
|
13819
13950
|
};
|
|
13820
|
-
const check = (file) =>
|
|
13951
|
+
const check = (file) => existsSync27(join20(cwd, file));
|
|
13821
13952
|
const configCandidates = [
|
|
13822
13953
|
"package.json",
|
|
13823
13954
|
"tsconfig.json",
|
|
@@ -14619,8 +14750,8 @@ async function handlePlan(args, ctx) {
|
|
|
14619
14750
|
}
|
|
14620
14751
|
|
|
14621
14752
|
// src/web/commands/_shared.ts
|
|
14622
|
-
import { existsSync as
|
|
14623
|
-
import { join as join24, resolve as resolve10, dirname as
|
|
14753
|
+
import { existsSync as existsSync30, readFileSync as readFileSync22, writeFileSync as writeFileSync3, mkdirSync as mkdirSync14, readdirSync as readdirSync13, statSync as statSync12, createWriteStream } from "fs";
|
|
14754
|
+
import { join as join24, resolve as resolve10, dirname as dirname9 } from "path";
|
|
14624
14755
|
|
|
14625
14756
|
// src/cli/review-prompts.ts
|
|
14626
14757
|
function buildReviewPrompt(diff, gitContextStr, detailed) {
|
|
@@ -14692,13 +14823,13 @@ function readGitDiff(options = {}) {
|
|
|
14692
14823
|
}
|
|
14693
14824
|
|
|
14694
14825
|
// src/diagnostics/doctor-report.ts
|
|
14695
|
-
import { existsSync as
|
|
14826
|
+
import { existsSync as existsSync29, statSync as statSync11 } from "fs";
|
|
14696
14827
|
import { join as join23 } from "path";
|
|
14697
14828
|
import { arch as arch2, platform as platform7, release as release2 } from "os";
|
|
14698
14829
|
|
|
14699
14830
|
// src/diagnostics/crash-log.ts
|
|
14700
14831
|
import {
|
|
14701
|
-
existsSync as
|
|
14832
|
+
existsSync as existsSync28,
|
|
14702
14833
|
mkdirSync as mkdirSync13,
|
|
14703
14834
|
readdirSync as readdirSync12,
|
|
14704
14835
|
readFileSync as readFileSync21,
|
|
@@ -14719,7 +14850,7 @@ function truncate(s, n) {
|
|
|
14719
14850
|
}
|
|
14720
14851
|
function listRecentCrashes(limit = 10, configDir) {
|
|
14721
14852
|
const dir = getLogsDir(configDir);
|
|
14722
|
-
if (!
|
|
14853
|
+
if (!existsSync28(dir)) return [];
|
|
14723
14854
|
const files = [];
|
|
14724
14855
|
let names;
|
|
14725
14856
|
try {
|
|
@@ -14752,7 +14883,7 @@ function listRecentCrashes(limit = 10, configDir) {
|
|
|
14752
14883
|
}
|
|
14753
14884
|
function getConfigDirUsage(configDir) {
|
|
14754
14885
|
const base = configDir ?? join22(homedir8(), CONFIG_DIR_NAME);
|
|
14755
|
-
if (!
|
|
14886
|
+
if (!existsSync28(base)) return { totalBytes: 0, entries: [] };
|
|
14756
14887
|
const entries = [];
|
|
14757
14888
|
let total = 0;
|
|
14758
14889
|
let names;
|
|
@@ -14787,7 +14918,7 @@ function dirSize(path3) {
|
|
|
14787
14918
|
|
|
14788
14919
|
// src/diagnostics/doctor-report.ts
|
|
14789
14920
|
function checkFile(label, path3) {
|
|
14790
|
-
const exists =
|
|
14921
|
+
const exists = existsSync29(path3);
|
|
14791
14922
|
let sizeBytes = null;
|
|
14792
14923
|
if (exists) {
|
|
14793
14924
|
try {
|
|
@@ -14885,7 +15016,7 @@ function buildDoctorReport(options) {
|
|
|
14885
15016
|
mcp: {
|
|
14886
15017
|
enabled: config.get("mcpEnabled"),
|
|
14887
15018
|
configured: Object.keys(mcpServers).length,
|
|
14888
|
-
projectConfigExists:
|
|
15019
|
+
projectConfigExists: existsSync29(join23(projectRoot, MCP_PROJECT_CONFIG_NAME)),
|
|
14889
15020
|
statuses: options.mcpStatuses ?? []
|
|
14890
15021
|
},
|
|
14891
15022
|
recentCrashes: listRecentCrashes(5, configDir),
|
|
@@ -15755,7 +15886,7 @@ ${activated.meta.description || ""}` });
|
|
|
15755
15886
|
async function handleCommands(_args, ctx) {
|
|
15756
15887
|
const configDir = ctx.config.getConfigDir();
|
|
15757
15888
|
const commandsDir = join24(configDir, CUSTOM_COMMANDS_DIR_NAME);
|
|
15758
|
-
if (!
|
|
15889
|
+
if (!existsSync30(commandsDir)) {
|
|
15759
15890
|
ctx.send({ type: "info", message: `No custom commands directory.
|
|
15760
15891
|
Create: ${commandsDir}/ with .md files.` });
|
|
15761
15892
|
return;
|
|
@@ -15855,7 +15986,7 @@ async function handleSecurityReview(args, ctx) {
|
|
|
15855
15986
|
async function handleTest(args, ctx) {
|
|
15856
15987
|
ctx.send({ type: "info", message: "\u{1F9EA} Running tests..." });
|
|
15857
15988
|
try {
|
|
15858
|
-
const { executeTests } = await import("./run-tests-
|
|
15989
|
+
const { executeTests } = await import("./run-tests-DI6WEYHX.js");
|
|
15859
15990
|
const argStr = args.join(" ").trim();
|
|
15860
15991
|
let testArgs = {};
|
|
15861
15992
|
if (argStr) {
|
|
@@ -15885,7 +16016,7 @@ async function handleDiff(args, ctx) {
|
|
|
15885
16016
|
let modifiedFiles = 0;
|
|
15886
16017
|
const diffLines2 = [];
|
|
15887
16018
|
for (const [filePath, { earliest }] of fileMap) {
|
|
15888
|
-
const currentContent =
|
|
16019
|
+
const currentContent = existsSync30(filePath) ? (() => {
|
|
15889
16020
|
try {
|
|
15890
16021
|
return readFileSync22(filePath, "utf-8");
|
|
15891
16022
|
} catch {
|
|
@@ -15924,7 +16055,7 @@ async function handleInit(args, ctx) {
|
|
|
15924
16055
|
const cwd = process.cwd();
|
|
15925
16056
|
const targetPath = join24(cwd, "AICLI.md");
|
|
15926
16057
|
const force = args.includes("--force");
|
|
15927
|
-
if (
|
|
16058
|
+
if (existsSync30(targetPath) && !force) {
|
|
15928
16059
|
ctx.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
|
|
15929
16060
|
Use /init --force to overwrite.` });
|
|
15930
16061
|
return;
|
|
@@ -16108,7 +16239,7 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
|
|
|
16108
16239
|
return;
|
|
16109
16240
|
}
|
|
16110
16241
|
const dirPath = resolve10(sub);
|
|
16111
|
-
if (!
|
|
16242
|
+
if (!existsSync30(dirPath)) {
|
|
16112
16243
|
ctx.send({ type: "error", message: `Directory not found: ${dirPath}` });
|
|
16113
16244
|
return;
|
|
16114
16245
|
}
|
|
@@ -17183,7 +17314,7 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
|
|
|
17183
17314
|
this.send({ type: "response_done", content, usage });
|
|
17184
17315
|
if (pendingTeeSave && isCleanDocumentBody(content)) {
|
|
17185
17316
|
try {
|
|
17186
|
-
|
|
17317
|
+
mkdirSync15(dirname10(pendingTeeSave), { recursive: true });
|
|
17187
17318
|
const bodyToSave = stripOuterCodeFence(content);
|
|
17188
17319
|
atomicWriteFileSync(pendingTeeSave, bodyToSave);
|
|
17189
17320
|
undoStack.push(pendingTeeSave, `save_last_response (deferred): ${pendingTeeSave}`);
|
|
@@ -17359,8 +17490,8 @@ ${summaryContent}`,
|
|
|
17359
17490
|
let isError = false;
|
|
17360
17491
|
let summary;
|
|
17361
17492
|
try {
|
|
17362
|
-
|
|
17363
|
-
fileStream =
|
|
17493
|
+
mkdirSync15(dirname10(saveToFile), { recursive: true });
|
|
17494
|
+
fileStream = createWriteStream2(saveToFile);
|
|
17364
17495
|
const teeSystemPrompt = stripToolCallReminder(systemPrompt ?? "") + CONTENT_ONLY_STREAM_REMINDER;
|
|
17365
17496
|
const teeExtraMessages = extraMessages.length > 0 ? [...extraMessages, { role: "user", content: TEE_FINAL_USER_NUDGE }] : [{ role: "user", content: TEE_FINAL_USER_NUDGE }];
|
|
17366
17497
|
const chatRequest = {
|
|
@@ -17934,8 +18065,8 @@ async function setupProxy(configProxy) {
|
|
|
17934
18065
|
}
|
|
17935
18066
|
|
|
17936
18067
|
// src/web/auth.ts
|
|
17937
|
-
import { existsSync as existsSync32, readFileSync as
|
|
17938
|
-
import { join as join27 } from "path";
|
|
18068
|
+
import { existsSync as existsSync32, readFileSync as readFileSync24, writeFileSync as writeFileSync5, mkdirSync as mkdirSync16, readdirSync as readdirSync15, copyFileSync } from "fs";
|
|
18069
|
+
import { isAbsolute as isAbsolute3, join as join27, relative as relative5 } from "path";
|
|
17939
18070
|
import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
|
|
17940
18071
|
var USERS_FILE = "users.json";
|
|
17941
18072
|
var TOKEN_EXPIRY_HOURS = 24;
|
|
@@ -17981,7 +18112,7 @@ var AuthManager = class {
|
|
|
17981
18112
|
const passwordHash = this.hashPassword(password, salt);
|
|
17982
18113
|
const dataDir = join27(USERS_DIR, username);
|
|
17983
18114
|
const fullDataDir = join27(this.baseDir, dataDir);
|
|
17984
|
-
|
|
18115
|
+
mkdirSync16(join27(fullDataDir, "history"), { recursive: true });
|
|
17985
18116
|
const user = {
|
|
17986
18117
|
username,
|
|
17987
18118
|
passwordHash,
|
|
@@ -18135,23 +18266,23 @@ var AuthManager = class {
|
|
|
18135
18266
|
const globalConfig = join27(this.baseDir, "config.json");
|
|
18136
18267
|
if (existsSync32(globalConfig)) {
|
|
18137
18268
|
try {
|
|
18138
|
-
const content =
|
|
18139
|
-
|
|
18269
|
+
const content = readFileSync24(globalConfig, "utf-8");
|
|
18270
|
+
writeFileSync5(join27(userDir, "config.json"), content, "utf-8");
|
|
18140
18271
|
} catch {
|
|
18141
18272
|
}
|
|
18142
18273
|
}
|
|
18143
18274
|
const globalMemory = join27(this.baseDir, "memory.md");
|
|
18144
18275
|
if (existsSync32(globalMemory)) {
|
|
18145
18276
|
try {
|
|
18146
|
-
const content =
|
|
18147
|
-
|
|
18277
|
+
const content = readFileSync24(globalMemory, "utf-8");
|
|
18278
|
+
writeFileSync5(join27(userDir, "memory.md"), content, "utf-8");
|
|
18148
18279
|
} catch {
|
|
18149
18280
|
}
|
|
18150
18281
|
}
|
|
18151
18282
|
const globalHistory = join27(this.baseDir, "history");
|
|
18152
18283
|
if (existsSync32(globalHistory)) {
|
|
18153
18284
|
try {
|
|
18154
|
-
const files =
|
|
18285
|
+
const files = readdirSync15(globalHistory).filter((f) => f.endsWith(".json"));
|
|
18155
18286
|
const userHistory = join27(userDir, "history");
|
|
18156
18287
|
for (const f of files) {
|
|
18157
18288
|
try {
|
|
@@ -18168,8 +18299,17 @@ var AuthManager = class {
|
|
|
18168
18299
|
loadOrCreate() {
|
|
18169
18300
|
if (existsSync32(this.usersFile)) {
|
|
18170
18301
|
try {
|
|
18171
|
-
|
|
18172
|
-
|
|
18302
|
+
const parsed = JSON.parse(readFileSync24(this.usersFile, "utf-8"));
|
|
18303
|
+
if (!this.isValidUsersDB(parsed)) {
|
|
18304
|
+
throw new Error("database structure is invalid");
|
|
18305
|
+
}
|
|
18306
|
+
return parsed;
|
|
18307
|
+
} catch (err) {
|
|
18308
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
18309
|
+
throw new Error(
|
|
18310
|
+
`Authentication database is unreadable or corrupt: ${this.usersFile} (${detail}). Refusing to start without authentication; restore or remove the file explicitly.`,
|
|
18311
|
+
{ cause: err }
|
|
18312
|
+
);
|
|
18173
18313
|
}
|
|
18174
18314
|
}
|
|
18175
18315
|
const db = {
|
|
@@ -18180,11 +18320,32 @@ var AuthManager = class {
|
|
|
18180
18320
|
this.saveDB(db);
|
|
18181
18321
|
return db;
|
|
18182
18322
|
}
|
|
18323
|
+
isValidUsersDB(value) {
|
|
18324
|
+
if (!value || typeof value !== "object") return false;
|
|
18325
|
+
const db = value;
|
|
18326
|
+
if (db.version !== 1 || typeof db.secret !== "string" || db.secret.length < 32 || !Array.isArray(db.users)) {
|
|
18327
|
+
return false;
|
|
18328
|
+
}
|
|
18329
|
+
const usernames = /* @__PURE__ */ new Set();
|
|
18330
|
+
for (const user of db.users) {
|
|
18331
|
+
if (!user || typeof user !== "object") return false;
|
|
18332
|
+
const record = user;
|
|
18333
|
+
if (typeof record.username !== "string" || !/^[a-z0-9_-]{2,32}$/.test(record.username) || usernames.has(record.username) || typeof record.passwordHash !== "string" || record.passwordHash.length === 0 || typeof record.salt !== "string" || record.salt.length === 0 || typeof record.createdAt !== "string" || typeof record.dataDir !== "string" || record.dataDir.length === 0 || isAbsolute3(record.dataDir)) {
|
|
18334
|
+
return false;
|
|
18335
|
+
}
|
|
18336
|
+
const dataDirFromBase = relative5(this.baseDir, join27(this.baseDir, record.dataDir));
|
|
18337
|
+
if (dataDirFromBase.startsWith("..") || isAbsolute3(dataDirFromBase)) return false;
|
|
18338
|
+
if (record.hashVersion !== void 0 && record.hashVersion !== 1 && record.hashVersion !== 2) return false;
|
|
18339
|
+
if (record.tokensRevokedBefore !== void 0 && !Number.isFinite(record.tokensRevokedBefore)) return false;
|
|
18340
|
+
usernames.add(record.username);
|
|
18341
|
+
}
|
|
18342
|
+
return true;
|
|
18343
|
+
}
|
|
18183
18344
|
save() {
|
|
18184
18345
|
this.saveDB(this.db);
|
|
18185
18346
|
}
|
|
18186
18347
|
saveDB(db) {
|
|
18187
|
-
|
|
18348
|
+
mkdirSync16(this.baseDir, { recursive: true });
|
|
18188
18349
|
atomicWriteFileSync(this.usersFile, JSON.stringify(db, null, 2));
|
|
18189
18350
|
}
|
|
18190
18351
|
/** Legacy hash — kept only for migrating old users (v0.2.x) */
|
|
@@ -18440,9 +18601,9 @@ async function startWebServer(options = {}) {
|
|
|
18440
18601
|
const prefix = req.query.prefix || "";
|
|
18441
18602
|
const targetDir = join28(cwd, prefix);
|
|
18442
18603
|
try {
|
|
18443
|
-
const canonicalTarget =
|
|
18444
|
-
const canonicalCwd =
|
|
18445
|
-
if (!canonicalTarget.startsWith(canonicalCwd +
|
|
18604
|
+
const canonicalTarget = realpathSync3(resolve13(targetDir));
|
|
18605
|
+
const canonicalCwd = realpathSync3(resolve13(cwd));
|
|
18606
|
+
if (!canonicalTarget.startsWith(canonicalCwd + sep4) && canonicalTarget !== canonicalCwd) {
|
|
18446
18607
|
res.json({ files: [] });
|
|
18447
18608
|
return;
|
|
18448
18609
|
}
|
|
@@ -18452,10 +18613,10 @@ async function startWebServer(options = {}) {
|
|
|
18452
18613
|
}
|
|
18453
18614
|
try {
|
|
18454
18615
|
const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "dist-cjs", "release", "__pycache__", ".next", ".nuxt", "coverage", ".cache"]);
|
|
18455
|
-
const entries =
|
|
18616
|
+
const entries = readdirSync16(targetDir, { withFileTypes: true });
|
|
18456
18617
|
const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
|
|
18457
18618
|
name: e.name,
|
|
18458
|
-
path:
|
|
18619
|
+
path: relative6(cwd, join28(targetDir, e.name)).replace(/\\/g, "/"),
|
|
18459
18620
|
isDir: e.isDirectory()
|
|
18460
18621
|
}));
|
|
18461
18622
|
res.json({ files });
|
|
@@ -18501,9 +18662,9 @@ async function startWebServer(options = {}) {
|
|
|
18501
18662
|
return;
|
|
18502
18663
|
}
|
|
18503
18664
|
try {
|
|
18504
|
-
const canonicalFile =
|
|
18505
|
-
const canonicalDir =
|
|
18506
|
-
if (!canonicalFile.startsWith(canonicalDir +
|
|
18665
|
+
const canonicalFile = realpathSync3(filePath);
|
|
18666
|
+
const canonicalDir = realpathSync3(histDir);
|
|
18667
|
+
if (!canonicalFile.startsWith(canonicalDir + sep4)) {
|
|
18507
18668
|
res.status(404).json({ error: "Session not found" });
|
|
18508
18669
|
return;
|
|
18509
18670
|
}
|
|
@@ -18511,7 +18672,7 @@ async function startWebServer(options = {}) {
|
|
|
18511
18672
|
res.status(404).json({ error: "Session not found" });
|
|
18512
18673
|
return;
|
|
18513
18674
|
}
|
|
18514
|
-
const data = JSON.parse(
|
|
18675
|
+
const data = JSON.parse(readFileSync25(filePath, "utf-8"));
|
|
18515
18676
|
res.json({ session: data });
|
|
18516
18677
|
} catch (err) {
|
|
18517
18678
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
@@ -18526,9 +18687,9 @@ async function startWebServer(options = {}) {
|
|
|
18526
18687
|
const cwd = process.cwd();
|
|
18527
18688
|
const fullPath = resolve13(join28(cwd, filePath));
|
|
18528
18689
|
try {
|
|
18529
|
-
const canonicalFull =
|
|
18530
|
-
const canonicalCwd =
|
|
18531
|
-
if (!canonicalFull.startsWith(canonicalCwd +
|
|
18690
|
+
const canonicalFull = realpathSync3(fullPath);
|
|
18691
|
+
const canonicalCwd = realpathSync3(resolve13(cwd));
|
|
18692
|
+
if (!canonicalFull.startsWith(canonicalCwd + sep4) && canonicalFull !== canonicalCwd) {
|
|
18532
18693
|
res.json({ error: "Access denied" });
|
|
18533
18694
|
return;
|
|
18534
18695
|
}
|
|
@@ -18537,12 +18698,12 @@ async function startWebServer(options = {}) {
|
|
|
18537
18698
|
return;
|
|
18538
18699
|
}
|
|
18539
18700
|
try {
|
|
18540
|
-
const stat =
|
|
18701
|
+
const stat = statSync14(fullPath);
|
|
18541
18702
|
if (stat.size > 512 * 1024) {
|
|
18542
18703
|
res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
|
|
18543
18704
|
return;
|
|
18544
18705
|
}
|
|
18545
|
-
const content =
|
|
18706
|
+
const content = readFileSync25(fullPath, "utf-8");
|
|
18546
18707
|
res.json({ content, size: stat.size });
|
|
18547
18708
|
} catch {
|
|
18548
18709
|
res.json({ error: "Cannot read file" });
|
|
@@ -18807,7 +18968,7 @@ function loadProjectMcpConfig() {
|
|
|
18807
18968
|
const configPath = join28(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
18808
18969
|
if (!existsSync33(configPath)) return null;
|
|
18809
18970
|
try {
|
|
18810
|
-
const raw = JSON.parse(
|
|
18971
|
+
const raw = JSON.parse(readFileSync25(configPath, "utf-8"));
|
|
18811
18972
|
return raw.mcpServers ?? raw;
|
|
18812
18973
|
} catch {
|
|
18813
18974
|
return null;
|