jinzd-ai-cli 0.4.212 → 0.4.213
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 +7 -0
- package/README.zh-CN.md +7 -0
- package/dist/{batch-WIKFEOAZ.js → batch-PKY63TME.js} +2 -2
- package/dist/{chunk-IFZX26K7.js → chunk-7NTFMLJJ.js} +1 -1
- package/dist/{chunk-HW2ALWQJ.js → chunk-FGUKBIYO.js} +2 -2
- package/dist/{chunk-LU6FBJQ5.js → chunk-G3PW3AOB.js} +1 -1
- package/dist/{chunk-GBBVCZ4W.js → chunk-IWLVH32D.js} +1 -1
- package/dist/{chunk-KE4B3NOQ.js → chunk-J35V3IAH.js} +1 -1
- package/dist/{chunk-NYCBOVNF.js → chunk-MRGWPCFQ.js} +1 -1
- package/dist/{chunk-N5LB3PPL.js → chunk-THNECAAY.js} +250 -59
- package/dist/{chunk-HX6N6QEY.js → chunk-UDMU4FUF.js} +1 -1
- package/dist/{ci-X3LNUFZV.js → ci-HBZ6CIW5.js} +2 -2
- package/dist/{constants-KDWG4KR4.js → constants-CAUBTSFW.js} +1 -1
- package/dist/{doctor-cli-TJTWIW7U.js → doctor-cli-5Z5UWYW6.js} +4 -4
- package/dist/electron-server.js +473 -214
- package/dist/{hub-JWSV4MKC.js → hub-ZRADBR76.js} +1 -1
- package/dist/index.js +79 -18
- package/dist/{run-tests-D7YBY4XB.js → run-tests-7J3PNWVS.js} +1 -1
- package/dist/{run-tests-H4Q2PRX6.js → run-tests-DSOJBJWA.js} +2 -2
- package/dist/{server-OFKGDRYI.js → server-PWBWHVYB.js} +4 -4
- package/dist/{server-VNCN7AY5.js → server-U4I7RI42.js} +83 -7
- package/dist/{task-orchestrator-AL2E642M.js → task-orchestrator-RUXWRMQV.js} +4 -4
- package/dist/{usage-LB6REWPO.js → usage-2P6TIBTN.js} +2 -2
- package/dist/web/client/app.js +1 -1
- package/package.json +1 -1
package/dist/electron-server.js
CHANGED
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
VERSION,
|
|
38
38
|
buildUserIdentityPrompt,
|
|
39
39
|
runTestsTool
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-J35V3IAH.js";
|
|
41
41
|
import {
|
|
42
42
|
hasSemanticIndex,
|
|
43
43
|
semanticSearch
|
|
@@ -61,8 +61,8 @@ import {
|
|
|
61
61
|
import express from "express";
|
|
62
62
|
import { createServer } from "http";
|
|
63
63
|
import { WebSocketServer } from "ws";
|
|
64
|
-
import { join as
|
|
65
|
-
import { existsSync as
|
|
64
|
+
import { join as join21, dirname as dirname6, resolve as resolve9, relative as relative4, sep as sep3 } from "path";
|
|
65
|
+
import { existsSync as existsSync27, readFileSync as readFileSync21, readdirSync as readdirSync12, statSync as statSync10, realpathSync } from "fs";
|
|
66
66
|
import { networkInterfaces } from "os";
|
|
67
67
|
|
|
68
68
|
// src/config/config-manager.ts
|
|
@@ -5790,7 +5790,7 @@ import { dirname as dirname3 } from "path";
|
|
|
5790
5790
|
|
|
5791
5791
|
// src/tools/executor.ts
|
|
5792
5792
|
import chalk3 from "chalk";
|
|
5793
|
-
import { existsSync as
|
|
5793
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
|
|
5794
5794
|
import { tmpdir } from "os";
|
|
5795
5795
|
|
|
5796
5796
|
// src/core/readline-internal.ts
|
|
@@ -6627,6 +6627,157 @@ function flush() {
|
|
|
6627
6627
|
}
|
|
6628
6628
|
}
|
|
6629
6629
|
|
|
6630
|
+
// src/tools/action-classifier.ts
|
|
6631
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
|
|
6632
|
+
import { isAbsolute as isAbsolute2, join as join5, resolve as resolve4 } from "path";
|
|
6633
|
+
var RECENT_DENIED_LIMIT = 50;
|
|
6634
|
+
var recentlyDenied = [];
|
|
6635
|
+
function recordRecentlyDeniedAutoAction(call, classification) {
|
|
6636
|
+
recentlyDenied.unshift({
|
|
6637
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6638
|
+
tool: call.name,
|
|
6639
|
+
reason: classification.reason,
|
|
6640
|
+
ruleId: classification.ruleId,
|
|
6641
|
+
argsPreview: JSON.stringify(call.arguments).slice(0, 240)
|
|
6642
|
+
});
|
|
6643
|
+
recentlyDenied.splice(RECENT_DENIED_LIMIT);
|
|
6644
|
+
}
|
|
6645
|
+
function getRecentlyDeniedAutoActions() {
|
|
6646
|
+
return [...recentlyDenied];
|
|
6647
|
+
}
|
|
6648
|
+
function clearRecentlyDeniedAutoActions() {
|
|
6649
|
+
recentlyDenied.splice(0, recentlyDenied.length);
|
|
6650
|
+
}
|
|
6651
|
+
function normalizePath(value) {
|
|
6652
|
+
return value.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
6653
|
+
}
|
|
6654
|
+
function isInside(root, target) {
|
|
6655
|
+
const absoluteRoot = resolve4(root);
|
|
6656
|
+
const absoluteTarget = isAbsolute2(target) ? target : resolve4(root, target);
|
|
6657
|
+
const base = normalizePath(absoluteRoot);
|
|
6658
|
+
const t = normalizePath(absoluteTarget);
|
|
6659
|
+
return t === base || t.startsWith(base + "/");
|
|
6660
|
+
}
|
|
6661
|
+
function getPathArg(call) {
|
|
6662
|
+
const raw = call.arguments.path ?? call.arguments.filePath ?? call.arguments.outputPath ?? call.arguments.file;
|
|
6663
|
+
return typeof raw === "string" && raw.trim() ? raw.trim() : null;
|
|
6664
|
+
}
|
|
6665
|
+
function splitShellWords(command) {
|
|
6666
|
+
const out = [];
|
|
6667
|
+
const re = /"([^"]*)"|'([^']*)'|([^\s]+)/g;
|
|
6668
|
+
let m;
|
|
6669
|
+
while (m = re.exec(command)) out.push(m[1] ?? m[2] ?? m[3] ?? "");
|
|
6670
|
+
return out.filter(Boolean);
|
|
6671
|
+
}
|
|
6672
|
+
function readPackageManifest(root) {
|
|
6673
|
+
const p = join5(root, "package.json");
|
|
6674
|
+
if (!existsSync8(p)) return null;
|
|
6675
|
+
try {
|
|
6676
|
+
return JSON.parse(readFileSync7(p, "utf-8"));
|
|
6677
|
+
} catch {
|
|
6678
|
+
return null;
|
|
6679
|
+
}
|
|
6680
|
+
}
|
|
6681
|
+
function manifestHasDependency(root, name) {
|
|
6682
|
+
const manifest = readPackageManifest(root);
|
|
6683
|
+
if (!manifest) return false;
|
|
6684
|
+
for (const key of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
6685
|
+
const deps = manifest[key];
|
|
6686
|
+
if (deps && typeof deps === "object" && Object.prototype.hasOwnProperty.call(deps, name)) return true;
|
|
6687
|
+
}
|
|
6688
|
+
return false;
|
|
6689
|
+
}
|
|
6690
|
+
function lockfileMentions(root, name) {
|
|
6691
|
+
for (const file of ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"]) {
|
|
6692
|
+
const p = join5(root, file);
|
|
6693
|
+
if (!existsSync8(p)) continue;
|
|
6694
|
+
try {
|
|
6695
|
+
if (readFileSync7(p, "utf-8").includes(name)) return true;
|
|
6696
|
+
} catch {
|
|
6697
|
+
}
|
|
6698
|
+
}
|
|
6699
|
+
return false;
|
|
6700
|
+
}
|
|
6701
|
+
function getInstallPackages(command) {
|
|
6702
|
+
const words = splitShellWords(command);
|
|
6703
|
+
const cmd = words[0]?.toLowerCase();
|
|
6704
|
+
const sub = words[1]?.toLowerCase();
|
|
6705
|
+
if (!cmd || !sub) return [];
|
|
6706
|
+
const isInstall = cmd === "npm" && (sub === "install" || sub === "i" || sub === "add") || (cmd === "pnpm" || cmd === "yarn") && (sub === "add" || sub === "install");
|
|
6707
|
+
if (!isInstall) return [];
|
|
6708
|
+
return words.slice(2).filter((w) => !w.startsWith("-")).filter((w) => w !== "." && w !== "./").map((w) => w.replace(/^@([^/]+)\/([^@]+)(?:@.+)?$/, "@$1/$2").replace(/^([^@]+)@.+$/, "$1"));
|
|
6709
|
+
}
|
|
6710
|
+
function isPlainGitPush(command) {
|
|
6711
|
+
if (!/^\s*git\s+push\b/i.test(command)) return false;
|
|
6712
|
+
if (/--force|-f\b|\+[^\s]+/i.test(command)) return false;
|
|
6713
|
+
return !/[;&|`$<>\n\r]/.test(command);
|
|
6714
|
+
}
|
|
6715
|
+
function hasSecretExfiltrationShape(command) {
|
|
6716
|
+
return /(?:\.env|config\.json|id_rsa|id_ed25519|AWS_SECRET|AICLI_API_KEY|OPENAI_API_KEY|ANTHROPIC_API_KEY)/i.test(command) && /\b(curl|wget|scp|rsync|nc|netcat|Invoke-WebRequest|Invoke-RestMethod)\b/i.test(command);
|
|
6717
|
+
}
|
|
6718
|
+
var RuleBasedActionClassifier = class {
|
|
6719
|
+
classify(call, dangerLevel, ctx) {
|
|
6720
|
+
if (call.name === "bash") return this.classifyShell(call, dangerLevel, ctx);
|
|
6721
|
+
if (call.name === "web_fetch" || call.name === "web_search" || call.name === "google_search") {
|
|
6722
|
+
return { decision: "auto-approve", reason: "read-only HTTP tool", ruleId: "auto.readonly-http", confidence: "high" };
|
|
6723
|
+
}
|
|
6724
|
+
if (dangerLevel === "safe") {
|
|
6725
|
+
return { decision: "auto-approve", reason: "safe read-only tool", ruleId: "auto.safe-tool", confidence: "high" };
|
|
6726
|
+
}
|
|
6727
|
+
if (dangerLevel === "write") {
|
|
6728
|
+
const p = getPathArg(call);
|
|
6729
|
+
if (p && (isInside(ctx.workspaceRoot, p) || (ctx.tempDirs ?? []).some((root) => isInside(root, p)))) {
|
|
6730
|
+
return { decision: "auto-approve", reason: "explicit write inside workspace/temp roots", ruleId: "auto.workspace-write", confidence: "high" };
|
|
6731
|
+
}
|
|
6732
|
+
return { decision: "confirm", reason: "write action is not proven low-risk", ruleId: "auto.write-confirm", confidence: "medium" };
|
|
6733
|
+
}
|
|
6734
|
+
return { decision: "confirm", reason: "destructive actions require confirmation", ruleId: "auto.destructive-confirm", confidence: "high" };
|
|
6735
|
+
}
|
|
6736
|
+
classifyShell(call, dangerLevel, ctx) {
|
|
6737
|
+
const command = String(call.arguments.command ?? "");
|
|
6738
|
+
if (/\bcurl\b[^\n|;&]*\|\s*(?:sudo\s+)?(?:bash|sh|zsh)\b/i.test(command)) {
|
|
6739
|
+
return { decision: "deny", reason: "curl pipe shell execution is blocked in auto mode", ruleId: "deny.curl-pipe-shell", confidence: "high" };
|
|
6740
|
+
}
|
|
6741
|
+
if (/\bgit\s+push\b/i.test(command) && /--force|-f\b|\+[^\s]+/i.test(command)) {
|
|
6742
|
+
return { decision: "deny", reason: "force push is blocked in auto mode", ruleId: "deny.force-push", confidence: "high" };
|
|
6743
|
+
}
|
|
6744
|
+
if (hasSecretExfiltrationShape(command)) {
|
|
6745
|
+
return { decision: "deny", reason: "possible secret exfiltration is blocked in auto mode", ruleId: "deny.secret-exfiltration", confidence: "high" };
|
|
6746
|
+
}
|
|
6747
|
+
if (/\b(terraform|tofu)\s+destroy\b|\bkubectl\s+delete\b|\bcdk\s+destroy\b/i.test(command)) {
|
|
6748
|
+
return { decision: "confirm", reason: "infrastructure destroy requires confirmation", ruleId: "confirm.iac-destroy", confidence: "high" };
|
|
6749
|
+
}
|
|
6750
|
+
if (/\b(prisma|sequelize|knex|typeorm)\b[^\n]*\b(migrate|migration)\b|\bdb:migrate\b/i.test(command)) {
|
|
6751
|
+
return { decision: "confirm", reason: "database migration requires confirmation", ruleId: "confirm.database-migration", confidence: "high" };
|
|
6752
|
+
}
|
|
6753
|
+
if (/\b(vercel|netlify|flyctl|railway|wrangler|serverless|firebase)\b[^\n]*\bdeploy\b|\bdeploy\b[^\n]*\b(prod|production)\b/i.test(command)) {
|
|
6754
|
+
return { decision: "confirm", reason: "production deployment requires confirmation", ruleId: "confirm.production-deploy", confidence: "high" };
|
|
6755
|
+
}
|
|
6756
|
+
if (/\b(iam|policy|token|secret|api[-_]?key|credential|permission|role)\b/i.test(command)) {
|
|
6757
|
+
return { decision: "confirm", reason: "credential or permission change requires confirmation", ruleId: "confirm.credentials-permissions", confidence: "medium" };
|
|
6758
|
+
}
|
|
6759
|
+
if (/\b(npx|pnpm\s+dlx|yarn\s+dlx)\s+(claude|codex|aider|cursor-agent|gemini)\b/i.test(command)) {
|
|
6760
|
+
return { decision: "confirm", reason: "third-party agent loop requires confirmation", ruleId: "confirm.third-party-agent", confidence: "high" };
|
|
6761
|
+
}
|
|
6762
|
+
if (isPlainGitPush(command)) {
|
|
6763
|
+
return { decision: "auto-approve", reason: "plain git push without force", ruleId: "auto.git-push", confidence: "high" };
|
|
6764
|
+
}
|
|
6765
|
+
const pkgs = getInstallPackages(command);
|
|
6766
|
+
if (pkgs.length > 0) {
|
|
6767
|
+
const allDeclared = pkgs.every((p) => manifestHasDependency(ctx.workspaceRoot, p) || lockfileMentions(ctx.workspaceRoot, p));
|
|
6768
|
+
return allDeclared ? { decision: "auto-approve", reason: "installing dependencies already declared in manifest/lockfile", ruleId: "auto.declared-dependency-install", confidence: "medium" } : { decision: "confirm", reason: "dependency install includes undeclared packages", ruleId: "confirm.undeclared-dependency-install", confidence: "medium" };
|
|
6769
|
+
}
|
|
6770
|
+
if (/\b(curl|wget|Invoke-WebRequest|Invoke-RestMethod)\b/i.test(command) && !/[>|;&`$]/.test(command)) {
|
|
6771
|
+
return { decision: "auto-approve", reason: "read-only HTTP shell command", ruleId: "auto.readonly-http-shell", confidence: "medium" };
|
|
6772
|
+
}
|
|
6773
|
+
if (dangerLevel === "safe") {
|
|
6774
|
+
return { decision: "auto-approve", reason: "safe shell action", ruleId: "auto.safe-shell", confidence: "medium" };
|
|
6775
|
+
}
|
|
6776
|
+
return { decision: "confirm", reason: "shell action is not proven low-risk", ruleId: "confirm.shell-default", confidence: "medium" };
|
|
6777
|
+
}
|
|
6778
|
+
};
|
|
6779
|
+
var defaultActionClassifier = new RuleBasedActionClassifier();
|
|
6780
|
+
|
|
6630
6781
|
// src/tools/executor.ts
|
|
6631
6782
|
function buildErrorPreview(content) {
|
|
6632
6783
|
const meaningful = content.split("\n").filter(
|
|
@@ -6666,6 +6817,8 @@ var ToolExecutor = class {
|
|
|
6666
6817
|
* destructive 操作仍然必须逐次确认。
|
|
6667
6818
|
*/
|
|
6668
6819
|
sessionAutoApprove = false;
|
|
6820
|
+
/** Session-level Auto Mode: rule-based low-risk auto approval. */
|
|
6821
|
+
sessionAutoMode = false;
|
|
6669
6822
|
/**
|
|
6670
6823
|
* Logical session key used to scope per-session state in stateful tools
|
|
6671
6824
|
* (currently only `bash`'s persistent cwd). Web mode sets this per-tab so
|
|
@@ -6825,6 +6978,40 @@ var ToolExecutor = class {
|
|
|
6825
6978
|
};
|
|
6826
6979
|
}
|
|
6827
6980
|
}
|
|
6981
|
+
if (this.sessionAutoMode && networkPermission?.action !== "confirm") {
|
|
6982
|
+
const classification = defaultActionClassifier.classify(call, dangerLevel, {
|
|
6983
|
+
workspaceRoot: this.workspaceRoot,
|
|
6984
|
+
tempDirs: [tmpdir()]
|
|
6985
|
+
});
|
|
6986
|
+
if (classification.decision === "deny") {
|
|
6987
|
+
recordRecentlyDeniedAutoAction(call, classification);
|
|
6988
|
+
return {
|
|
6989
|
+
callId: call.id,
|
|
6990
|
+
content: `[Auto denied] ${classification.reason}. Do not retry without asking.`,
|
|
6991
|
+
isError: true
|
|
6992
|
+
};
|
|
6993
|
+
}
|
|
6994
|
+
if (classification.decision === "auto-approve") {
|
|
6995
|
+
this.printToolCall(call);
|
|
6996
|
+
if (dangerLevel === "write") this.printDiffPreview(call);
|
|
6997
|
+
console.log(theme.warning(` \u26A1 Auto-approved (/auto: ${classification.ruleId})`));
|
|
6998
|
+
try {
|
|
6999
|
+
const rawContent = await runTool(tool, call.arguments, call.name);
|
|
7000
|
+
const content = truncateOutput(rawContent, call.name);
|
|
7001
|
+
const wasTruncated = content !== rawContent;
|
|
7002
|
+
this.printToolResult(call.name, rawContent, false, wasTruncated);
|
|
7003
|
+
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
7004
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
|
|
7005
|
+
return { callId: call.id, content, isError: false };
|
|
7006
|
+
} catch (err) {
|
|
7007
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7008
|
+
this.printToolResult(call.name, message, true, false);
|
|
7009
|
+
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
7010
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
|
|
7011
|
+
return { callId: call.id, content: message, isError: true };
|
|
7012
|
+
}
|
|
7013
|
+
}
|
|
7014
|
+
}
|
|
6828
7015
|
if (this.sessionAutoApprove && dangerLevel === "write") {
|
|
6829
7016
|
this.printToolCall(call);
|
|
6830
7017
|
if (dangerLevel === "write") this.printDiffPreview(call);
|
|
@@ -6992,7 +7179,7 @@ var ToolExecutor = class {
|
|
|
6992
7179
|
rl.resume();
|
|
6993
7180
|
process.stdout.write(prompt);
|
|
6994
7181
|
this.confirming = true;
|
|
6995
|
-
return new Promise((
|
|
7182
|
+
return new Promise((resolve10) => {
|
|
6996
7183
|
let completed = false;
|
|
6997
7184
|
const cleanup = (result) => {
|
|
6998
7185
|
if (completed) return;
|
|
@@ -7002,7 +7189,7 @@ var ToolExecutor = class {
|
|
|
7002
7189
|
rl.pause();
|
|
7003
7190
|
rlAny.output = savedOutput;
|
|
7004
7191
|
this.confirming = false;
|
|
7005
|
-
|
|
7192
|
+
resolve10(result);
|
|
7006
7193
|
};
|
|
7007
7194
|
const onLine = (line) => {
|
|
7008
7195
|
const trimmed = line.trim();
|
|
@@ -7069,10 +7256,10 @@ var ToolExecutor = class {
|
|
|
7069
7256
|
const filePath = String(call.arguments["path"] ?? "");
|
|
7070
7257
|
const newContent = String(call.arguments["content"] ?? "");
|
|
7071
7258
|
if (!filePath) return;
|
|
7072
|
-
if (
|
|
7259
|
+
if (existsSync9(filePath)) {
|
|
7073
7260
|
let oldContent;
|
|
7074
7261
|
try {
|
|
7075
|
-
oldContent =
|
|
7262
|
+
oldContent = readFileSync8(filePath, "utf-8");
|
|
7076
7263
|
} catch {
|
|
7077
7264
|
return;
|
|
7078
7265
|
}
|
|
@@ -7095,7 +7282,7 @@ var ToolExecutor = class {
|
|
|
7095
7282
|
}
|
|
7096
7283
|
} else if (call.name === "edit_file") {
|
|
7097
7284
|
const filePath = String(call.arguments["path"] ?? "");
|
|
7098
|
-
if (!filePath || !
|
|
7285
|
+
if (!filePath || !existsSync9(filePath)) return;
|
|
7099
7286
|
const oldStr = call.arguments["old_str"];
|
|
7100
7287
|
const newStr = call.arguments["new_str"];
|
|
7101
7288
|
if (oldStr !== void 0) {
|
|
@@ -7122,7 +7309,7 @@ var ToolExecutor = class {
|
|
|
7122
7309
|
const to = Number(call.arguments["delete_to_line"] ?? from);
|
|
7123
7310
|
let fileContent;
|
|
7124
7311
|
try {
|
|
7125
|
-
fileContent =
|
|
7312
|
+
fileContent = readFileSync8(filePath, "utf-8");
|
|
7126
7313
|
} catch {
|
|
7127
7314
|
return;
|
|
7128
7315
|
}
|
|
@@ -7176,7 +7363,7 @@ var ToolExecutor = class {
|
|
|
7176
7363
|
rl.resume();
|
|
7177
7364
|
process.stdout.write(color("Proceed? [y/N] (type y + Enter to confirm) "));
|
|
7178
7365
|
this.confirming = true;
|
|
7179
|
-
return new Promise((
|
|
7366
|
+
return new Promise((resolve10) => {
|
|
7180
7367
|
let completed = false;
|
|
7181
7368
|
const cleanup = (answer) => {
|
|
7182
7369
|
if (completed) return;
|
|
@@ -7186,7 +7373,7 @@ var ToolExecutor = class {
|
|
|
7186
7373
|
rl.pause();
|
|
7187
7374
|
rlAny.output = savedOutput;
|
|
7188
7375
|
this.confirming = false;
|
|
7189
|
-
|
|
7376
|
+
resolve10(answer === "y");
|
|
7190
7377
|
};
|
|
7191
7378
|
const onLine = (line) => {
|
|
7192
7379
|
const trimmed = line.trim();
|
|
@@ -7214,11 +7401,11 @@ var ToolExecutor = class {
|
|
|
7214
7401
|
};
|
|
7215
7402
|
|
|
7216
7403
|
// src/tools/sensitive-paths.ts
|
|
7217
|
-
import { resolve as
|
|
7404
|
+
import { resolve as resolve5, sep as sep2, basename as basename2 } from "path";
|
|
7218
7405
|
import { homedir as homedir4 } from "os";
|
|
7219
7406
|
var home = homedir4();
|
|
7220
7407
|
function norm(p) {
|
|
7221
|
-
const abs =
|
|
7408
|
+
const abs = resolve5(p);
|
|
7222
7409
|
return process.platform === "win32" ? abs.toLowerCase() : abs;
|
|
7223
7410
|
}
|
|
7224
7411
|
function homeRel(p) {
|
|
@@ -7377,7 +7564,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
7377
7564
|
};
|
|
7378
7565
|
|
|
7379
7566
|
// src/tools/builtin/edit-file.ts
|
|
7380
|
-
import { readFileSync as
|
|
7567
|
+
import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
|
|
7381
7568
|
|
|
7382
7569
|
// src/tools/builtin/patch-apply.ts
|
|
7383
7570
|
function parseUnifiedDiff(patch) {
|
|
@@ -7738,7 +7925,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
7738
7925
|
const filePath = String(args["path"] ?? "");
|
|
7739
7926
|
const encoding = args["encoding"] ?? "utf-8";
|
|
7740
7927
|
if (!filePath) throw new ToolError("edit_file", "path is required");
|
|
7741
|
-
if (!
|
|
7928
|
+
if (!existsSync10(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
|
|
7742
7929
|
const verdict = classifyWritePath(filePath);
|
|
7743
7930
|
if (verdict.sensitive && subAgentGuard.active) {
|
|
7744
7931
|
throw new ToolError(
|
|
@@ -7746,7 +7933,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
7746
7933
|
`Refused: sub-agents cannot edit sensitive paths \u2014 ${verdict.reason}. If this is genuinely needed, ask the parent agent to perform the edit so the user can approve it.`
|
|
7747
7934
|
);
|
|
7748
7935
|
}
|
|
7749
|
-
const original =
|
|
7936
|
+
const original = readFileSync9(filePath, encoding);
|
|
7750
7937
|
if (args["patch"] !== void 0) {
|
|
7751
7938
|
const patchText = String(args["patch"] ?? "");
|
|
7752
7939
|
const stopOnError = args["stop_on_error"] !== false;
|
|
@@ -7904,8 +8091,8 @@ function truncatePreview(str, maxLen = 80) {
|
|
|
7904
8091
|
}
|
|
7905
8092
|
|
|
7906
8093
|
// src/tools/builtin/list-dir.ts
|
|
7907
|
-
import { readdirSync as readdirSync4, statSync as statSync3, existsSync as
|
|
7908
|
-
import { join as
|
|
8094
|
+
import { readdirSync as readdirSync4, statSync as statSync3, existsSync as existsSync11 } from "fs";
|
|
8095
|
+
import { join as join6, basename as basename3 } from "path";
|
|
7909
8096
|
var listDirTool = {
|
|
7910
8097
|
definition: {
|
|
7911
8098
|
name: "list_dir",
|
|
@@ -7927,7 +8114,7 @@ var listDirTool = {
|
|
|
7927
8114
|
async execute(args) {
|
|
7928
8115
|
const dirPath = String(args["path"] ?? process.cwd());
|
|
7929
8116
|
const recursive = Boolean(args["recursive"] ?? false);
|
|
7930
|
-
if (!
|
|
8117
|
+
if (!existsSync11(dirPath)) {
|
|
7931
8118
|
const targetName = basename3(dirPath).toLowerCase();
|
|
7932
8119
|
const cwd = process.cwd();
|
|
7933
8120
|
const suggestions = [];
|
|
@@ -7985,11 +8172,11 @@ function listRecursive(basePath, indent, recursive, lines) {
|
|
|
7985
8172
|
if (entry.isDirectory()) {
|
|
7986
8173
|
lines.push(`${indent}\u{1F4C1} ${entry.name}/`);
|
|
7987
8174
|
if (recursive) {
|
|
7988
|
-
listRecursive(
|
|
8175
|
+
listRecursive(join6(basePath, entry.name), indent + " ", true, lines);
|
|
7989
8176
|
}
|
|
7990
8177
|
} else {
|
|
7991
8178
|
try {
|
|
7992
|
-
const stat = statSync3(
|
|
8179
|
+
const stat = statSync3(join6(basePath, entry.name));
|
|
7993
8180
|
const size = formatSize(stat.size);
|
|
7994
8181
|
lines.push(`${indent}\u{1F4C4} ${entry.name} (${size})`);
|
|
7995
8182
|
} catch {
|
|
@@ -8005,9 +8192,9 @@ function formatSize(bytes) {
|
|
|
8005
8192
|
}
|
|
8006
8193
|
|
|
8007
8194
|
// src/tools/builtin/grep-files.ts
|
|
8008
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
8195
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync10, statSync as statSync4, existsSync as existsSync12 } from "fs";
|
|
8009
8196
|
import { readFile } from "fs/promises";
|
|
8010
|
-
import { join as
|
|
8197
|
+
import { join as join7, relative } from "path";
|
|
8011
8198
|
var grepFilesTool = {
|
|
8012
8199
|
definition: {
|
|
8013
8200
|
name: "grep_files",
|
|
@@ -8058,7 +8245,7 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
|
|
|
8058
8245
|
const contextLines = Math.max(0, Number(args["context_lines"] ?? 0));
|
|
8059
8246
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 50));
|
|
8060
8247
|
if (!pattern) throw new ToolError("grep_files", "pattern is required");
|
|
8061
|
-
if (!
|
|
8248
|
+
if (!existsSync12(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
|
|
8062
8249
|
const MAX_PATTERN_LENGTH = 1e3;
|
|
8063
8250
|
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
8064
8251
|
throw new ToolError("grep_files", `Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`);
|
|
@@ -8152,11 +8339,11 @@ function collectFilePaths(rootPath, filePattern, maxFiles) {
|
|
|
8152
8339
|
if (paths.length >= maxFiles) return;
|
|
8153
8340
|
if (entry.isDirectory()) {
|
|
8154
8341
|
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
8155
|
-
walk(
|
|
8342
|
+
walk(join7(dirPath, entry.name));
|
|
8156
8343
|
} else if (entry.isFile()) {
|
|
8157
8344
|
if (isBinary(entry.name)) continue;
|
|
8158
8345
|
if (filePattern && !matchesFilePattern(entry.name, filePattern)) continue;
|
|
8159
|
-
const fullPath =
|
|
8346
|
+
const fullPath = join7(dirPath, entry.name);
|
|
8160
8347
|
try {
|
|
8161
8348
|
if (statSync4(fullPath).size > 1e6) continue;
|
|
8162
8349
|
} catch {
|
|
@@ -8211,7 +8398,7 @@ function searchInFile(fullPath, displayPath2, regex, contextLines, maxResults, r
|
|
|
8211
8398
|
}
|
|
8212
8399
|
let content;
|
|
8213
8400
|
try {
|
|
8214
|
-
content =
|
|
8401
|
+
content = readFileSync10(fullPath, "utf-8");
|
|
8215
8402
|
} catch {
|
|
8216
8403
|
return;
|
|
8217
8404
|
}
|
|
@@ -8242,8 +8429,8 @@ function searchInFile(fullPath, displayPath2, regex, contextLines, maxResults, r
|
|
|
8242
8429
|
}
|
|
8243
8430
|
|
|
8244
8431
|
// src/tools/builtin/glob-files.ts
|
|
8245
|
-
import { readdirSync as readdirSync6, statSync as statSync5, existsSync as
|
|
8246
|
-
import { join as
|
|
8432
|
+
import { readdirSync as readdirSync6, statSync as statSync5, existsSync as existsSync13 } from "fs";
|
|
8433
|
+
import { join as join8, relative as relative2, basename as basename4 } from "path";
|
|
8247
8434
|
var globFilesTool = {
|
|
8248
8435
|
definition: {
|
|
8249
8436
|
name: "glob_files",
|
|
@@ -8276,7 +8463,7 @@ Results sorted by most recent modification time. Automatically skips node_module
|
|
|
8276
8463
|
const rootPath = String(args["path"] ?? process.cwd());
|
|
8277
8464
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 100));
|
|
8278
8465
|
if (!pattern) throw new ToolError("glob_files", "pattern is required");
|
|
8279
|
-
if (!
|
|
8466
|
+
if (!existsSync13(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
|
|
8280
8467
|
const regex = globToRegex(pattern);
|
|
8281
8468
|
const matches = [];
|
|
8282
8469
|
collectMatchingFiles(rootPath, rootPath, regex, matches, maxResults);
|
|
@@ -8353,7 +8540,7 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
|
|
|
8353
8540
|
}
|
|
8354
8541
|
for (const entry of entries) {
|
|
8355
8542
|
if (results.length >= maxResults) break;
|
|
8356
|
-
const fullPath =
|
|
8543
|
+
const fullPath = join8(dirPath, entry.name);
|
|
8357
8544
|
if (entry.isDirectory()) {
|
|
8358
8545
|
if (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
8359
8546
|
collectMatchingFiles(fullPath, rootPath, regex, results, maxResults);
|
|
@@ -8435,7 +8622,7 @@ var runInteractiveTool = {
|
|
|
8435
8622
|
PYTHONDONTWRITEBYTECODE: "1"
|
|
8436
8623
|
};
|
|
8437
8624
|
const prefixWarnings = [argsTypeWarning, stdinTypeWarning].filter(Boolean).join("");
|
|
8438
|
-
return new Promise((
|
|
8625
|
+
return new Promise((resolve10) => {
|
|
8439
8626
|
const child = spawn2(executable, cmdArgs.map(String), {
|
|
8440
8627
|
cwd: process.cwd(),
|
|
8441
8628
|
env,
|
|
@@ -8468,22 +8655,22 @@ var runInteractiveTool = {
|
|
|
8468
8655
|
setTimeout(writeNextLine, 400);
|
|
8469
8656
|
const timer = setTimeout(() => {
|
|
8470
8657
|
child.kill();
|
|
8471
|
-
|
|
8658
|
+
resolve10(`${prefixWarnings}[Timeout after ${timeout}ms]
|
|
8472
8659
|
${buildOutput(stdout, stderr)}`);
|
|
8473
8660
|
}, timeout);
|
|
8474
8661
|
child.on("close", (code) => {
|
|
8475
8662
|
clearTimeout(timer);
|
|
8476
8663
|
const output = buildOutput(stdout, stderr);
|
|
8477
8664
|
if (code !== 0 && code !== null) {
|
|
8478
|
-
|
|
8665
|
+
resolve10(`${prefixWarnings}Exit code ${code}:
|
|
8479
8666
|
${output}`);
|
|
8480
8667
|
} else {
|
|
8481
|
-
|
|
8668
|
+
resolve10(`${prefixWarnings}${output || "(no output)"}`);
|
|
8482
8669
|
}
|
|
8483
8670
|
});
|
|
8484
8671
|
child.on("error", (err) => {
|
|
8485
8672
|
clearTimeout(timer);
|
|
8486
|
-
|
|
8673
|
+
resolve10(
|
|
8487
8674
|
`${prefixWarnings}Failed to start process "${executable}": ${err.message}
|
|
8488
8675
|
Hint: On Windows, use the full path to the executable, e.g.:
|
|
8489
8676
|
C:\\Users\\Jinzd\\anaconda3\\envs\\python312\\python.exe`
|
|
@@ -9151,11 +9338,11 @@ Any of these triggers means use save_last_response, NOT write_file:
|
|
|
9151
9338
|
};
|
|
9152
9339
|
|
|
9153
9340
|
// src/tools/builtin/save-memory.ts
|
|
9154
|
-
import { existsSync as
|
|
9155
|
-
import { join as
|
|
9341
|
+
import { existsSync as existsSync14, readFileSync as readFileSync11, statSync as statSync6, mkdirSync as mkdirSync7 } from "fs";
|
|
9342
|
+
import { join as join9 } from "path";
|
|
9156
9343
|
import { homedir as homedir5 } from "os";
|
|
9157
9344
|
function getMemoryFilePath() {
|
|
9158
|
-
return
|
|
9345
|
+
return join9(homedir5(), CONFIG_DIR_NAME, MEMORY_FILE_NAME);
|
|
9159
9346
|
}
|
|
9160
9347
|
function formatTimestamp() {
|
|
9161
9348
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -9179,8 +9366,8 @@ var saveMemoryTool = {
|
|
|
9179
9366
|
const content = String(args["content"] ?? "").trim();
|
|
9180
9367
|
if (!content) throw new ToolError("save_memory", "content is required");
|
|
9181
9368
|
const memoryPath = getMemoryFilePath();
|
|
9182
|
-
const configDir =
|
|
9183
|
-
if (!
|
|
9369
|
+
const configDir = join9(homedir5(), CONFIG_DIR_NAME);
|
|
9370
|
+
if (!existsSync14(configDir)) {
|
|
9184
9371
|
mkdirSync7(configDir, { recursive: true });
|
|
9185
9372
|
}
|
|
9186
9373
|
const timestamp = formatTimestamp();
|
|
@@ -9188,7 +9375,7 @@ var saveMemoryTool = {
|
|
|
9188
9375
|
## ${timestamp}
|
|
9189
9376
|
${content}
|
|
9190
9377
|
`;
|
|
9191
|
-
const previous =
|
|
9378
|
+
const previous = existsSync14(memoryPath) ? readFileSync11(memoryPath, "utf-8") : "";
|
|
9192
9379
|
atomicWriteFileSync(memoryPath, previous + entry);
|
|
9193
9380
|
const byteSize = statSync6(memoryPath).size;
|
|
9194
9381
|
return `Memory saved successfully. File size: ${byteSize} bytes in ${MEMORY_FILE_NAME}`;
|
|
@@ -9235,7 +9422,7 @@ function promptUser(rl, question) {
|
|
|
9235
9422
|
console.log();
|
|
9236
9423
|
console.log(chalk4.cyan("\u2753 ") + chalk4.bold(question));
|
|
9237
9424
|
process.stdout.write(chalk4.cyan("> "));
|
|
9238
|
-
return new Promise((
|
|
9425
|
+
return new Promise((resolve10) => {
|
|
9239
9426
|
let completed = false;
|
|
9240
9427
|
const cleanup = (answer) => {
|
|
9241
9428
|
if (completed) return;
|
|
@@ -9245,7 +9432,7 @@ function promptUser(rl, question) {
|
|
|
9245
9432
|
rl.pause();
|
|
9246
9433
|
rlAny.output = savedOutput;
|
|
9247
9434
|
askUserContext.prompting = false;
|
|
9248
|
-
|
|
9435
|
+
resolve10(answer);
|
|
9249
9436
|
};
|
|
9250
9437
|
const onLine = (line) => {
|
|
9251
9438
|
cleanup(line);
|
|
@@ -10000,14 +10187,14 @@ var taskStopTool = {
|
|
|
10000
10187
|
|
|
10001
10188
|
// src/tools/builtin/git-tools.ts
|
|
10002
10189
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
10003
|
-
import { existsSync as
|
|
10004
|
-
import { join as
|
|
10190
|
+
import { existsSync as existsSync15 } from "fs";
|
|
10191
|
+
import { join as join10 } from "path";
|
|
10005
10192
|
function assertGitRepo(cwd) {
|
|
10006
10193
|
let dir = cwd;
|
|
10007
10194
|
const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
|
|
10008
10195
|
while (dir && dir !== root) {
|
|
10009
|
-
if (
|
|
10010
|
-
const parent =
|
|
10196
|
+
if (existsSync15(join10(dir, ".git"))) return;
|
|
10197
|
+
const parent = join10(dir, "..");
|
|
10011
10198
|
if (parent === dir) break;
|
|
10012
10199
|
dir = parent;
|
|
10013
10200
|
}
|
|
@@ -10272,9 +10459,9 @@ ${commitOutput.trim()}`;
|
|
|
10272
10459
|
};
|
|
10273
10460
|
|
|
10274
10461
|
// src/tools/builtin/notebook-edit.ts
|
|
10275
|
-
import { readFileSync as
|
|
10462
|
+
import { readFileSync as readFileSync12, existsSync as existsSync16 } from "fs";
|
|
10276
10463
|
import { writeFile } from "fs/promises";
|
|
10277
|
-
import { resolve as
|
|
10464
|
+
import { resolve as resolve6, extname as extname2 } from "path";
|
|
10278
10465
|
var notebookEditTool = {
|
|
10279
10466
|
definition: {
|
|
10280
10467
|
name: "notebook_edit",
|
|
@@ -10323,14 +10510,14 @@ var notebookEditTool = {
|
|
|
10323
10510
|
if (!Number.isInteger(cellIndexRaw) || cellIndexRaw < 1) {
|
|
10324
10511
|
throw new ToolError("notebook_edit", "cell_index must be a positive integer (1-based)");
|
|
10325
10512
|
}
|
|
10326
|
-
const absPath =
|
|
10513
|
+
const absPath = resolve6(filePath);
|
|
10327
10514
|
if (extname2(absPath).toLowerCase() !== ".ipynb") {
|
|
10328
10515
|
throw new ToolError("notebook_edit", "path must point to a .ipynb file");
|
|
10329
10516
|
}
|
|
10330
|
-
if (!
|
|
10517
|
+
if (!existsSync16(absPath)) {
|
|
10331
10518
|
throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
|
|
10332
10519
|
}
|
|
10333
|
-
const raw =
|
|
10520
|
+
const raw = readFileSync12(absPath, "utf-8");
|
|
10334
10521
|
const nb = parseNotebook(raw);
|
|
10335
10522
|
const cellIdx0 = cellIndexRaw - 1;
|
|
10336
10523
|
undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
|
|
@@ -10747,8 +10934,8 @@ function estimateToolDefinitionTokens(def) {
|
|
|
10747
10934
|
|
|
10748
10935
|
// src/tools/registry.ts
|
|
10749
10936
|
import { pathToFileURL } from "url";
|
|
10750
|
-
import { existsSync as
|
|
10751
|
-
import { join as
|
|
10937
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync8, readdirSync as readdirSync7 } from "fs";
|
|
10938
|
+
import { join as join11 } from "path";
|
|
10752
10939
|
var ToolRegistry = class {
|
|
10753
10940
|
tools = /* @__PURE__ */ new Map();
|
|
10754
10941
|
pluginToolNames = /* @__PURE__ */ new Set();
|
|
@@ -10909,7 +11096,7 @@ var ToolRegistry = class {
|
|
|
10909
11096
|
* Returns the number of successfully loaded plugins.
|
|
10910
11097
|
*/
|
|
10911
11098
|
async loadPlugins(pluginsDir, allowPlugins = false) {
|
|
10912
|
-
if (!
|
|
11099
|
+
if (!existsSync17(pluginsDir)) {
|
|
10913
11100
|
try {
|
|
10914
11101
|
mkdirSync8(pluginsDir, { recursive: true });
|
|
10915
11102
|
} catch {
|
|
@@ -10935,12 +11122,12 @@ var ToolRegistry = class {
|
|
|
10935
11122
|
process.stderr.write(
|
|
10936
11123
|
`
|
|
10937
11124
|
[plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
|
|
10938
|
-
` + files.map((f) => ` + ${
|
|
11125
|
+
` + files.map((f) => ` + ${join11(pluginsDir, f)}`).join("\n") + "\n\n"
|
|
10939
11126
|
);
|
|
10940
11127
|
let loaded = 0;
|
|
10941
11128
|
for (const file of files) {
|
|
10942
11129
|
try {
|
|
10943
|
-
const fileUrl = pathToFileURL(
|
|
11130
|
+
const fileUrl = pathToFileURL(join11(pluginsDir, file)).href;
|
|
10944
11131
|
const mod = await import(fileUrl);
|
|
10945
11132
|
const tool = mod.tool ?? mod.default?.tool ?? mod.default;
|
|
10946
11133
|
if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
|
|
@@ -11102,7 +11289,7 @@ var McpClient = class {
|
|
|
11102
11289
|
// 内部方法:JSON-RPC 通信
|
|
11103
11290
|
// ══════════════════════════════════════════════════════════════════
|
|
11104
11291
|
sendRequest(method, params) {
|
|
11105
|
-
return new Promise((
|
|
11292
|
+
return new Promise((resolve10, reject) => {
|
|
11106
11293
|
if (!this.process?.stdin?.writable) {
|
|
11107
11294
|
return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
|
|
11108
11295
|
}
|
|
@@ -11126,7 +11313,7 @@ var McpClient = class {
|
|
|
11126
11313
|
this.pendingRequests.set(id, {
|
|
11127
11314
|
resolve: (result) => {
|
|
11128
11315
|
cleanup();
|
|
11129
|
-
|
|
11316
|
+
resolve10(result);
|
|
11130
11317
|
},
|
|
11131
11318
|
reject: (error) => {
|
|
11132
11319
|
cleanup();
|
|
@@ -11203,13 +11390,13 @@ var McpClient = class {
|
|
|
11203
11390
|
}
|
|
11204
11391
|
/** Promise 超时包装 */
|
|
11205
11392
|
withTimeout(promise, ms, label) {
|
|
11206
|
-
return new Promise((
|
|
11393
|
+
return new Promise((resolve10, reject) => {
|
|
11207
11394
|
const timer = setTimeout(() => {
|
|
11208
11395
|
reject(new Error(`MCP [${this.serverId}] ${label} timed out after ${ms}ms`));
|
|
11209
11396
|
}, ms);
|
|
11210
11397
|
promise.then((val) => {
|
|
11211
11398
|
clearTimeout(timer);
|
|
11212
|
-
|
|
11399
|
+
resolve10(val);
|
|
11213
11400
|
}).catch((err) => {
|
|
11214
11401
|
clearTimeout(timer);
|
|
11215
11402
|
reject(err);
|
|
@@ -11486,11 +11673,11 @@ var McpManager = class {
|
|
|
11486
11673
|
};
|
|
11487
11674
|
|
|
11488
11675
|
// src/skills/manager.ts
|
|
11489
|
-
import { existsSync as
|
|
11490
|
-
import { join as
|
|
11676
|
+
import { existsSync as existsSync18, readdirSync as readdirSync8, mkdirSync as mkdirSync9, statSync as statSync7 } from "fs";
|
|
11677
|
+
import { join as join12 } from "path";
|
|
11491
11678
|
|
|
11492
11679
|
// src/skills/types.ts
|
|
11493
|
-
import { readFileSync as
|
|
11680
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
11494
11681
|
import { basename as basename5 } from "path";
|
|
11495
11682
|
function parseSimpleYaml(yaml) {
|
|
11496
11683
|
const result = {};
|
|
@@ -11512,7 +11699,7 @@ function parseYamlArray(value) {
|
|
|
11512
11699
|
function parseSkillFile(filePath) {
|
|
11513
11700
|
let raw;
|
|
11514
11701
|
try {
|
|
11515
|
-
raw =
|
|
11702
|
+
raw = readFileSync13(filePath, "utf-8");
|
|
11516
11703
|
} catch {
|
|
11517
11704
|
return null;
|
|
11518
11705
|
}
|
|
@@ -11555,7 +11742,7 @@ var SkillManager = class {
|
|
|
11555
11742
|
/** 发现并加载 skillsDir 下所有 .md 文件,返回加载数量 */
|
|
11556
11743
|
loadSkills() {
|
|
11557
11744
|
this.skills.clear();
|
|
11558
|
-
if (!
|
|
11745
|
+
if (!existsSync18(this.skillsDir)) {
|
|
11559
11746
|
try {
|
|
11560
11747
|
mkdirSync9(this.skillsDir, { recursive: true });
|
|
11561
11748
|
} catch {
|
|
@@ -11570,14 +11757,14 @@ var SkillManager = class {
|
|
|
11570
11757
|
}
|
|
11571
11758
|
for (const entry of entries) {
|
|
11572
11759
|
let filePath;
|
|
11573
|
-
const fullPath =
|
|
11760
|
+
const fullPath = join12(this.skillsDir, entry);
|
|
11574
11761
|
if (entry.endsWith(".md")) {
|
|
11575
11762
|
filePath = fullPath;
|
|
11576
11763
|
} else {
|
|
11577
11764
|
try {
|
|
11578
11765
|
if (statSync7(fullPath).isDirectory()) {
|
|
11579
|
-
const skillMd =
|
|
11580
|
-
if (
|
|
11766
|
+
const skillMd = join12(fullPath, "SKILL.md");
|
|
11767
|
+
if (existsSync18(skillMd)) {
|
|
11581
11768
|
filePath = skillMd;
|
|
11582
11769
|
} else {
|
|
11583
11770
|
continue;
|
|
@@ -11642,7 +11829,7 @@ var SkillManager = class {
|
|
|
11642
11829
|
// src/web/tool-executor-web.ts
|
|
11643
11830
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
11644
11831
|
import { tmpdir as tmpdir2 } from "os";
|
|
11645
|
-
import { existsSync as
|
|
11832
|
+
import { existsSync as existsSync19, readFileSync as readFileSync14 } from "fs";
|
|
11646
11833
|
var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
11647
11834
|
constructor(registry, ws) {
|
|
11648
11835
|
this.registry = registry;
|
|
@@ -11671,6 +11858,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11671
11858
|
confirming = false;
|
|
11672
11859
|
/** Session-level auto-approve toggle (/yolo command) */
|
|
11673
11860
|
sessionAutoApprove = false;
|
|
11861
|
+
/** Session-level Auto Mode: rule-based low-risk auto approval. */
|
|
11862
|
+
sessionAutoMode = false;
|
|
11674
11863
|
/** Track tool start times for duration calculation */
|
|
11675
11864
|
toolStartTimes = /* @__PURE__ */ new Map();
|
|
11676
11865
|
/**
|
|
@@ -11710,33 +11899,33 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11710
11899
|
}
|
|
11711
11900
|
/** Resolve a pending confirm from client response */
|
|
11712
11901
|
resolveConfirm(requestId, approved) {
|
|
11713
|
-
const
|
|
11714
|
-
if (
|
|
11902
|
+
const resolve10 = this.pendingConfirms.get(requestId);
|
|
11903
|
+
if (resolve10) {
|
|
11715
11904
|
this.clearPendingTimer(requestId);
|
|
11716
11905
|
this.pendingConfirms.delete(requestId);
|
|
11717
11906
|
this.confirming = false;
|
|
11718
|
-
|
|
11907
|
+
resolve10(approved);
|
|
11719
11908
|
}
|
|
11720
11909
|
}
|
|
11721
11910
|
/** Resolve a pending batch confirm from client response */
|
|
11722
11911
|
resolveBatchConfirm(requestId, decision) {
|
|
11723
|
-
const
|
|
11724
|
-
if (
|
|
11912
|
+
const resolve10 = this.pendingBatchConfirms.get(requestId);
|
|
11913
|
+
if (resolve10) {
|
|
11725
11914
|
this.clearPendingTimer(requestId);
|
|
11726
11915
|
this.pendingBatchConfirms.delete(requestId);
|
|
11727
11916
|
this.confirming = false;
|
|
11728
11917
|
if (decision === "all" || decision === "none") {
|
|
11729
|
-
|
|
11918
|
+
resolve10(decision);
|
|
11730
11919
|
} else {
|
|
11731
|
-
|
|
11920
|
+
resolve10(new Set(decision));
|
|
11732
11921
|
}
|
|
11733
11922
|
}
|
|
11734
11923
|
}
|
|
11735
11924
|
/** Cancel all pending confirms (e.g., on disconnect) */
|
|
11736
11925
|
cancelAll() {
|
|
11737
|
-
for (const
|
|
11926
|
+
for (const resolve10 of this.pendingConfirms.values()) resolve10(false);
|
|
11738
11927
|
this.pendingConfirms.clear();
|
|
11739
|
-
for (const
|
|
11928
|
+
for (const resolve10 of this.pendingBatchConfirms.values()) resolve10("none");
|
|
11740
11929
|
this.pendingBatchConfirms.clear();
|
|
11741
11930
|
this.confirming = false;
|
|
11742
11931
|
}
|
|
@@ -11779,9 +11968,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11779
11968
|
if (call.name === "write_file") {
|
|
11780
11969
|
const filePath = String(call.arguments["path"] ?? "");
|
|
11781
11970
|
const newContent = String(call.arguments["content"] ?? "");
|
|
11782
|
-
if (filePath &&
|
|
11971
|
+
if (filePath && existsSync19(filePath)) {
|
|
11783
11972
|
try {
|
|
11784
|
-
const old =
|
|
11973
|
+
const old = readFileSync14(filePath, "utf-8");
|
|
11785
11974
|
return renderDiff(old, newContent, { filePath });
|
|
11786
11975
|
} catch {
|
|
11787
11976
|
}
|
|
@@ -11812,8 +12001,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11812
12001
|
diff: this.getDiffPreview(call)
|
|
11813
12002
|
};
|
|
11814
12003
|
this.send(msg);
|
|
11815
|
-
return new Promise((
|
|
11816
|
-
this.pendingConfirms.set(requestId,
|
|
12004
|
+
return new Promise((resolve10) => {
|
|
12005
|
+
this.pendingConfirms.set(requestId, resolve10);
|
|
11817
12006
|
this.pendingTimers.set(requestId, setTimeout(() => {
|
|
11818
12007
|
if (this.pendingConfirms.has(requestId)) {
|
|
11819
12008
|
this.resolveConfirm(requestId, false);
|
|
@@ -11837,8 +12026,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11837
12026
|
files
|
|
11838
12027
|
};
|
|
11839
12028
|
this.send(msg);
|
|
11840
|
-
return new Promise((
|
|
11841
|
-
this.pendingBatchConfirms.set(requestId,
|
|
12029
|
+
return new Promise((resolve10) => {
|
|
12030
|
+
this.pendingBatchConfirms.set(requestId, resolve10);
|
|
11842
12031
|
this.pendingTimers.set(requestId, setTimeout(() => {
|
|
11843
12032
|
if (this.pendingBatchConfirms.has(requestId)) {
|
|
11844
12033
|
this.resolveBatchConfirm(requestId, "none");
|
|
@@ -11948,6 +12137,35 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11948
12137
|
}
|
|
11949
12138
|
}
|
|
11950
12139
|
this.sendToolCallStart(call);
|
|
12140
|
+
if (this.sessionAutoMode && networkPermission?.action !== "confirm") {
|
|
12141
|
+
const classification = defaultActionClassifier.classify(call, dangerLevel, {
|
|
12142
|
+
workspaceRoot: this.workspaceRoot,
|
|
12143
|
+
tempDirs: [tmpdir2()]
|
|
12144
|
+
});
|
|
12145
|
+
if (classification.decision === "deny") {
|
|
12146
|
+
recordRecentlyDeniedAutoAction(call, classification);
|
|
12147
|
+
const rejectionMsg = `[Auto denied] ${classification.reason}. Do not retry without asking.`;
|
|
12148
|
+
this.sendToolCallResult(call, rejectionMsg, true);
|
|
12149
|
+
return { callId: call.id, content: rejectionMsg, isError: true };
|
|
12150
|
+
}
|
|
12151
|
+
if (classification.decision === "auto-approve") {
|
|
12152
|
+
this.send({ type: "info", message: `\u26A1 Auto-approved (/auto: ${classification.ruleId})` });
|
|
12153
|
+
try {
|
|
12154
|
+
const rawContent = await runTool(tool, call.arguments, call.name);
|
|
12155
|
+
const content = truncateOutput(rawContent, call.name);
|
|
12156
|
+
this.sendToolCallResult(call, rawContent, false);
|
|
12157
|
+
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
12158
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
|
|
12159
|
+
return { callId: call.id, content, isError: false };
|
|
12160
|
+
} catch (err) {
|
|
12161
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
12162
|
+
this.sendToolCallResult(call, message, true);
|
|
12163
|
+
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
12164
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
|
|
12165
|
+
return { callId: call.id, content: message, isError: true };
|
|
12166
|
+
}
|
|
12167
|
+
}
|
|
12168
|
+
}
|
|
11951
12169
|
if (this.sessionAutoApprove && dangerLevel === "write") {
|
|
11952
12170
|
try {
|
|
11953
12171
|
const rawContent = await runTool(tool, call.arguments, call.name);
|
|
@@ -12055,20 +12273,20 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
12055
12273
|
};
|
|
12056
12274
|
|
|
12057
12275
|
// src/core/system-prompt-builder.ts
|
|
12058
|
-
import { existsSync as
|
|
12059
|
-
import { join as
|
|
12276
|
+
import { existsSync as existsSync21, readFileSync as readFileSync16 } from "fs";
|
|
12277
|
+
import { join as join14 } from "path";
|
|
12060
12278
|
|
|
12061
12279
|
// src/repl/dev-state.ts
|
|
12062
|
-
import { existsSync as
|
|
12063
|
-
import { join as
|
|
12280
|
+
import { existsSync as existsSync20, readFileSync as readFileSync15, unlinkSync as unlinkSync4, mkdirSync as mkdirSync10 } from "fs";
|
|
12281
|
+
import { join as join13 } from "path";
|
|
12064
12282
|
import { homedir as homedir6 } from "os";
|
|
12065
12283
|
function getDevStatePath() {
|
|
12066
|
-
return
|
|
12284
|
+
return join13(homedir6(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
|
|
12067
12285
|
}
|
|
12068
12286
|
function loadDevState() {
|
|
12069
12287
|
const path3 = getDevStatePath();
|
|
12070
|
-
if (!
|
|
12071
|
-
const content =
|
|
12288
|
+
if (!existsSync20(path3)) return null;
|
|
12289
|
+
const content = readFileSync15(path3, "utf-8").trim();
|
|
12072
12290
|
return content || null;
|
|
12073
12291
|
}
|
|
12074
12292
|
|
|
@@ -12126,9 +12344,9 @@ ${ctx.activeSkill.content}`);
|
|
|
12126
12344
|
return { stable: stableParts.join("\n\n---\n\n"), volatile };
|
|
12127
12345
|
}
|
|
12128
12346
|
function loadMemoryContent(configDir) {
|
|
12129
|
-
const memoryPath =
|
|
12130
|
-
if (!
|
|
12131
|
-
let content =
|
|
12347
|
+
const memoryPath = join14(configDir, MEMORY_FILE_NAME);
|
|
12348
|
+
if (!existsSync21(memoryPath)) return null;
|
|
12349
|
+
let content = readFileSync16(memoryPath, "utf-8").trim();
|
|
12132
12350
|
if (!content) return null;
|
|
12133
12351
|
if (content.length > MEMORY_MAX_CHARS) {
|
|
12134
12352
|
content = content.slice(-MEMORY_MAX_CHARS);
|
|
@@ -12337,8 +12555,8 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
|
|
|
12337
12555
|
}
|
|
12338
12556
|
|
|
12339
12557
|
// src/core/context-files.ts
|
|
12340
|
-
import { existsSync as
|
|
12341
|
-
import { join as
|
|
12558
|
+
import { existsSync as existsSync22, readFileSync as readFileSync17 } from "fs";
|
|
12559
|
+
import { join as join15, relative as relative3, resolve as resolve7 } from "path";
|
|
12342
12560
|
function uniqueNonEmpty(values) {
|
|
12343
12561
|
const seen = /* @__PURE__ */ new Set();
|
|
12344
12562
|
const out = [];
|
|
@@ -12361,14 +12579,14 @@ function displayPath(filePath, cwd) {
|
|
|
12361
12579
|
return filePath;
|
|
12362
12580
|
}
|
|
12363
12581
|
function isInsideOrEqual(parent, child) {
|
|
12364
|
-
const rel = relative3(
|
|
12582
|
+
const rel = relative3(resolve7(parent), resolve7(child));
|
|
12365
12583
|
return rel === "" || !!rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\");
|
|
12366
12584
|
}
|
|
12367
12585
|
function readContextFile(level, filePath, cwd, maxBytes) {
|
|
12368
12586
|
const name = filePath.split(/[\\/]/).pop() ?? filePath;
|
|
12369
12587
|
const shown = displayPath(filePath, cwd);
|
|
12370
12588
|
try {
|
|
12371
|
-
const raw =
|
|
12589
|
+
const raw = readFileSync17(filePath);
|
|
12372
12590
|
const byteCount = raw.byteLength;
|
|
12373
12591
|
if (byteCount === 0) {
|
|
12374
12592
|
return {
|
|
@@ -12415,8 +12633,8 @@ function readContextFile(level, filePath, cwd, maxBytes) {
|
|
|
12415
12633
|
function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
12416
12634
|
const skipped = [];
|
|
12417
12635
|
for (const candidate of candidates) {
|
|
12418
|
-
const filePath =
|
|
12419
|
-
if (!
|
|
12636
|
+
const filePath = join15(dir, candidate);
|
|
12637
|
+
if (!existsSync22(filePath)) continue;
|
|
12420
12638
|
const result = readContextFile(level, filePath, cwd, maxBytes);
|
|
12421
12639
|
if (result.skipped) skipped.push(result.skipped);
|
|
12422
12640
|
if (result.layer) return { layer: result.layer, skipped };
|
|
@@ -12424,7 +12642,7 @@ function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
|
12424
12642
|
return { layer: null, skipped };
|
|
12425
12643
|
}
|
|
12426
12644
|
function loadContextFiles(options) {
|
|
12427
|
-
const cwd =
|
|
12645
|
+
const cwd = resolve7(options.cwd);
|
|
12428
12646
|
const maxBytes = options.maxBytes ?? CONTEXT_FILE_MAX_BYTES;
|
|
12429
12647
|
const candidates = options.candidates ?? buildContextFileCandidates(options.fallbackFilenames);
|
|
12430
12648
|
const skipped = [];
|
|
@@ -12432,7 +12650,7 @@ function loadContextFiles(options) {
|
|
|
12432
12650
|
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
12433
12651
|
}
|
|
12434
12652
|
if (options.setting && options.setting !== "auto") {
|
|
12435
|
-
const filePath =
|
|
12653
|
+
const filePath = resolve7(cwd, String(options.setting));
|
|
12436
12654
|
if (!isInsideOrEqual(cwd, filePath)) {
|
|
12437
12655
|
skipped.push({
|
|
12438
12656
|
level: "single",
|
|
@@ -12443,7 +12661,7 @@ function loadContextFiles(options) {
|
|
|
12443
12661
|
});
|
|
12444
12662
|
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
12445
12663
|
}
|
|
12446
|
-
if (!
|
|
12664
|
+
if (!existsSync22(filePath)) {
|
|
12447
12665
|
skipped.push({
|
|
12448
12666
|
level: "single",
|
|
12449
12667
|
filePath,
|
|
@@ -12476,7 +12694,7 @@ function loadContextFiles(options) {
|
|
|
12476
12694
|
skipped.push(...result.skipped);
|
|
12477
12695
|
if (result.layer) layers.push(result.layer);
|
|
12478
12696
|
}
|
|
12479
|
-
if (
|
|
12697
|
+
if (resolve7(options.cwd) !== resolve7(options.projectRoot)) {
|
|
12480
12698
|
const result = findFirstContextFile("local", options.cwd, cwd, candidates, maxBytes);
|
|
12481
12699
|
skipped.push(...result.skipped);
|
|
12482
12700
|
if (result.layer) layers.push(result.layer);
|
|
@@ -12494,13 +12712,13 @@ function loadContextFiles(options) {
|
|
|
12494
12712
|
}
|
|
12495
12713
|
|
|
12496
12714
|
// src/web/session-handler.ts
|
|
12497
|
-
import { existsSync as
|
|
12498
|
-
import { join as
|
|
12715
|
+
import { existsSync as existsSync25, readFileSync as readFileSync19, writeFileSync as writeFileSync2, mkdirSync as mkdirSync11, readdirSync as readdirSync10, statSync as statSync9, createWriteStream } from "fs";
|
|
12716
|
+
import { join as join19, resolve as resolve8, dirname as dirname5 } from "path";
|
|
12499
12717
|
|
|
12500
12718
|
// src/tools/git-context.ts
|
|
12501
12719
|
import { execSync as execSync2 } from "child_process";
|
|
12502
|
-
import { existsSync as
|
|
12503
|
-
import { join as
|
|
12720
|
+
import { existsSync as existsSync23 } from "fs";
|
|
12721
|
+
import { join as join16 } from "path";
|
|
12504
12722
|
function runGit2(cmd, cwd) {
|
|
12505
12723
|
try {
|
|
12506
12724
|
return execSync2(`git ${cmd}`, {
|
|
@@ -12517,7 +12735,7 @@ function getGitRoot(cwd = process.cwd()) {
|
|
|
12517
12735
|
return runGit2("rev-parse --show-toplevel", cwd);
|
|
12518
12736
|
}
|
|
12519
12737
|
function getGitContext(cwd = process.cwd()) {
|
|
12520
|
-
if (!
|
|
12738
|
+
if (!existsSync23(join16(cwd, ".git"))) {
|
|
12521
12739
|
const result = runGit2("rev-parse --git-dir", cwd);
|
|
12522
12740
|
if (!result) return null;
|
|
12523
12741
|
}
|
|
@@ -12646,8 +12864,8 @@ If no security issues found, state "\u2705 No security vulnerabilities detected"
|
|
|
12646
12864
|
}
|
|
12647
12865
|
|
|
12648
12866
|
// src/repl/commands/project-init.ts
|
|
12649
|
-
import { existsSync as
|
|
12650
|
-
import { join as
|
|
12867
|
+
import { existsSync as existsSync24, readFileSync as readFileSync18, readdirSync as readdirSync9, statSync as statSync8 } from "fs";
|
|
12868
|
+
import { join as join17 } from "path";
|
|
12651
12869
|
var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
12652
12870
|
"node_modules",
|
|
12653
12871
|
".git",
|
|
@@ -12691,11 +12909,11 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
12691
12909
|
const sorted = filtered.sort((a, b) => {
|
|
12692
12910
|
let aIsDir = false, bIsDir = false;
|
|
12693
12911
|
try {
|
|
12694
|
-
aIsDir = statSync8(
|
|
12912
|
+
aIsDir = statSync8(join17(d, a)).isDirectory();
|
|
12695
12913
|
} catch {
|
|
12696
12914
|
}
|
|
12697
12915
|
try {
|
|
12698
|
-
bIsDir = statSync8(
|
|
12916
|
+
bIsDir = statSync8(join17(d, b)).isDirectory();
|
|
12699
12917
|
} catch {
|
|
12700
12918
|
}
|
|
12701
12919
|
if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
|
|
@@ -12703,7 +12921,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
12703
12921
|
});
|
|
12704
12922
|
for (let i = 0; i < sorted.length && count < maxEntries; i++) {
|
|
12705
12923
|
const name = sorted[i];
|
|
12706
|
-
const fullPath =
|
|
12924
|
+
const fullPath = join17(d, name);
|
|
12707
12925
|
const isLast = i === sorted.length - 1;
|
|
12708
12926
|
const connector = isLast ? "+-- " : "|-- ";
|
|
12709
12927
|
let isDir;
|
|
@@ -12730,7 +12948,7 @@ function scanProject(cwd) {
|
|
|
12730
12948
|
configFiles: [],
|
|
12731
12949
|
directoryStructure: ""
|
|
12732
12950
|
};
|
|
12733
|
-
const check = (file) =>
|
|
12951
|
+
const check = (file) => existsSync24(join17(cwd, file));
|
|
12734
12952
|
const configCandidates = [
|
|
12735
12953
|
"package.json",
|
|
12736
12954
|
"tsconfig.json",
|
|
@@ -12757,7 +12975,7 @@ function scanProject(cwd) {
|
|
|
12757
12975
|
info.type = "node";
|
|
12758
12976
|
info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
|
|
12759
12977
|
try {
|
|
12760
|
-
const pkg = JSON.parse(
|
|
12978
|
+
const pkg = JSON.parse(readFileSync18(join17(cwd, "package.json"), "utf-8"));
|
|
12761
12979
|
const scripts = pkg.scripts ?? {};
|
|
12762
12980
|
info.buildCommand = scripts.build ? "npm run build" : void 0;
|
|
12763
12981
|
info.testCommand = scripts.test ? "npm test" : void 0;
|
|
@@ -13417,7 +13635,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
|
|
|
13417
13635
|
}
|
|
13418
13636
|
|
|
13419
13637
|
// src/hub/persist.ts
|
|
13420
|
-
import { join as
|
|
13638
|
+
import { join as join18 } from "path";
|
|
13421
13639
|
function discussionToMessages(state2) {
|
|
13422
13640
|
const out = [];
|
|
13423
13641
|
const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
|
|
@@ -13455,7 +13673,7 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
|
|
|
13455
13673
|
session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
|
|
13456
13674
|
session.titleAiGenerated = true;
|
|
13457
13675
|
await sm.save();
|
|
13458
|
-
return { id: session.id, path:
|
|
13676
|
+
return { id: session.id, path: join18(config.getHistoryDir(), `${session.id}.json`) };
|
|
13459
13677
|
}
|
|
13460
13678
|
|
|
13461
13679
|
// src/web/session-handler.ts
|
|
@@ -13639,10 +13857,10 @@ var SessionHandler = class {
|
|
|
13639
13857
|
return;
|
|
13640
13858
|
}
|
|
13641
13859
|
case "ask_user_response": {
|
|
13642
|
-
const
|
|
13643
|
-
if (
|
|
13860
|
+
const resolve10 = this.pendingAskUser.get(msg.requestId);
|
|
13861
|
+
if (resolve10) {
|
|
13644
13862
|
this.pendingAskUser.delete(msg.requestId);
|
|
13645
|
-
|
|
13863
|
+
resolve10(msg.answer);
|
|
13646
13864
|
}
|
|
13647
13865
|
return;
|
|
13648
13866
|
}
|
|
@@ -13653,10 +13871,10 @@ var SessionHandler = class {
|
|
|
13653
13871
|
case "memory_rebuild":
|
|
13654
13872
|
return this.handleMemoryRebuild(Boolean(msg.full));
|
|
13655
13873
|
case "auto_pause_response": {
|
|
13656
|
-
const
|
|
13657
|
-
if (
|
|
13874
|
+
const resolve10 = this.pendingAutoPause.get(msg.requestId);
|
|
13875
|
+
if (resolve10) {
|
|
13658
13876
|
this.pendingAutoPause.delete(msg.requestId);
|
|
13659
|
-
|
|
13877
|
+
resolve10({ action: msg.action, message: msg.message });
|
|
13660
13878
|
}
|
|
13661
13879
|
return;
|
|
13662
13880
|
}
|
|
@@ -13671,10 +13889,10 @@ var SessionHandler = class {
|
|
|
13671
13889
|
this.hubOrchestrator?.abort();
|
|
13672
13890
|
return;
|
|
13673
13891
|
case "hub_steer": {
|
|
13674
|
-
const
|
|
13675
|
-
if (
|
|
13892
|
+
const resolve10 = this.pendingHubReview.get(msg.requestId);
|
|
13893
|
+
if (resolve10) {
|
|
13676
13894
|
this.pendingHubReview.delete(msg.requestId);
|
|
13677
|
-
|
|
13895
|
+
resolve10({ action: msg.action, message: msg.message });
|
|
13678
13896
|
}
|
|
13679
13897
|
return;
|
|
13680
13898
|
}
|
|
@@ -13687,9 +13905,9 @@ var SessionHandler = class {
|
|
|
13687
13905
|
runLifecycleHooks(this.config.get("hooks") ?? void 0, "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
|
|
13688
13906
|
this.toolExecutor.cancelAll();
|
|
13689
13907
|
if (this.abortController) this.abortController.abort();
|
|
13690
|
-
for (const
|
|
13908
|
+
for (const resolve10 of this.pendingAskUser.values()) resolve10(null);
|
|
13691
13909
|
this.pendingAskUser.clear();
|
|
13692
|
-
for (const
|
|
13910
|
+
for (const resolve10 of this.pendingAutoPause.values()) resolve10({ action: "stop" });
|
|
13693
13911
|
this.pendingAutoPause.clear();
|
|
13694
13912
|
this.saveIfNeeded();
|
|
13695
13913
|
}
|
|
@@ -13823,9 +14041,9 @@ var SessionHandler = class {
|
|
|
13823
14041
|
this.hubOrchestrator = orchestrator;
|
|
13824
14042
|
orchestrator.onEvent = (event) => this.send({ type: "hub_event", event });
|
|
13825
14043
|
if (config.humanSteer) {
|
|
13826
|
-
orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((
|
|
14044
|
+
orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve10) => {
|
|
13827
14045
|
const requestId = `hubrev_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
13828
|
-
this.pendingHubReview.set(requestId,
|
|
14046
|
+
this.pendingHubReview.set(requestId, resolve10);
|
|
13829
14047
|
this.send({ type: "hub_review", requestId, round, maxRounds });
|
|
13830
14048
|
});
|
|
13831
14049
|
}
|
|
@@ -14182,8 +14400,8 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
|
|
|
14182
14400
|
onMcpToolUsed: (name) => this.usedMcpToolNames.add(name),
|
|
14183
14401
|
requestAutoPause: async ({ effectiveRound, maxToolRounds: totalRounds, toolSummary }) => {
|
|
14184
14402
|
const requestId = `pause_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
14185
|
-
const pauseResp = await new Promise((
|
|
14186
|
-
this.pendingAutoPause.set(requestId,
|
|
14403
|
+
const pauseResp = await new Promise((resolve10) => {
|
|
14404
|
+
this.pendingAutoPause.set(requestId, resolve10);
|
|
14187
14405
|
this.send({
|
|
14188
14406
|
type: "auto_pause_request",
|
|
14189
14407
|
requestId,
|
|
@@ -14362,8 +14580,8 @@ ${summaryContent}`,
|
|
|
14362
14580
|
}
|
|
14363
14581
|
if (chunk.done) break;
|
|
14364
14582
|
}
|
|
14365
|
-
await new Promise((
|
|
14366
|
-
fileStream.end((err) => err ? reject(err) :
|
|
14583
|
+
await new Promise((resolve10, reject) => {
|
|
14584
|
+
fileStream.end((err) => err ? reject(err) : resolve10());
|
|
14367
14585
|
});
|
|
14368
14586
|
const verdict = evaluateTeeContent(fullContent, saveToFile, priorContent);
|
|
14369
14587
|
if (verdict.kind === "reject") {
|
|
@@ -14389,7 +14607,7 @@ ${summaryContent}`,
|
|
|
14389
14607
|
} catch (err) {
|
|
14390
14608
|
if (fileStream) {
|
|
14391
14609
|
try {
|
|
14392
|
-
await new Promise((
|
|
14610
|
+
await new Promise((resolve10) => fileStream.end(() => resolve10()));
|
|
14393
14611
|
} catch {
|
|
14394
14612
|
}
|
|
14395
14613
|
}
|
|
@@ -14819,6 +15037,47 @@ ${activated.meta.description || ""}` });
|
|
|
14819
15037
|
break;
|
|
14820
15038
|
}
|
|
14821
15039
|
// ── /yolo ──────────────────────────────────────────────────────
|
|
15040
|
+
case "auto": {
|
|
15041
|
+
const sub = args[0]?.toLowerCase() ?? "status";
|
|
15042
|
+
if (sub === "on") {
|
|
15043
|
+
this.toolExecutor.sessionAutoMode = true;
|
|
15044
|
+
this.send({ type: "info", message: "\u26A1 Auto Mode ON \u2014 low-risk actions may auto-run; high-risk actions still confirm or deny. Auto Mode is session-scoped." });
|
|
15045
|
+
} else if (sub === "off") {
|
|
15046
|
+
this.toolExecutor.sessionAutoMode = false;
|
|
15047
|
+
this.send({ type: "info", message: "Auto Mode disabled." });
|
|
15048
|
+
} else if (sub === "status") {
|
|
15049
|
+
this.send({ type: "info", message: `Auto Mode: ${this.toolExecutor.sessionAutoMode ? "on" : "off"}
|
|
15050
|
+
Scope: current session only
|
|
15051
|
+
Denied: /permissions recently-denied` });
|
|
15052
|
+
} else {
|
|
15053
|
+
this.send({ type: "error", message: "Usage: /auto [on|off|status]" });
|
|
15054
|
+
}
|
|
15055
|
+
this.sendStatus();
|
|
15056
|
+
break;
|
|
15057
|
+
}
|
|
15058
|
+
case "permissions": {
|
|
15059
|
+
const sub = args[0]?.toLowerCase() ?? "recently-denied";
|
|
15060
|
+
if (sub === "recently-denied") {
|
|
15061
|
+
const rows = getRecentlyDeniedAutoActions();
|
|
15062
|
+
if (rows.length === 0) {
|
|
15063
|
+
this.send({ type: "info", message: "No recently denied Auto Mode actions." });
|
|
15064
|
+
} else {
|
|
15065
|
+
const lines = [`Recently denied Auto Mode actions (${rows.length})`, ""];
|
|
15066
|
+
for (const r of rows.slice(0, 20)) {
|
|
15067
|
+
lines.push(`- ${r.timestamp} ${r.tool} ${r.ruleId}`);
|
|
15068
|
+
lines.push(` ${r.reason}`);
|
|
15069
|
+
lines.push(` ${r.argsPreview}`);
|
|
15070
|
+
}
|
|
15071
|
+
this.send({ type: "info", message: lines.join("\n") });
|
|
15072
|
+
}
|
|
15073
|
+
} else if (sub === "clear-denied") {
|
|
15074
|
+
clearRecentlyDeniedAutoActions();
|
|
15075
|
+
this.send({ type: "info", message: "Cleared recently denied Auto Mode actions." });
|
|
15076
|
+
} else {
|
|
15077
|
+
this.send({ type: "error", message: "Usage: /permissions recently-denied|clear-denied" });
|
|
15078
|
+
}
|
|
15079
|
+
break;
|
|
15080
|
+
}
|
|
14822
15081
|
case "yolo": {
|
|
14823
15082
|
const sub = args[0]?.toLowerCase();
|
|
14824
15083
|
if (sub === "off") {
|
|
@@ -14919,9 +15178,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14919
15178
|
let modifiedFiles = 0;
|
|
14920
15179
|
const diffLines2 = [];
|
|
14921
15180
|
for (const [filePath, { earliest }] of fileMap) {
|
|
14922
|
-
const currentContent =
|
|
15181
|
+
const currentContent = existsSync25(filePath) ? (() => {
|
|
14923
15182
|
try {
|
|
14924
|
-
return
|
|
15183
|
+
return readFileSync19(filePath, "utf-8");
|
|
14925
15184
|
} catch {
|
|
14926
15185
|
return null;
|
|
14927
15186
|
}
|
|
@@ -15022,7 +15281,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15022
15281
|
break;
|
|
15023
15282
|
}
|
|
15024
15283
|
const sub = args[0]?.toLowerCase();
|
|
15025
|
-
const
|
|
15284
|
+
const resolve10 = (ref) => {
|
|
15026
15285
|
const r = session.resolveBranchRef(ref);
|
|
15027
15286
|
if (r.ok) return r.id;
|
|
15028
15287
|
if (r.reason === "ambiguous") {
|
|
@@ -15076,7 +15335,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15076
15335
|
this.send({ type: "error", message: "Usage: /branch switch <id|title>" });
|
|
15077
15336
|
break;
|
|
15078
15337
|
}
|
|
15079
|
-
const id =
|
|
15338
|
+
const id = resolve10(ref);
|
|
15080
15339
|
if (!id) break;
|
|
15081
15340
|
const ok = session.switchBranch(id);
|
|
15082
15341
|
if (ok) {
|
|
@@ -15096,7 +15355,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15096
15355
|
this.send({ type: "error", message: "Usage: /branch delete <id|title>" });
|
|
15097
15356
|
break;
|
|
15098
15357
|
}
|
|
15099
|
-
const id =
|
|
15358
|
+
const id = resolve10(ref);
|
|
15100
15359
|
if (!id) break;
|
|
15101
15360
|
const ok = session.deleteBranch(id);
|
|
15102
15361
|
if (ok) {
|
|
@@ -15115,7 +15374,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15115
15374
|
this.send({ type: "error", message: "Usage: /branch rename <id|title> <new title>" });
|
|
15116
15375
|
break;
|
|
15117
15376
|
}
|
|
15118
|
-
const id =
|
|
15377
|
+
const id = resolve10(ref);
|
|
15119
15378
|
if (!id) break;
|
|
15120
15379
|
const ok = session.renameBranch(id, title);
|
|
15121
15380
|
if (ok) {
|
|
@@ -15133,7 +15392,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15133
15392
|
this.send({ type: "error", message: "Usage: /branch diff <id|title>" });
|
|
15134
15393
|
break;
|
|
15135
15394
|
}
|
|
15136
|
-
const id =
|
|
15395
|
+
const id = resolve10(ref);
|
|
15137
15396
|
if (!id) break;
|
|
15138
15397
|
const d = session.diffBranches(id);
|
|
15139
15398
|
if (!d) {
|
|
@@ -15175,7 +15434,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15175
15434
|
this.send({ type: "error", message: "Usage: /branch cherry-pick <source-id|title> <msg-index>" });
|
|
15176
15435
|
break;
|
|
15177
15436
|
}
|
|
15178
|
-
const id =
|
|
15437
|
+
const id = resolve10(ref);
|
|
15179
15438
|
if (!id) break;
|
|
15180
15439
|
const idx = parseInt(idxStr, 10);
|
|
15181
15440
|
if (Number.isNaN(idx)) {
|
|
@@ -15437,7 +15696,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15437
15696
|
case "test": {
|
|
15438
15697
|
this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
|
|
15439
15698
|
try {
|
|
15440
|
-
const { executeTests } = await import("./run-tests-
|
|
15699
|
+
const { executeTests } = await import("./run-tests-7J3PNWVS.js");
|
|
15441
15700
|
const argStr = args.join(" ").trim();
|
|
15442
15701
|
let testArgs = {};
|
|
15443
15702
|
if (argStr) {
|
|
@@ -15454,9 +15713,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15454
15713
|
// ── /init ───────────────────────────────────────────────────────
|
|
15455
15714
|
case "init": {
|
|
15456
15715
|
const cwd = process.cwd();
|
|
15457
|
-
const targetPath =
|
|
15716
|
+
const targetPath = join19(cwd, "AICLI.md");
|
|
15458
15717
|
const force = args.includes("--force");
|
|
15459
|
-
if (
|
|
15718
|
+
if (existsSync25(targetPath) && !force) {
|
|
15460
15719
|
this.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
|
|
15461
15720
|
Use /init --force to overwrite.` });
|
|
15462
15721
|
break;
|
|
@@ -15489,7 +15748,7 @@ Use /context reload to load it.` });
|
|
|
15489
15748
|
lines.push("**Config Files:**");
|
|
15490
15749
|
lines.push(` Dir: ${configDir}`);
|
|
15491
15750
|
const checkFile = (label, filePath) => {
|
|
15492
|
-
const exists =
|
|
15751
|
+
const exists = existsSync25(filePath);
|
|
15493
15752
|
let extra = "";
|
|
15494
15753
|
if (exists) {
|
|
15495
15754
|
try {
|
|
@@ -15499,9 +15758,9 @@ Use /context reload to load it.` });
|
|
|
15499
15758
|
}
|
|
15500
15759
|
lines.push(` ${exists ? "\u2713" : "\u2013"} ${label.padEnd(14)} ${exists ? filePath + extra : "(not found)"}`);
|
|
15501
15760
|
};
|
|
15502
|
-
checkFile("config.json",
|
|
15503
|
-
checkFile("memory.md",
|
|
15504
|
-
checkFile("dev-state.md",
|
|
15761
|
+
checkFile("config.json", join19(configDir, "config.json"));
|
|
15762
|
+
checkFile("memory.md", join19(configDir, MEMORY_FILE_NAME));
|
|
15763
|
+
checkFile("dev-state.md", join19(configDir, "dev-state.md"));
|
|
15505
15764
|
lines.push("");
|
|
15506
15765
|
if (this.mcpManager) {
|
|
15507
15766
|
lines.push("**MCP Servers:**");
|
|
@@ -15688,8 +15947,8 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
|
|
|
15688
15947
|
this.send({ type: "info", message: "\u2713 All added directories removed from context." });
|
|
15689
15948
|
break;
|
|
15690
15949
|
}
|
|
15691
|
-
const dirPath =
|
|
15692
|
-
if (!
|
|
15950
|
+
const dirPath = resolve8(sub);
|
|
15951
|
+
if (!existsSync25(dirPath)) {
|
|
15693
15952
|
this.send({ type: "error", message: `Directory not found: ${dirPath}` });
|
|
15694
15953
|
break;
|
|
15695
15954
|
}
|
|
@@ -15705,8 +15964,8 @@ It will be included in AI context for subsequent messages.` });
|
|
|
15705
15964
|
// ── /commands ───────────────────────────────────────────────────
|
|
15706
15965
|
case "commands": {
|
|
15707
15966
|
const configDir = this.config.getConfigDir();
|
|
15708
|
-
const commandsDir =
|
|
15709
|
-
if (!
|
|
15967
|
+
const commandsDir = join19(configDir, CUSTOM_COMMANDS_DIR_NAME);
|
|
15968
|
+
if (!existsSync25(commandsDir)) {
|
|
15710
15969
|
this.send({ type: "info", message: `No custom commands directory.
|
|
15711
15970
|
Create: ${commandsDir}/ with .md files.` });
|
|
15712
15971
|
break;
|
|
@@ -15732,7 +15991,7 @@ Add .md files to create commands.` });
|
|
|
15732
15991
|
// ── /plugins ────────────────────────────────────────────────────
|
|
15733
15992
|
case "plugins": {
|
|
15734
15993
|
const configDir = this.config.getConfigDir();
|
|
15735
|
-
const pluginsDir =
|
|
15994
|
+
const pluginsDir = join19(configDir, PLUGINS_DIR_NAME);
|
|
15736
15995
|
const pluginTools = this.toolRegistry.listPluginTools();
|
|
15737
15996
|
const lines = [`\u{1F50C} **Plugins:**`, `Dir: ${pluginsDir}`, ""];
|
|
15738
15997
|
if (pluginTools.length === 0) {
|
|
@@ -15929,11 +16188,11 @@ Add .md files to create commands.` });
|
|
|
15929
16188
|
}
|
|
15930
16189
|
memoryShow() {
|
|
15931
16190
|
const configDir = this.config.getConfigDir();
|
|
15932
|
-
const memPath =
|
|
16191
|
+
const memPath = join19(configDir, MEMORY_FILE_NAME);
|
|
15933
16192
|
let content = "";
|
|
15934
16193
|
try {
|
|
15935
|
-
if (
|
|
15936
|
-
content =
|
|
16194
|
+
if (existsSync25(memPath)) {
|
|
16195
|
+
content = readFileSync19(memPath, "utf-8");
|
|
15937
16196
|
}
|
|
15938
16197
|
} catch (err) {
|
|
15939
16198
|
process.stderr.write(`[web] Failed to read memory file: ${err instanceof Error ? err.message : err}
|
|
@@ -15947,11 +16206,11 @@ Add .md files to create commands.` });
|
|
|
15947
16206
|
}
|
|
15948
16207
|
memoryAdd(text) {
|
|
15949
16208
|
const configDir = this.config.getConfigDir();
|
|
15950
|
-
const memPath =
|
|
16209
|
+
const memPath = join19(configDir, MEMORY_FILE_NAME);
|
|
15951
16210
|
try {
|
|
15952
16211
|
mkdirSync11(configDir, { recursive: true });
|
|
15953
16212
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
|
|
15954
|
-
const previous =
|
|
16213
|
+
const previous = existsSync25(memPath) ? readFileSync19(memPath, "utf-8") : "";
|
|
15955
16214
|
const entry = `
|
|
15956
16215
|
- [${timestamp}] ${text}
|
|
15957
16216
|
`;
|
|
@@ -15963,7 +16222,7 @@ Add .md files to create commands.` });
|
|
|
15963
16222
|
}
|
|
15964
16223
|
memoryClear() {
|
|
15965
16224
|
const configDir = this.config.getConfigDir();
|
|
15966
|
-
const memPath =
|
|
16225
|
+
const memPath = join19(configDir, MEMORY_FILE_NAME);
|
|
15967
16226
|
try {
|
|
15968
16227
|
atomicWriteFileSync(memPath, "");
|
|
15969
16228
|
this.send({ type: "info", message: "\u{1F5D1}\uFE0F Persistent memory cleared." });
|
|
@@ -16197,8 +16456,8 @@ async function setupProxy(configProxy) {
|
|
|
16197
16456
|
}
|
|
16198
16457
|
|
|
16199
16458
|
// src/web/auth.ts
|
|
16200
|
-
import { existsSync as
|
|
16201
|
-
import { join as
|
|
16459
|
+
import { existsSync as existsSync26, readFileSync as readFileSync20, writeFileSync as writeFileSync3, mkdirSync as mkdirSync12, readdirSync as readdirSync11, copyFileSync } from "fs";
|
|
16460
|
+
import { join as join20 } from "path";
|
|
16202
16461
|
import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
|
|
16203
16462
|
var USERS_FILE = "users.json";
|
|
16204
16463
|
var TOKEN_EXPIRY_HOURS = 24;
|
|
@@ -16213,7 +16472,7 @@ var AuthManager = class {
|
|
|
16213
16472
|
db;
|
|
16214
16473
|
constructor(baseDir) {
|
|
16215
16474
|
this.baseDir = baseDir;
|
|
16216
|
-
this.usersFile =
|
|
16475
|
+
this.usersFile = join20(baseDir, USERS_FILE);
|
|
16217
16476
|
this.db = this.loadOrCreate();
|
|
16218
16477
|
}
|
|
16219
16478
|
// ── Public API ─────────────────────────────────────────────────
|
|
@@ -16242,9 +16501,9 @@ var AuthManager = class {
|
|
|
16242
16501
|
}
|
|
16243
16502
|
const salt = randomBytes(16).toString("hex");
|
|
16244
16503
|
const passwordHash = this.hashPassword(password, salt);
|
|
16245
|
-
const dataDir =
|
|
16246
|
-
const fullDataDir =
|
|
16247
|
-
mkdirSync12(
|
|
16504
|
+
const dataDir = join20(USERS_DIR, username);
|
|
16505
|
+
const fullDataDir = join20(this.baseDir, dataDir);
|
|
16506
|
+
mkdirSync12(join20(fullDataDir, "history"), { recursive: true });
|
|
16248
16507
|
const user = {
|
|
16249
16508
|
username,
|
|
16250
16509
|
passwordHash,
|
|
@@ -16359,7 +16618,7 @@ var AuthManager = class {
|
|
|
16359
16618
|
getUserDataDir(username) {
|
|
16360
16619
|
const user = this.db.users.find((u) => u.username === username);
|
|
16361
16620
|
if (!user) throw new Error(`User not found: ${username}`);
|
|
16362
|
-
return
|
|
16621
|
+
return join20(this.baseDir, user.dataDir);
|
|
16363
16622
|
}
|
|
16364
16623
|
/** List all usernames */
|
|
16365
16624
|
listUsers() {
|
|
@@ -16395,30 +16654,30 @@ var AuthManager = class {
|
|
|
16395
16654
|
const err = this.register(username, password);
|
|
16396
16655
|
if (err) return err;
|
|
16397
16656
|
const userDir = this.getUserDataDir(username);
|
|
16398
|
-
const globalConfig =
|
|
16399
|
-
if (
|
|
16657
|
+
const globalConfig = join20(this.baseDir, "config.json");
|
|
16658
|
+
if (existsSync26(globalConfig)) {
|
|
16400
16659
|
try {
|
|
16401
|
-
const content =
|
|
16402
|
-
writeFileSync3(
|
|
16660
|
+
const content = readFileSync20(globalConfig, "utf-8");
|
|
16661
|
+
writeFileSync3(join20(userDir, "config.json"), content, "utf-8");
|
|
16403
16662
|
} catch {
|
|
16404
16663
|
}
|
|
16405
16664
|
}
|
|
16406
|
-
const globalMemory =
|
|
16407
|
-
if (
|
|
16665
|
+
const globalMemory = join20(this.baseDir, "memory.md");
|
|
16666
|
+
if (existsSync26(globalMemory)) {
|
|
16408
16667
|
try {
|
|
16409
|
-
const content =
|
|
16410
|
-
writeFileSync3(
|
|
16668
|
+
const content = readFileSync20(globalMemory, "utf-8");
|
|
16669
|
+
writeFileSync3(join20(userDir, "memory.md"), content, "utf-8");
|
|
16411
16670
|
} catch {
|
|
16412
16671
|
}
|
|
16413
16672
|
}
|
|
16414
|
-
const globalHistory =
|
|
16415
|
-
if (
|
|
16673
|
+
const globalHistory = join20(this.baseDir, "history");
|
|
16674
|
+
if (existsSync26(globalHistory)) {
|
|
16416
16675
|
try {
|
|
16417
16676
|
const files = readdirSync11(globalHistory).filter((f) => f.endsWith(".json"));
|
|
16418
|
-
const userHistory =
|
|
16677
|
+
const userHistory = join20(userDir, "history");
|
|
16419
16678
|
for (const f of files) {
|
|
16420
16679
|
try {
|
|
16421
|
-
copyFileSync(
|
|
16680
|
+
copyFileSync(join20(globalHistory, f), join20(userHistory, f));
|
|
16422
16681
|
} catch {
|
|
16423
16682
|
}
|
|
16424
16683
|
}
|
|
@@ -16429,9 +16688,9 @@ var AuthManager = class {
|
|
|
16429
16688
|
}
|
|
16430
16689
|
// ── Private methods ────────────────────────────────────────────
|
|
16431
16690
|
loadOrCreate() {
|
|
16432
|
-
if (
|
|
16691
|
+
if (existsSync26(this.usersFile)) {
|
|
16433
16692
|
try {
|
|
16434
|
-
return JSON.parse(
|
|
16693
|
+
return JSON.parse(readFileSync20(this.usersFile, "utf-8"));
|
|
16435
16694
|
} catch {
|
|
16436
16695
|
}
|
|
16437
16696
|
}
|
|
@@ -16483,7 +16742,7 @@ function getModuleDir() {
|
|
|
16483
16742
|
}
|
|
16484
16743
|
} catch {
|
|
16485
16744
|
}
|
|
16486
|
-
return
|
|
16745
|
+
return resolve9(".");
|
|
16487
16746
|
}
|
|
16488
16747
|
async function startWebServer(options = {}) {
|
|
16489
16748
|
const port = options.port ?? 3e3;
|
|
@@ -16548,8 +16807,8 @@ async function startWebServer(options = {}) {
|
|
|
16548
16807
|
}
|
|
16549
16808
|
}
|
|
16550
16809
|
let skillManager = null;
|
|
16551
|
-
const skillsDir =
|
|
16552
|
-
if (
|
|
16810
|
+
const skillsDir = join21(config.getConfigDir(), SKILLS_DIR_NAME);
|
|
16811
|
+
if (existsSync27(skillsDir)) {
|
|
16553
16812
|
skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
|
|
16554
16813
|
skillManager.loadSkills();
|
|
16555
16814
|
const count = skillManager.listSkills().length;
|
|
@@ -16620,18 +16879,18 @@ async function startWebServer(options = {}) {
|
|
|
16620
16879
|
next();
|
|
16621
16880
|
};
|
|
16622
16881
|
const moduleDir = getModuleDir();
|
|
16623
|
-
let clientDir =
|
|
16624
|
-
if (!
|
|
16625
|
-
clientDir =
|
|
16882
|
+
let clientDir = join21(moduleDir, "web", "client");
|
|
16883
|
+
if (!existsSync27(clientDir)) {
|
|
16884
|
+
clientDir = join21(moduleDir, "client");
|
|
16626
16885
|
}
|
|
16627
|
-
if (!
|
|
16628
|
-
clientDir =
|
|
16886
|
+
if (!existsSync27(clientDir)) {
|
|
16887
|
+
clientDir = join21(moduleDir, "..", "..", "src", "web", "client");
|
|
16629
16888
|
}
|
|
16630
|
-
if (!
|
|
16631
|
-
clientDir =
|
|
16889
|
+
if (!existsSync27(clientDir)) {
|
|
16890
|
+
clientDir = join21(process.cwd(), "src", "web", "client");
|
|
16632
16891
|
}
|
|
16633
16892
|
console.log(` Static files: ${clientDir}`);
|
|
16634
|
-
app.use("/vendor", express.static(
|
|
16893
|
+
app.use("/vendor", express.static(join21(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
|
|
16635
16894
|
app.use(express.static(clientDir));
|
|
16636
16895
|
app.get("/api/status", (_req, res) => {
|
|
16637
16896
|
res.json({
|
|
@@ -16698,10 +16957,10 @@ async function startWebServer(options = {}) {
|
|
|
16698
16957
|
app.get("/api/files", requireAuth, (req, res) => {
|
|
16699
16958
|
const cwd = process.cwd();
|
|
16700
16959
|
const prefix = req.query.prefix || "";
|
|
16701
|
-
const targetDir =
|
|
16960
|
+
const targetDir = join21(cwd, prefix);
|
|
16702
16961
|
try {
|
|
16703
|
-
const canonicalTarget = realpathSync(
|
|
16704
|
-
const canonicalCwd = realpathSync(
|
|
16962
|
+
const canonicalTarget = realpathSync(resolve9(targetDir));
|
|
16963
|
+
const canonicalCwd = realpathSync(resolve9(cwd));
|
|
16705
16964
|
if (!canonicalTarget.startsWith(canonicalCwd + sep3) && canonicalTarget !== canonicalCwd) {
|
|
16706
16965
|
res.json({ files: [] });
|
|
16707
16966
|
return;
|
|
@@ -16715,7 +16974,7 @@ async function startWebServer(options = {}) {
|
|
|
16715
16974
|
const entries = readdirSync12(targetDir, { withFileTypes: true });
|
|
16716
16975
|
const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
|
|
16717
16976
|
name: e.name,
|
|
16718
|
-
path: relative4(cwd,
|
|
16977
|
+
path: relative4(cwd, join21(targetDir, e.name)).replace(/\\/g, "/"),
|
|
16719
16978
|
isDir: e.isDirectory()
|
|
16720
16979
|
}));
|
|
16721
16980
|
res.json({ files });
|
|
@@ -16751,8 +17010,8 @@ async function startWebServer(options = {}) {
|
|
|
16751
17010
|
try {
|
|
16752
17011
|
const authUser = req._authUser;
|
|
16753
17012
|
const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
|
|
16754
|
-
const filePath =
|
|
16755
|
-
if (!
|
|
17013
|
+
const filePath = join21(histDir, `${id}.json`);
|
|
17014
|
+
if (!existsSync27(filePath)) {
|
|
16756
17015
|
res.status(404).json({ error: "Session not found" });
|
|
16757
17016
|
return;
|
|
16758
17017
|
}
|
|
@@ -16767,7 +17026,7 @@ async function startWebServer(options = {}) {
|
|
|
16767
17026
|
res.status(404).json({ error: "Session not found" });
|
|
16768
17027
|
return;
|
|
16769
17028
|
}
|
|
16770
|
-
const data = JSON.parse(
|
|
17029
|
+
const data = JSON.parse(readFileSync21(filePath, "utf-8"));
|
|
16771
17030
|
res.json({ session: data });
|
|
16772
17031
|
} catch (err) {
|
|
16773
17032
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
@@ -16780,10 +17039,10 @@ async function startWebServer(options = {}) {
|
|
|
16780
17039
|
return;
|
|
16781
17040
|
}
|
|
16782
17041
|
const cwd = process.cwd();
|
|
16783
|
-
const fullPath =
|
|
17042
|
+
const fullPath = resolve9(join21(cwd, filePath));
|
|
16784
17043
|
try {
|
|
16785
17044
|
const canonicalFull = realpathSync(fullPath);
|
|
16786
|
-
const canonicalCwd = realpathSync(
|
|
17045
|
+
const canonicalCwd = realpathSync(resolve9(cwd));
|
|
16787
17046
|
if (!canonicalFull.startsWith(canonicalCwd + sep3) && canonicalFull !== canonicalCwd) {
|
|
16788
17047
|
res.json({ error: "Access denied" });
|
|
16789
17048
|
return;
|
|
@@ -16798,7 +17057,7 @@ async function startWebServer(options = {}) {
|
|
|
16798
17057
|
res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
|
|
16799
17058
|
return;
|
|
16800
17059
|
}
|
|
16801
|
-
const content =
|
|
17060
|
+
const content = readFileSync21(fullPath, "utf-8");
|
|
16802
17061
|
res.json({ content, size: stat.size });
|
|
16803
17062
|
} catch {
|
|
16804
17063
|
res.json({ error: "Cannot read file" });
|
|
@@ -17002,7 +17261,7 @@ async function startWebServer(options = {}) {
|
|
|
17002
17261
|
});
|
|
17003
17262
|
const MAX_PORT_ATTEMPTS = 10;
|
|
17004
17263
|
let actualPort = port;
|
|
17005
|
-
const result = await new Promise((
|
|
17264
|
+
const result = await new Promise((resolve10, reject) => {
|
|
17006
17265
|
const tryListen = (attempt) => {
|
|
17007
17266
|
server.once("error", (err) => {
|
|
17008
17267
|
if (err.code === "EADDRINUSE" && attempt < MAX_PORT_ATTEMPTS) {
|
|
@@ -17033,7 +17292,7 @@ async function startWebServer(options = {}) {
|
|
|
17033
17292
|
}
|
|
17034
17293
|
console.log(` Press Ctrl+C to stop
|
|
17035
17294
|
`);
|
|
17036
|
-
|
|
17295
|
+
resolve10({ port: actualPort, host, url });
|
|
17037
17296
|
});
|
|
17038
17297
|
};
|
|
17039
17298
|
tryListen(1);
|
|
@@ -17053,17 +17312,17 @@ function resolveProjectMcpPath() {
|
|
|
17053
17312
|
const cwd = process.cwd();
|
|
17054
17313
|
const gitRoot = getGitRoot(cwd);
|
|
17055
17314
|
const projectRoot = gitRoot ?? cwd;
|
|
17056
|
-
const configPath =
|
|
17057
|
-
return
|
|
17315
|
+
const configPath = join21(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
17316
|
+
return existsSync27(configPath) ? configPath : null;
|
|
17058
17317
|
}
|
|
17059
17318
|
function loadProjectMcpConfig() {
|
|
17060
17319
|
const cwd = process.cwd();
|
|
17061
17320
|
const gitRoot = getGitRoot(cwd);
|
|
17062
17321
|
const projectRoot = gitRoot ?? cwd;
|
|
17063
|
-
const configPath =
|
|
17064
|
-
if (!
|
|
17322
|
+
const configPath = join21(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
17323
|
+
if (!existsSync27(configPath)) return null;
|
|
17065
17324
|
try {
|
|
17066
|
-
const raw = JSON.parse(
|
|
17325
|
+
const raw = JSON.parse(readFileSync21(configPath, "utf-8"));
|
|
17067
17326
|
return raw.mcpServers ?? raw;
|
|
17068
17327
|
} catch {
|
|
17069
17328
|
return null;
|