jinzd-ai-cli 0.4.212 → 0.4.214
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 +12 -2
- package/README.zh-CN.md +12 -2
- package/dist/{batch-WIKFEOAZ.js → batch-NPUUDETK.js} +2 -2
- package/dist/{chunk-HW2ALWQJ.js → chunk-5FG7PSVM.js} +2 -2
- package/dist/{chunk-GBBVCZ4W.js → chunk-DBYMX2ZC.js} +1 -1
- package/dist/{chunk-KE4B3NOQ.js → chunk-JOJA35RK.js} +1 -1
- package/dist/{chunk-LU6FBJQ5.js → chunk-JQC7IATA.js} +1 -1
- package/dist/{chunk-N5LB3PPL.js → chunk-R5CJD2AB.js} +615 -118
- package/dist/{chunk-HX6N6QEY.js → chunk-TR6MSPSS.js} +1 -1
- package/dist/{chunk-NYCBOVNF.js → chunk-YFHF3PVB.js} +1 -1
- package/dist/{chunk-IFZX26K7.js → chunk-YMFGGCUP.js} +1 -1
- package/dist/{ci-X3LNUFZV.js → ci-3TU7WCY4.js} +2 -2
- package/dist/{constants-KDWG4KR4.js → constants-BKR7T3QH.js} +1 -1
- package/dist/{doctor-cli-TJTWIW7U.js → doctor-cli-TGVSWKDB.js} +4 -4
- package/dist/electron-server.js +913 -280
- package/dist/{hub-JWSV4MKC.js → hub-GURYEW6F.js} +1 -1
- package/dist/index.js +80 -18
- package/dist/{run-tests-H4Q2PRX6.js → run-tests-F44JA2YW.js} +2 -2
- package/dist/{run-tests-D7YBY4XB.js → run-tests-R6FL6CQ3.js} +1 -1
- package/dist/{server-VNCN7AY5.js → server-FXWCSCDZ.js} +164 -8
- package/dist/{server-OFKGDRYI.js → server-GQXC46R5.js} +4 -4
- package/dist/{task-orchestrator-AL2E642M.js → task-orchestrator-6AZD5WSR.js} +4 -4
- package/dist/{usage-LB6REWPO.js → usage-W7J2QA3G.js} +2 -2
- package/dist/web/client/app.js +4 -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-JOJA35RK.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 join22, dirname as dirname6, resolve as resolve9, relative as relative4, sep as sep3 } from "path";
|
|
65
|
+
import { existsSync as existsSync28, readFileSync as readFileSync22, readdirSync as readdirSync13, 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);
|
|
@@ -9476,6 +9663,218 @@ function formatResults2(query, data, _requested) {
|
|
|
9476
9663
|
return header + "\n" + results.join("\n\n");
|
|
9477
9664
|
}
|
|
9478
9665
|
|
|
9666
|
+
// src/agents/agent-config.ts
|
|
9667
|
+
import { existsSync as existsSync15, readdirSync as readdirSync7, readFileSync as readFileSync12 } from "fs";
|
|
9668
|
+
import { join as join10 } from "path";
|
|
9669
|
+
import { homedir as homedir6 } from "os";
|
|
9670
|
+
var READ_ONLY_TOOLS2 = /* @__PURE__ */ new Set([
|
|
9671
|
+
"read_file",
|
|
9672
|
+
"list_dir",
|
|
9673
|
+
"grep_files",
|
|
9674
|
+
"glob_files",
|
|
9675
|
+
"web_fetch",
|
|
9676
|
+
"google_search",
|
|
9677
|
+
"write_todos",
|
|
9678
|
+
"find_symbol",
|
|
9679
|
+
"get_outline",
|
|
9680
|
+
"find_references",
|
|
9681
|
+
"search_code"
|
|
9682
|
+
]);
|
|
9683
|
+
var BUILTIN_AGENTS = [
|
|
9684
|
+
{
|
|
9685
|
+
name: "explorer",
|
|
9686
|
+
description: "Read-only code exploration and evidence gathering.",
|
|
9687
|
+
permissionProfile: "read-only",
|
|
9688
|
+
allowedTools: [...READ_ONLY_TOOLS2],
|
|
9689
|
+
maxToolRounds: 10,
|
|
9690
|
+
contextPolicy: "task-only",
|
|
9691
|
+
system: "Act as a read-only explorer. Inspect code, collect evidence, and do not modify files.",
|
|
9692
|
+
source: "builtin"
|
|
9693
|
+
},
|
|
9694
|
+
{
|
|
9695
|
+
name: "worker",
|
|
9696
|
+
description: "Implementation and focused fixes within sub-agent safety limits.",
|
|
9697
|
+
permissionProfile: "workspace-write",
|
|
9698
|
+
maxToolRounds: 15,
|
|
9699
|
+
contextPolicy: "task-only",
|
|
9700
|
+
system: "Act as a focused implementation worker. Keep edits scoped and report changed files.",
|
|
9701
|
+
source: "builtin"
|
|
9702
|
+
},
|
|
9703
|
+
{
|
|
9704
|
+
name: "reviewer",
|
|
9705
|
+
description: "Code review focused on bugs, regressions, and missing tests.",
|
|
9706
|
+
permissionProfile: "read-only",
|
|
9707
|
+
allowedTools: [...READ_ONLY_TOOLS2],
|
|
9708
|
+
maxToolRounds: 12,
|
|
9709
|
+
contextPolicy: "task-only",
|
|
9710
|
+
system: "Act as a code reviewer. Lead with concrete findings, file evidence, and residual risks.",
|
|
9711
|
+
source: "builtin"
|
|
9712
|
+
},
|
|
9713
|
+
{
|
|
9714
|
+
name: "security",
|
|
9715
|
+
description: "Security audit with read-only evidence collection.",
|
|
9716
|
+
permissionProfile: "read-only",
|
|
9717
|
+
allowedTools: [...READ_ONLY_TOOLS2],
|
|
9718
|
+
maxToolRounds: 12,
|
|
9719
|
+
contextPolicy: "task-only",
|
|
9720
|
+
system: "Act as a security reviewer. Look for trust-boundary, injection, filesystem, network, and secret-handling issues.",
|
|
9721
|
+
source: "builtin"
|
|
9722
|
+
},
|
|
9723
|
+
{
|
|
9724
|
+
name: "tester",
|
|
9725
|
+
description: "Test planning and regression analysis without direct shell execution.",
|
|
9726
|
+
permissionProfile: "read-only",
|
|
9727
|
+
allowedTools: [...READ_ONLY_TOOLS2],
|
|
9728
|
+
maxToolRounds: 12,
|
|
9729
|
+
contextPolicy: "task-only",
|
|
9730
|
+
system: "Act as a tester. Inspect tests, identify coverage gaps, and propose precise regression checks.",
|
|
9731
|
+
source: "builtin"
|
|
9732
|
+
}
|
|
9733
|
+
];
|
|
9734
|
+
function normalizeName(name) {
|
|
9735
|
+
return name.trim().toLowerCase();
|
|
9736
|
+
}
|
|
9737
|
+
function isStringArray(value) {
|
|
9738
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
9739
|
+
}
|
|
9740
|
+
function parseAgentConfig(raw, source, path3) {
|
|
9741
|
+
if (!raw || typeof raw !== "object") return null;
|
|
9742
|
+
const obj = raw;
|
|
9743
|
+
if (typeof obj.name !== "string" || obj.name.trim() === "") return null;
|
|
9744
|
+
const cfg = {
|
|
9745
|
+
name: obj.name.trim(),
|
|
9746
|
+
source,
|
|
9747
|
+
path: path3
|
|
9748
|
+
};
|
|
9749
|
+
if (typeof obj.description === "string") cfg.description = obj.description;
|
|
9750
|
+
if (typeof obj.provider === "string") cfg.provider = obj.provider;
|
|
9751
|
+
if (typeof obj.model === "string") cfg.model = obj.model;
|
|
9752
|
+
if (typeof obj.system === "string") cfg.system = obj.system;
|
|
9753
|
+
if (typeof obj.instructions === "string") cfg.instructions = obj.instructions;
|
|
9754
|
+
if (typeof obj.systemInstructions === "string") cfg.systemInstructions = obj.systemInstructions;
|
|
9755
|
+
if (isStringArray(obj.allowedTools)) cfg.allowedTools = obj.allowedTools;
|
|
9756
|
+
if (isStringArray(obj.blockedTools)) cfg.blockedTools = obj.blockedTools;
|
|
9757
|
+
if (typeof obj.permissionProfile === "string") cfg.permissionProfile = obj.permissionProfile;
|
|
9758
|
+
if (typeof obj.maxToolRounds === "number" && Number.isFinite(obj.maxToolRounds)) cfg.maxToolRounds = obj.maxToolRounds;
|
|
9759
|
+
if (typeof obj.contextPolicy === "string") cfg.contextPolicy = obj.contextPolicy;
|
|
9760
|
+
return cfg;
|
|
9761
|
+
}
|
|
9762
|
+
function loadAgentDir(dir, source) {
|
|
9763
|
+
if (!existsSync15(dir)) return [];
|
|
9764
|
+
const out = [];
|
|
9765
|
+
for (const file of readdirSync7(dir)) {
|
|
9766
|
+
if (!file.endsWith(".json")) continue;
|
|
9767
|
+
const path3 = join10(dir, file);
|
|
9768
|
+
try {
|
|
9769
|
+
const parsed = JSON.parse(readFileSync12(path3, "utf-8"));
|
|
9770
|
+
const cfg = parseAgentConfig(parsed, source, path3);
|
|
9771
|
+
if (cfg) out.push(cfg);
|
|
9772
|
+
} catch {
|
|
9773
|
+
}
|
|
9774
|
+
}
|
|
9775
|
+
return out;
|
|
9776
|
+
}
|
|
9777
|
+
function getAgentDirs(configDir, cwd = process.cwd()) {
|
|
9778
|
+
return {
|
|
9779
|
+
user: join10(configDir ?? join10(homedir6(), CONFIG_DIR_NAME), "agents"),
|
|
9780
|
+
project: join10(cwd, CONFIG_DIR_NAME, "agents")
|
|
9781
|
+
};
|
|
9782
|
+
}
|
|
9783
|
+
function listAgentConfigs(configDir, cwd = process.cwd()) {
|
|
9784
|
+
const dirs = getAgentDirs(configDir, cwd);
|
|
9785
|
+
const byName = /* @__PURE__ */ new Map();
|
|
9786
|
+
for (const cfg of BUILTIN_AGENTS) byName.set(normalizeName(cfg.name), { ...cfg });
|
|
9787
|
+
for (const cfg of loadAgentDir(dirs.user, "user")) byName.set(normalizeName(cfg.name), cfg);
|
|
9788
|
+
for (const cfg of loadAgentDir(dirs.project, "project")) byName.set(normalizeName(cfg.name), cfg);
|
|
9789
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
9790
|
+
}
|
|
9791
|
+
function resolveAgentConfig(name, configDir, cwd = process.cwd()) {
|
|
9792
|
+
const target = normalizeName(name);
|
|
9793
|
+
return listAgentConfigs(configDir, cwd).find((cfg) => normalizeName(cfg.name) === target);
|
|
9794
|
+
}
|
|
9795
|
+
function getAgentInstructions(agent) {
|
|
9796
|
+
return agent.system ?? agent.systemInstructions ?? agent.instructions;
|
|
9797
|
+
}
|
|
9798
|
+
function getEffectiveAgentTools(agent) {
|
|
9799
|
+
let allowed = new Set(SUBAGENT_ALLOWED_TOOLS);
|
|
9800
|
+
if (agent.permissionProfile === "read-only") {
|
|
9801
|
+
allowed = new Set([...allowed].filter((name) => READ_ONLY_TOOLS2.has(name)));
|
|
9802
|
+
}
|
|
9803
|
+
if (agent.allowedTools && agent.allowedTools.length > 0) {
|
|
9804
|
+
const requested = new Set(agent.allowedTools);
|
|
9805
|
+
allowed = new Set([...allowed].filter((name) => requested.has(name)));
|
|
9806
|
+
}
|
|
9807
|
+
if (agent.blockedTools && agent.blockedTools.length > 0) {
|
|
9808
|
+
for (const name of agent.blockedTools) allowed.delete(name);
|
|
9809
|
+
}
|
|
9810
|
+
return allowed;
|
|
9811
|
+
}
|
|
9812
|
+
function getEffectiveMaxRounds(requestedMaxRounds, agent) {
|
|
9813
|
+
const agentMax = agent.maxToolRounds && agent.maxToolRounds > 0 ? Math.round(agent.maxToolRounds) : SUBAGENT_MAX_ROUNDS_LIMIT;
|
|
9814
|
+
return Math.min(requestedMaxRounds, agentMax, SUBAGENT_MAX_ROUNDS_LIMIT);
|
|
9815
|
+
}
|
|
9816
|
+
|
|
9817
|
+
// src/agents/agent-runtime.ts
|
|
9818
|
+
var nextId = 1;
|
|
9819
|
+
var preferredAgentName = "worker";
|
|
9820
|
+
var runs = [];
|
|
9821
|
+
var MAX_RUNS = 50;
|
|
9822
|
+
function nowIso() {
|
|
9823
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
9824
|
+
}
|
|
9825
|
+
function startAgentRun(input) {
|
|
9826
|
+
const run = {
|
|
9827
|
+
id: `agent-${nextId++}`,
|
|
9828
|
+
agentName: input.agentName,
|
|
9829
|
+
task: input.task,
|
|
9830
|
+
status: "running",
|
|
9831
|
+
startedAt: nowIso(),
|
|
9832
|
+
toolNames: [],
|
|
9833
|
+
agentIndex: input.agentIndex,
|
|
9834
|
+
stopRequested: false
|
|
9835
|
+
};
|
|
9836
|
+
runs.unshift(run);
|
|
9837
|
+
if (runs.length > MAX_RUNS) runs.splice(MAX_RUNS);
|
|
9838
|
+
return run;
|
|
9839
|
+
}
|
|
9840
|
+
function recordAgentTool(runId, toolName) {
|
|
9841
|
+
const run = runs.find((r) => r.id === runId);
|
|
9842
|
+
if (!run) return;
|
|
9843
|
+
if (!run.toolNames.includes(toolName)) run.toolNames.push(toolName);
|
|
9844
|
+
}
|
|
9845
|
+
function finishAgentRun(runId, status, summary, error) {
|
|
9846
|
+
const run = runs.find((r) => r.id === runId);
|
|
9847
|
+
if (!run) return;
|
|
9848
|
+
run.status = status;
|
|
9849
|
+
run.finishedAt = nowIso();
|
|
9850
|
+
if (summary) run.summary = summary;
|
|
9851
|
+
if (error) run.error = error;
|
|
9852
|
+
}
|
|
9853
|
+
function listAgentRuns() {
|
|
9854
|
+
return runs.map((run) => ({ ...run, toolNames: [...run.toolNames] }));
|
|
9855
|
+
}
|
|
9856
|
+
function requestAgentStop(idOrAll) {
|
|
9857
|
+
const target = idOrAll.trim().toLowerCase();
|
|
9858
|
+
let count = 0;
|
|
9859
|
+
for (const run of runs) {
|
|
9860
|
+
if (run.status !== "running") continue;
|
|
9861
|
+
if (target === "all" || run.id.toLowerCase() === target) {
|
|
9862
|
+
run.stopRequested = true;
|
|
9863
|
+
count++;
|
|
9864
|
+
}
|
|
9865
|
+
}
|
|
9866
|
+
return count;
|
|
9867
|
+
}
|
|
9868
|
+
function isAgentStopRequested(runId) {
|
|
9869
|
+
return runs.find((r) => r.id === runId)?.stopRequested ?? false;
|
|
9870
|
+
}
|
|
9871
|
+
function setPreferredAgentName(name) {
|
|
9872
|
+
preferredAgentName = name.trim() || "worker";
|
|
9873
|
+
}
|
|
9874
|
+
function getPreferredAgentName() {
|
|
9875
|
+
return preferredAgentName;
|
|
9876
|
+
}
|
|
9877
|
+
|
|
9479
9878
|
// src/tools/builtin/spawn-agent.ts
|
|
9480
9879
|
var spawnAgentContext = {
|
|
9481
9880
|
provider: null,
|
|
@@ -9490,18 +9889,27 @@ function agentPrefix(agentIndex) {
|
|
|
9490
9889
|
return theme.dim(` \u2503${agentIndex} `);
|
|
9491
9890
|
}
|
|
9492
9891
|
var SubAgentExecutor = class {
|
|
9493
|
-
constructor(registry, agentIndex = null) {
|
|
9892
|
+
constructor(registry, agentIndex = null, runId) {
|
|
9494
9893
|
this.registry = registry;
|
|
9894
|
+
this.runId = runId;
|
|
9495
9895
|
this.prefix = agentPrefix(agentIndex);
|
|
9496
9896
|
}
|
|
9497
9897
|
round = 0;
|
|
9498
9898
|
totalRounds = 0;
|
|
9499
9899
|
prefix = PREFIX;
|
|
9900
|
+
toolNames = [];
|
|
9500
9901
|
setRoundInfo(current, total) {
|
|
9501
9902
|
this.round = current;
|
|
9502
9903
|
this.totalRounds = total;
|
|
9503
9904
|
}
|
|
9504
9905
|
async execute(call) {
|
|
9906
|
+
if (this.runId && isAgentStopRequested(this.runId)) {
|
|
9907
|
+
return {
|
|
9908
|
+
callId: call.id,
|
|
9909
|
+
content: "Sub-agent stop requested.",
|
|
9910
|
+
isError: true
|
|
9911
|
+
};
|
|
9912
|
+
}
|
|
9505
9913
|
const tool = this.registry.get(call.name);
|
|
9506
9914
|
if (!tool) {
|
|
9507
9915
|
return {
|
|
@@ -9522,6 +9930,8 @@ var SubAgentExecutor = class {
|
|
|
9522
9930
|
};
|
|
9523
9931
|
}
|
|
9524
9932
|
this.printToolCall(call, dangerLevel);
|
|
9933
|
+
if (!this.toolNames.includes(call.name)) this.toolNames.push(call.name);
|
|
9934
|
+
if (this.runId) recordAgentTool(this.runId, call.name);
|
|
9525
9935
|
try {
|
|
9526
9936
|
const rawContent = await runTool(tool, call.arguments, call.name);
|
|
9527
9937
|
const content = truncateOutput(rawContent, call.name);
|
|
@@ -9541,7 +9951,6 @@ var SubAgentExecutor = class {
|
|
|
9541
9951
|
}
|
|
9542
9952
|
return results;
|
|
9543
9953
|
}
|
|
9544
|
-
// ── 带前缀的终端输出 ──
|
|
9545
9954
|
printPrefixed(text) {
|
|
9546
9955
|
for (const line of text.split("\n")) {
|
|
9547
9956
|
console.log(this.prefix + line);
|
|
@@ -9582,7 +9991,7 @@ var SubAgentExecutor = class {
|
|
|
9582
9991
|
}
|
|
9583
9992
|
}
|
|
9584
9993
|
};
|
|
9585
|
-
function buildSubAgentSystemPrompt(task, parentSystemPrompt) {
|
|
9994
|
+
function buildSubAgentSystemPrompt(task, agent, parentSystemPrompt) {
|
|
9586
9995
|
const parts = [];
|
|
9587
9996
|
if (parentSystemPrompt) {
|
|
9588
9997
|
parts.push(parentSystemPrompt);
|
|
@@ -9590,6 +9999,8 @@ function buildSubAgentSystemPrompt(task, parentSystemPrompt) {
|
|
|
9590
9999
|
parts.push(
|
|
9591
10000
|
`# Sub-Agent Mode
|
|
9592
10001
|
|
|
10002
|
+
**Agent Role**: ${agent.name}${agent.description ? ` \u2014 ${agent.description}` : ""}
|
|
10003
|
+
|
|
9593
10004
|
You are a focused sub-agent delegated by the main agent to complete a specific task.
|
|
9594
10005
|
|
|
9595
10006
|
**Your Task**:
|
|
@@ -9603,13 +10014,19 @@ ${task}
|
|
|
9603
10014
|
5. Destructive operations (rm -rf etc.) will be automatically blocked
|
|
9604
10015
|
6. Be efficient, minimize unnecessary tool call rounds`
|
|
9605
10016
|
);
|
|
10017
|
+
const agentInstructions = getAgentInstructions(agent);
|
|
10018
|
+
if (agentInstructions) {
|
|
10019
|
+
parts.push(`# Agent Instructions
|
|
10020
|
+
|
|
10021
|
+
${agentInstructions}`);
|
|
10022
|
+
}
|
|
9606
10023
|
return parts.join("\n\n---\n\n");
|
|
9607
10024
|
}
|
|
9608
|
-
function printSubAgentHeader(task, maxRounds, agentIndex = null) {
|
|
10025
|
+
function printSubAgentHeader(task, maxRounds, agentName, agentIndex = null) {
|
|
9609
10026
|
const prefix = agentPrefix(agentIndex);
|
|
9610
10027
|
console.log();
|
|
9611
10028
|
console.log(theme.dim(" \u250F\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
|
|
9612
|
-
const label = agentIndex !== null ? `\u{1F916} Sub-Agent #${agentIndex} Spawned` :
|
|
10029
|
+
const label = agentIndex !== null ? `\u{1F916} Sub-Agent #${agentIndex} Spawned (${agentName})` : `\u{1F916} Sub-Agent Spawned (${agentName})`;
|
|
9613
10030
|
console.log(prefix + theme.toolCall(label));
|
|
9614
10031
|
console.log(
|
|
9615
10032
|
prefix + theme.dim("Task: ") + (task.slice(0, 120) + (task.length > 120 ? "..." : ""))
|
|
@@ -9632,58 +10049,110 @@ function printSubAgentFooter(usage, agentIndex = null) {
|
|
|
9632
10049
|
console.log(theme.dim(" \u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
|
|
9633
10050
|
console.log();
|
|
9634
10051
|
}
|
|
9635
|
-
|
|
10052
|
+
function summarizeContent(content) {
|
|
10053
|
+
const flat = content.replace(/\s+/g, " ").trim();
|
|
10054
|
+
return flat.length > 500 ? flat.slice(0, 497) + "..." : flat;
|
|
10055
|
+
}
|
|
10056
|
+
function extractEvidence(content, toolNames) {
|
|
10057
|
+
const evidence = /* @__PURE__ */ new Set();
|
|
10058
|
+
if (toolNames.length > 0) evidence.add(`Tools: ${toolNames.join(", ")}`);
|
|
10059
|
+
const matches = content.match(/(?:[\w.-]+[\\/])+[\w.-]+\.(?:ts|tsx|js|jsx|json|md|css|html|py|go|rs|yml|yaml)/g) ?? [];
|
|
10060
|
+
for (const match of matches.slice(0, 8)) evidence.add(match);
|
|
10061
|
+
return [...evidence].slice(0, 10);
|
|
10062
|
+
}
|
|
10063
|
+
function formatAgentResult(input) {
|
|
10064
|
+
const lines = [input.title, "", `**Agent**: ${input.agentName}`];
|
|
10065
|
+
if (input.task) lines.push(`**Task**: ${input.task}`);
|
|
10066
|
+
lines.push("", "### Summary", input.summary || "(no summary)", "", "### Key Evidence");
|
|
10067
|
+
if (input.evidence.length > 0) {
|
|
10068
|
+
for (const item of input.evidence) lines.push(`- ${item}`);
|
|
10069
|
+
} else {
|
|
10070
|
+
lines.push("- No structured evidence extracted.");
|
|
10071
|
+
}
|
|
10072
|
+
lines.push("", "### Tools Used");
|
|
10073
|
+
lines.push(input.toolNames.length > 0 ? input.toolNames.join(", ") : "No tools used.");
|
|
10074
|
+
lines.push("", "### Full Result", input.content, "");
|
|
10075
|
+
lines.push(`*Tokens: ${input.usage.inputTokens} in / ${input.usage.outputTokens} out*`);
|
|
10076
|
+
return lines;
|
|
10077
|
+
}
|
|
10078
|
+
async function runSubAgent(task, maxRounds, agentIndex, ctx, agent) {
|
|
9636
10079
|
if (!ctx.provider) {
|
|
9637
10080
|
throw new ToolError("spawn_agent", "provider not initialized (context not injected)");
|
|
9638
10081
|
}
|
|
10082
|
+
const providerFromConfig = agent.provider && ctx.providers?.has(agent.provider) ? ctx.providers.get(agent.provider) : void 0;
|
|
10083
|
+
const provider = providerFromConfig ?? ctx.provider;
|
|
10084
|
+
const model = agent.model ?? (providerFromConfig?.info?.defaultModel ?? ctx.model);
|
|
10085
|
+
const run = startAgentRun({
|
|
10086
|
+
agentName: agent.name,
|
|
10087
|
+
task,
|
|
10088
|
+
agentIndex: agentIndex ?? void 0
|
|
10089
|
+
});
|
|
10090
|
+
const effectiveTools = getEffectiveAgentTools(agent);
|
|
9639
10091
|
const subRegistry = new ToolRegistry();
|
|
9640
10092
|
for (const tool of subRegistry.listAll()) {
|
|
9641
|
-
if (!SUBAGENT_ALLOWED_TOOLS.has(tool.definition.name)) {
|
|
10093
|
+
if (!SUBAGENT_ALLOWED_TOOLS.has(tool.definition.name) || !effectiveTools.has(tool.definition.name)) {
|
|
9642
10094
|
subRegistry.unregister(tool.definition.name);
|
|
9643
10095
|
}
|
|
9644
10096
|
}
|
|
9645
|
-
const subExecutor = new SubAgentExecutor(subRegistry, agentIndex);
|
|
10097
|
+
const subExecutor = new SubAgentExecutor(subRegistry, agentIndex, run.id);
|
|
9646
10098
|
const subMessages = [
|
|
9647
10099
|
{ role: "user", content: task, timestamp: /* @__PURE__ */ new Date() }
|
|
9648
10100
|
];
|
|
9649
|
-
const subSystemPrompt = buildSubAgentSystemPrompt(task, ctx.systemPrompt);
|
|
10101
|
+
const subSystemPrompt = buildSubAgentSystemPrompt(task, agent, ctx.systemPrompt);
|
|
9650
10102
|
const toolDefs = subRegistry.getDefinitions();
|
|
9651
|
-
printSubAgentHeader(task, maxRounds, agentIndex);
|
|
9652
|
-
|
|
9653
|
-
|
|
9654
|
-
|
|
9655
|
-
|
|
9656
|
-
|
|
9657
|
-
|
|
9658
|
-
|
|
9659
|
-
|
|
9660
|
-
|
|
9661
|
-
|
|
9662
|
-
|
|
9663
|
-
|
|
9664
|
-
|
|
9665
|
-
|
|
9666
|
-
|
|
9667
|
-
|
|
9668
|
-
|
|
9669
|
-
|
|
9670
|
-
|
|
9671
|
-
|
|
9672
|
-
|
|
9673
|
-
|
|
9674
|
-
|
|
10103
|
+
printSubAgentHeader(task, maxRounds, agent.name, agentIndex);
|
|
10104
|
+
try {
|
|
10105
|
+
const loop = await runLeanAgentLoop({
|
|
10106
|
+
provider,
|
|
10107
|
+
messages: subMessages,
|
|
10108
|
+
model,
|
|
10109
|
+
maxRounds,
|
|
10110
|
+
chatParams: {
|
|
10111
|
+
temperature: ctx.modelParams.temperature,
|
|
10112
|
+
maxTokens: ctx.modelParams.maxTokens,
|
|
10113
|
+
timeout: ctx.modelParams.timeout,
|
|
10114
|
+
thinking: ctx.modelParams.thinking
|
|
10115
|
+
},
|
|
10116
|
+
executeTools: (calls) => subExecutor.executeAll(calls),
|
|
10117
|
+
systemPromptForRound: () => subSystemPrompt,
|
|
10118
|
+
toolDefsForRound: () => toolDefs,
|
|
10119
|
+
onRoundStart: (round) => {
|
|
10120
|
+
subExecutor.setRoundInfo(round + 1, maxRounds);
|
|
10121
|
+
if (ctx.configManager) {
|
|
10122
|
+
googleSearchContext.configManager = ctx.configManager;
|
|
10123
|
+
}
|
|
10124
|
+
},
|
|
10125
|
+
onRoundsExhausted: () => `(Sub-agent reached maximum rounds (${maxRounds}) without producing a final response)`,
|
|
10126
|
+
onError: (errMsg) => {
|
|
10127
|
+
process.stderr.write(`
|
|
9675
10128
|
[spawn_agent] Error in sub-agent loop: ${errMsg}
|
|
9676
10129
|
`);
|
|
9677
|
-
|
|
9678
|
-
|
|
9679
|
-
|
|
9680
|
-
|
|
9681
|
-
|
|
10130
|
+
return `(Sub-agent error: ${errMsg})`;
|
|
10131
|
+
}
|
|
10132
|
+
});
|
|
10133
|
+
printSubAgentFooter(loop.usage, agentIndex);
|
|
10134
|
+
const summary = summarizeContent(loop.content);
|
|
10135
|
+
const evidence = extractEvidence(loop.content, subExecutor.toolNames);
|
|
10136
|
+
const stopped = isAgentStopRequested(run.id);
|
|
10137
|
+
finishAgentRun(run.id, stopped ? "stopped" : "ok", summary);
|
|
10138
|
+
return {
|
|
10139
|
+
content: loop.content,
|
|
10140
|
+
usage: loop.usage,
|
|
10141
|
+
toolNames: subExecutor.toolNames,
|
|
10142
|
+
summary,
|
|
10143
|
+
evidence,
|
|
10144
|
+
agentName: agent.name
|
|
10145
|
+
};
|
|
10146
|
+
} catch (err) {
|
|
10147
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
10148
|
+
finishAgentRun(run.id, "error", void 0, message);
|
|
10149
|
+
throw err;
|
|
10150
|
+
}
|
|
9682
10151
|
}
|
|
9683
10152
|
var spawnAgentTool = {
|
|
9684
10153
|
definition: {
|
|
9685
10154
|
name: "spawn_agent",
|
|
9686
|
-
description: "Delegate subtask(s) to independent
|
|
10155
|
+
description: "Delegate subtask(s) to independent named agents. Each sub-agent has its own conversation context and agentic tool-call loop, inherits the parent safety boundary, and can only narrow the sub-agent tool whitelist. Pass `agent` to select a built-in or configured role. Built-ins: explorer, worker, reviewer, security, tester. Pass `task` for a single sub-agent, or `tasks` (array) to run multiple sub-agents IN PARALLEL. Returns summary, key evidence, and tools used for each sub-agent result.",
|
|
9687
10156
|
parameters: {
|
|
9688
10157
|
task: {
|
|
9689
10158
|
type: "string",
|
|
@@ -9701,7 +10170,12 @@ var spawnAgentTool = {
|
|
|
9701
10170
|
},
|
|
9702
10171
|
max_rounds: {
|
|
9703
10172
|
type: "number",
|
|
9704
|
-
description: "Max tool-call rounds per sub-agent (1-
|
|
10173
|
+
description: "Max tool-call rounds per sub-agent (1-30, default 15). Agent config can only reduce this value.",
|
|
10174
|
+
required: false
|
|
10175
|
+
},
|
|
10176
|
+
agent: {
|
|
10177
|
+
type: "string",
|
|
10178
|
+
description: "Agent role name from built-ins or ~/.aicli/agents / .aicli/agents JSON configs. Built-ins: explorer, worker, reviewer, security, tester.",
|
|
9705
10179
|
required: false
|
|
9706
10180
|
}
|
|
9707
10181
|
},
|
|
@@ -9721,10 +10195,17 @@ var spawnAgentTool = {
|
|
|
9721
10195
|
Math.max(Math.round(rawMaxRounds), 1),
|
|
9722
10196
|
SUBAGENT_MAX_ROUNDS_LIMIT
|
|
9723
10197
|
);
|
|
10198
|
+
const requestedAgentName = typeof args["agent"] === "string" && args["agent"].trim() ? args["agent"].trim() : getPreferredAgentName();
|
|
9724
10199
|
const ctx = spawnAgentContext;
|
|
9725
10200
|
if (!ctx.provider) {
|
|
9726
10201
|
throw new ToolError("spawn_agent", "provider not initialized (context not injected)");
|
|
9727
10202
|
}
|
|
10203
|
+
const agent = resolveAgentConfig(requestedAgentName, ctx.configManager?.getConfigDir());
|
|
10204
|
+
if (!agent) {
|
|
10205
|
+
const available = listAgentConfigs(ctx.configManager?.getConfigDir()).map((cfg) => cfg.name).join(", ");
|
|
10206
|
+
throw new ToolError("spawn_agent", `unknown agent "${requestedAgentName}". Available agents: ${available}`);
|
|
10207
|
+
}
|
|
10208
|
+
const effectiveMaxRounds = getEffectiveMaxRounds(maxRounds, agent);
|
|
9728
10209
|
const hookConfig = ctx.configManager?.get("hooks") ?? void 0;
|
|
9729
10210
|
const hookConfigDir = ctx.configManager?.getConfigDir() ?? process.cwd();
|
|
9730
10211
|
const taskCount = singleTask ? 1 : tasksArr.length;
|
|
@@ -9732,7 +10213,8 @@ var spawnAgentTool = {
|
|
|
9732
10213
|
task: singleTask || void 0,
|
|
9733
10214
|
tasks: tasksArr.length > 0 ? tasksArr : void 0,
|
|
9734
10215
|
taskCount,
|
|
9735
|
-
maxRounds
|
|
10216
|
+
maxRounds: effectiveMaxRounds,
|
|
10217
|
+
agent: agent.name
|
|
9736
10218
|
};
|
|
9737
10219
|
const startDecisions = runLifecycleHooks(hookConfig, "SubagentStart", hookPayload, hookConfigDir);
|
|
9738
10220
|
const startDeny = startDecisions.find((d) => d.action === "deny");
|
|
@@ -9744,32 +10226,37 @@ var spawnAgentTool = {
|
|
|
9744
10226
|
subAgentGuard.active = true;
|
|
9745
10227
|
try {
|
|
9746
10228
|
if (singleTask) {
|
|
9747
|
-
const
|
|
10229
|
+
const result = await runSubAgent(singleTask, effectiveMaxRounds, null, ctx, agent);
|
|
9748
10230
|
subagentStatus = "ok";
|
|
9749
|
-
return
|
|
9750
|
-
"## Sub-Agent Result",
|
|
9751
|
-
|
|
9752
|
-
|
|
9753
|
-
|
|
9754
|
-
|
|
9755
|
-
|
|
9756
|
-
|
|
10231
|
+
return formatAgentResult({
|
|
10232
|
+
title: "## Sub-Agent Result",
|
|
10233
|
+
content: result.content,
|
|
10234
|
+
usage: result.usage,
|
|
10235
|
+
toolNames: result.toolNames,
|
|
10236
|
+
summary: result.summary,
|
|
10237
|
+
evidence: result.evidence,
|
|
10238
|
+
agentName: result.agentName
|
|
10239
|
+
}).join("\n");
|
|
9757
10240
|
}
|
|
9758
10241
|
console.log();
|
|
9759
|
-
console.log(theme.toolCall(`\u{1F916} Spawning ${tasksArr.length} sub-agents in parallel...`));
|
|
10242
|
+
console.log(theme.toolCall(`\u{1F916} Spawning ${tasksArr.length} sub-agents in parallel as ${agent.name}...`));
|
|
9760
10243
|
const results = await Promise.all(
|
|
9761
|
-
tasksArr.map((t, i) => runSubAgent(t,
|
|
10244
|
+
tasksArr.map((t, i) => runSubAgent(t, effectiveMaxRounds, i + 1, ctx, agent))
|
|
9762
10245
|
);
|
|
9763
10246
|
const totalIn = results.reduce((s, r) => s + r.usage.inputTokens, 0);
|
|
9764
10247
|
const totalOut = results.reduce((s, r) => s + r.usage.outputTokens, 0);
|
|
9765
10248
|
const lines = [`## Parallel Sub-Agent Results (${results.length} agents)`, ""];
|
|
9766
10249
|
results.forEach((r, i) => {
|
|
9767
|
-
lines.push(
|
|
9768
|
-
|
|
9769
|
-
|
|
9770
|
-
|
|
9771
|
-
|
|
9772
|
-
|
|
10250
|
+
lines.push(...formatAgentResult({
|
|
10251
|
+
title: `### Sub-Agent #${i + 1}`,
|
|
10252
|
+
task: `${tasksArr[i].slice(0, 200)}${tasksArr[i].length > 200 ? "..." : ""}`,
|
|
10253
|
+
content: r.content,
|
|
10254
|
+
usage: r.usage,
|
|
10255
|
+
toolNames: r.toolNames,
|
|
10256
|
+
summary: r.summary,
|
|
10257
|
+
evidence: r.evidence,
|
|
10258
|
+
agentName: r.agentName
|
|
10259
|
+
}));
|
|
9773
10260
|
lines.push("");
|
|
9774
10261
|
});
|
|
9775
10262
|
lines.push("---");
|
|
@@ -10000,14 +10487,14 @@ var taskStopTool = {
|
|
|
10000
10487
|
|
|
10001
10488
|
// src/tools/builtin/git-tools.ts
|
|
10002
10489
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
10003
|
-
import { existsSync as
|
|
10004
|
-
import { join as
|
|
10490
|
+
import { existsSync as existsSync16 } from "fs";
|
|
10491
|
+
import { join as join11 } from "path";
|
|
10005
10492
|
function assertGitRepo(cwd) {
|
|
10006
10493
|
let dir = cwd;
|
|
10007
10494
|
const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
|
|
10008
10495
|
while (dir && dir !== root) {
|
|
10009
|
-
if (
|
|
10010
|
-
const parent =
|
|
10496
|
+
if (existsSync16(join11(dir, ".git"))) return;
|
|
10497
|
+
const parent = join11(dir, "..");
|
|
10011
10498
|
if (parent === dir) break;
|
|
10012
10499
|
dir = parent;
|
|
10013
10500
|
}
|
|
@@ -10272,9 +10759,9 @@ ${commitOutput.trim()}`;
|
|
|
10272
10759
|
};
|
|
10273
10760
|
|
|
10274
10761
|
// src/tools/builtin/notebook-edit.ts
|
|
10275
|
-
import { readFileSync as
|
|
10762
|
+
import { readFileSync as readFileSync13, existsSync as existsSync17 } from "fs";
|
|
10276
10763
|
import { writeFile } from "fs/promises";
|
|
10277
|
-
import { resolve as
|
|
10764
|
+
import { resolve as resolve6, extname as extname2 } from "path";
|
|
10278
10765
|
var notebookEditTool = {
|
|
10279
10766
|
definition: {
|
|
10280
10767
|
name: "notebook_edit",
|
|
@@ -10323,14 +10810,14 @@ var notebookEditTool = {
|
|
|
10323
10810
|
if (!Number.isInteger(cellIndexRaw) || cellIndexRaw < 1) {
|
|
10324
10811
|
throw new ToolError("notebook_edit", "cell_index must be a positive integer (1-based)");
|
|
10325
10812
|
}
|
|
10326
|
-
const absPath =
|
|
10813
|
+
const absPath = resolve6(filePath);
|
|
10327
10814
|
if (extname2(absPath).toLowerCase() !== ".ipynb") {
|
|
10328
10815
|
throw new ToolError("notebook_edit", "path must point to a .ipynb file");
|
|
10329
10816
|
}
|
|
10330
|
-
if (!
|
|
10817
|
+
if (!existsSync17(absPath)) {
|
|
10331
10818
|
throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
|
|
10332
10819
|
}
|
|
10333
|
-
const raw =
|
|
10820
|
+
const raw = readFileSync13(absPath, "utf-8");
|
|
10334
10821
|
const nb = parseNotebook(raw);
|
|
10335
10822
|
const cellIdx0 = cellIndexRaw - 1;
|
|
10336
10823
|
undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
|
|
@@ -10747,8 +11234,8 @@ function estimateToolDefinitionTokens(def) {
|
|
|
10747
11234
|
|
|
10748
11235
|
// src/tools/registry.ts
|
|
10749
11236
|
import { pathToFileURL } from "url";
|
|
10750
|
-
import { existsSync as
|
|
10751
|
-
import { join as
|
|
11237
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync8, readdirSync as readdirSync8 } from "fs";
|
|
11238
|
+
import { join as join12 } from "path";
|
|
10752
11239
|
var ToolRegistry = class {
|
|
10753
11240
|
tools = /* @__PURE__ */ new Map();
|
|
10754
11241
|
pluginToolNames = /* @__PURE__ */ new Set();
|
|
@@ -10909,7 +11396,7 @@ var ToolRegistry = class {
|
|
|
10909
11396
|
* Returns the number of successfully loaded plugins.
|
|
10910
11397
|
*/
|
|
10911
11398
|
async loadPlugins(pluginsDir, allowPlugins = false) {
|
|
10912
|
-
if (!
|
|
11399
|
+
if (!existsSync18(pluginsDir)) {
|
|
10913
11400
|
try {
|
|
10914
11401
|
mkdirSync8(pluginsDir, { recursive: true });
|
|
10915
11402
|
} catch {
|
|
@@ -10918,7 +11405,7 @@ var ToolRegistry = class {
|
|
|
10918
11405
|
}
|
|
10919
11406
|
let files;
|
|
10920
11407
|
try {
|
|
10921
|
-
files =
|
|
11408
|
+
files = readdirSync8(pluginsDir).filter((f) => f.endsWith(".js"));
|
|
10922
11409
|
} catch {
|
|
10923
11410
|
return 0;
|
|
10924
11411
|
}
|
|
@@ -10935,12 +11422,12 @@ var ToolRegistry = class {
|
|
|
10935
11422
|
process.stderr.write(
|
|
10936
11423
|
`
|
|
10937
11424
|
[plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
|
|
10938
|
-
` + files.map((f) => ` + ${
|
|
11425
|
+
` + files.map((f) => ` + ${join12(pluginsDir, f)}`).join("\n") + "\n\n"
|
|
10939
11426
|
);
|
|
10940
11427
|
let loaded = 0;
|
|
10941
11428
|
for (const file of files) {
|
|
10942
11429
|
try {
|
|
10943
|
-
const fileUrl = pathToFileURL(
|
|
11430
|
+
const fileUrl = pathToFileURL(join12(pluginsDir, file)).href;
|
|
10944
11431
|
const mod = await import(fileUrl);
|
|
10945
11432
|
const tool = mod.tool ?? mod.default?.tool ?? mod.default;
|
|
10946
11433
|
if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
|
|
@@ -11102,7 +11589,7 @@ var McpClient = class {
|
|
|
11102
11589
|
// 内部方法:JSON-RPC 通信
|
|
11103
11590
|
// ══════════════════════════════════════════════════════════════════
|
|
11104
11591
|
sendRequest(method, params) {
|
|
11105
|
-
return new Promise((
|
|
11592
|
+
return new Promise((resolve10, reject) => {
|
|
11106
11593
|
if (!this.process?.stdin?.writable) {
|
|
11107
11594
|
return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
|
|
11108
11595
|
}
|
|
@@ -11126,7 +11613,7 @@ var McpClient = class {
|
|
|
11126
11613
|
this.pendingRequests.set(id, {
|
|
11127
11614
|
resolve: (result) => {
|
|
11128
11615
|
cleanup();
|
|
11129
|
-
|
|
11616
|
+
resolve10(result);
|
|
11130
11617
|
},
|
|
11131
11618
|
reject: (error) => {
|
|
11132
11619
|
cleanup();
|
|
@@ -11203,13 +11690,13 @@ var McpClient = class {
|
|
|
11203
11690
|
}
|
|
11204
11691
|
/** Promise 超时包装 */
|
|
11205
11692
|
withTimeout(promise, ms, label) {
|
|
11206
|
-
return new Promise((
|
|
11693
|
+
return new Promise((resolve10, reject) => {
|
|
11207
11694
|
const timer = setTimeout(() => {
|
|
11208
11695
|
reject(new Error(`MCP [${this.serverId}] ${label} timed out after ${ms}ms`));
|
|
11209
11696
|
}, ms);
|
|
11210
11697
|
promise.then((val) => {
|
|
11211
11698
|
clearTimeout(timer);
|
|
11212
|
-
|
|
11699
|
+
resolve10(val);
|
|
11213
11700
|
}).catch((err) => {
|
|
11214
11701
|
clearTimeout(timer);
|
|
11215
11702
|
reject(err);
|
|
@@ -11486,11 +11973,11 @@ var McpManager = class {
|
|
|
11486
11973
|
};
|
|
11487
11974
|
|
|
11488
11975
|
// src/skills/manager.ts
|
|
11489
|
-
import { existsSync as
|
|
11490
|
-
import { join as
|
|
11976
|
+
import { existsSync as existsSync19, readdirSync as readdirSync9, mkdirSync as mkdirSync9, statSync as statSync7 } from "fs";
|
|
11977
|
+
import { join as join13 } from "path";
|
|
11491
11978
|
|
|
11492
11979
|
// src/skills/types.ts
|
|
11493
|
-
import { readFileSync as
|
|
11980
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
11494
11981
|
import { basename as basename5 } from "path";
|
|
11495
11982
|
function parseSimpleYaml(yaml) {
|
|
11496
11983
|
const result = {};
|
|
@@ -11512,7 +11999,7 @@ function parseYamlArray(value) {
|
|
|
11512
11999
|
function parseSkillFile(filePath) {
|
|
11513
12000
|
let raw;
|
|
11514
12001
|
try {
|
|
11515
|
-
raw =
|
|
12002
|
+
raw = readFileSync14(filePath, "utf-8");
|
|
11516
12003
|
} catch {
|
|
11517
12004
|
return null;
|
|
11518
12005
|
}
|
|
@@ -11555,7 +12042,7 @@ var SkillManager = class {
|
|
|
11555
12042
|
/** 发现并加载 skillsDir 下所有 .md 文件,返回加载数量 */
|
|
11556
12043
|
loadSkills() {
|
|
11557
12044
|
this.skills.clear();
|
|
11558
|
-
if (!
|
|
12045
|
+
if (!existsSync19(this.skillsDir)) {
|
|
11559
12046
|
try {
|
|
11560
12047
|
mkdirSync9(this.skillsDir, { recursive: true });
|
|
11561
12048
|
} catch {
|
|
@@ -11564,20 +12051,20 @@ var SkillManager = class {
|
|
|
11564
12051
|
}
|
|
11565
12052
|
let entries;
|
|
11566
12053
|
try {
|
|
11567
|
-
entries =
|
|
12054
|
+
entries = readdirSync9(this.skillsDir);
|
|
11568
12055
|
} catch {
|
|
11569
12056
|
return 0;
|
|
11570
12057
|
}
|
|
11571
12058
|
for (const entry of entries) {
|
|
11572
12059
|
let filePath;
|
|
11573
|
-
const fullPath =
|
|
12060
|
+
const fullPath = join13(this.skillsDir, entry);
|
|
11574
12061
|
if (entry.endsWith(".md")) {
|
|
11575
12062
|
filePath = fullPath;
|
|
11576
12063
|
} else {
|
|
11577
12064
|
try {
|
|
11578
12065
|
if (statSync7(fullPath).isDirectory()) {
|
|
11579
|
-
const skillMd =
|
|
11580
|
-
if (
|
|
12066
|
+
const skillMd = join13(fullPath, "SKILL.md");
|
|
12067
|
+
if (existsSync19(skillMd)) {
|
|
11581
12068
|
filePath = skillMd;
|
|
11582
12069
|
} else {
|
|
11583
12070
|
continue;
|
|
@@ -11642,7 +12129,7 @@ var SkillManager = class {
|
|
|
11642
12129
|
// src/web/tool-executor-web.ts
|
|
11643
12130
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
11644
12131
|
import { tmpdir as tmpdir2 } from "os";
|
|
11645
|
-
import { existsSync as
|
|
12132
|
+
import { existsSync as existsSync20, readFileSync as readFileSync15 } from "fs";
|
|
11646
12133
|
var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
11647
12134
|
constructor(registry, ws) {
|
|
11648
12135
|
this.registry = registry;
|
|
@@ -11671,6 +12158,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11671
12158
|
confirming = false;
|
|
11672
12159
|
/** Session-level auto-approve toggle (/yolo command) */
|
|
11673
12160
|
sessionAutoApprove = false;
|
|
12161
|
+
/** Session-level Auto Mode: rule-based low-risk auto approval. */
|
|
12162
|
+
sessionAutoMode = false;
|
|
11674
12163
|
/** Track tool start times for duration calculation */
|
|
11675
12164
|
toolStartTimes = /* @__PURE__ */ new Map();
|
|
11676
12165
|
/**
|
|
@@ -11710,33 +12199,33 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11710
12199
|
}
|
|
11711
12200
|
/** Resolve a pending confirm from client response */
|
|
11712
12201
|
resolveConfirm(requestId, approved) {
|
|
11713
|
-
const
|
|
11714
|
-
if (
|
|
12202
|
+
const resolve10 = this.pendingConfirms.get(requestId);
|
|
12203
|
+
if (resolve10) {
|
|
11715
12204
|
this.clearPendingTimer(requestId);
|
|
11716
12205
|
this.pendingConfirms.delete(requestId);
|
|
11717
12206
|
this.confirming = false;
|
|
11718
|
-
|
|
12207
|
+
resolve10(approved);
|
|
11719
12208
|
}
|
|
11720
12209
|
}
|
|
11721
12210
|
/** Resolve a pending batch confirm from client response */
|
|
11722
12211
|
resolveBatchConfirm(requestId, decision) {
|
|
11723
|
-
const
|
|
11724
|
-
if (
|
|
12212
|
+
const resolve10 = this.pendingBatchConfirms.get(requestId);
|
|
12213
|
+
if (resolve10) {
|
|
11725
12214
|
this.clearPendingTimer(requestId);
|
|
11726
12215
|
this.pendingBatchConfirms.delete(requestId);
|
|
11727
12216
|
this.confirming = false;
|
|
11728
12217
|
if (decision === "all" || decision === "none") {
|
|
11729
|
-
|
|
12218
|
+
resolve10(decision);
|
|
11730
12219
|
} else {
|
|
11731
|
-
|
|
12220
|
+
resolve10(new Set(decision));
|
|
11732
12221
|
}
|
|
11733
12222
|
}
|
|
11734
12223
|
}
|
|
11735
12224
|
/** Cancel all pending confirms (e.g., on disconnect) */
|
|
11736
12225
|
cancelAll() {
|
|
11737
|
-
for (const
|
|
12226
|
+
for (const resolve10 of this.pendingConfirms.values()) resolve10(false);
|
|
11738
12227
|
this.pendingConfirms.clear();
|
|
11739
|
-
for (const
|
|
12228
|
+
for (const resolve10 of this.pendingBatchConfirms.values()) resolve10("none");
|
|
11740
12229
|
this.pendingBatchConfirms.clear();
|
|
11741
12230
|
this.confirming = false;
|
|
11742
12231
|
}
|
|
@@ -11779,9 +12268,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11779
12268
|
if (call.name === "write_file") {
|
|
11780
12269
|
const filePath = String(call.arguments["path"] ?? "");
|
|
11781
12270
|
const newContent = String(call.arguments["content"] ?? "");
|
|
11782
|
-
if (filePath &&
|
|
12271
|
+
if (filePath && existsSync20(filePath)) {
|
|
11783
12272
|
try {
|
|
11784
|
-
const old =
|
|
12273
|
+
const old = readFileSync15(filePath, "utf-8");
|
|
11785
12274
|
return renderDiff(old, newContent, { filePath });
|
|
11786
12275
|
} catch {
|
|
11787
12276
|
}
|
|
@@ -11812,8 +12301,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11812
12301
|
diff: this.getDiffPreview(call)
|
|
11813
12302
|
};
|
|
11814
12303
|
this.send(msg);
|
|
11815
|
-
return new Promise((
|
|
11816
|
-
this.pendingConfirms.set(requestId,
|
|
12304
|
+
return new Promise((resolve10) => {
|
|
12305
|
+
this.pendingConfirms.set(requestId, resolve10);
|
|
11817
12306
|
this.pendingTimers.set(requestId, setTimeout(() => {
|
|
11818
12307
|
if (this.pendingConfirms.has(requestId)) {
|
|
11819
12308
|
this.resolveConfirm(requestId, false);
|
|
@@ -11837,8 +12326,8 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11837
12326
|
files
|
|
11838
12327
|
};
|
|
11839
12328
|
this.send(msg);
|
|
11840
|
-
return new Promise((
|
|
11841
|
-
this.pendingBatchConfirms.set(requestId,
|
|
12329
|
+
return new Promise((resolve10) => {
|
|
12330
|
+
this.pendingBatchConfirms.set(requestId, resolve10);
|
|
11842
12331
|
this.pendingTimers.set(requestId, setTimeout(() => {
|
|
11843
12332
|
if (this.pendingBatchConfirms.has(requestId)) {
|
|
11844
12333
|
this.resolveBatchConfirm(requestId, "none");
|
|
@@ -11948,6 +12437,35 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
11948
12437
|
}
|
|
11949
12438
|
}
|
|
11950
12439
|
this.sendToolCallStart(call);
|
|
12440
|
+
if (this.sessionAutoMode && networkPermission?.action !== "confirm") {
|
|
12441
|
+
const classification = defaultActionClassifier.classify(call, dangerLevel, {
|
|
12442
|
+
workspaceRoot: this.workspaceRoot,
|
|
12443
|
+
tempDirs: [tmpdir2()]
|
|
12444
|
+
});
|
|
12445
|
+
if (classification.decision === "deny") {
|
|
12446
|
+
recordRecentlyDeniedAutoAction(call, classification);
|
|
12447
|
+
const rejectionMsg = `[Auto denied] ${classification.reason}. Do not retry without asking.`;
|
|
12448
|
+
this.sendToolCallResult(call, rejectionMsg, true);
|
|
12449
|
+
return { callId: call.id, content: rejectionMsg, isError: true };
|
|
12450
|
+
}
|
|
12451
|
+
if (classification.decision === "auto-approve") {
|
|
12452
|
+
this.send({ type: "info", message: `\u26A1 Auto-approved (/auto: ${classification.ruleId})` });
|
|
12453
|
+
try {
|
|
12454
|
+
const rawContent = await runTool(tool, call.arguments, call.name);
|
|
12455
|
+
const content = truncateOutput(rawContent, call.name);
|
|
12456
|
+
this.sendToolCallResult(call, rawContent, false);
|
|
12457
|
+
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
12458
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "ok" }, { configDir: this.hookConfigDir });
|
|
12459
|
+
return { callId: call.id, content, isError: false };
|
|
12460
|
+
} catch (err) {
|
|
12461
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
12462
|
+
this.sendToolCallResult(call, message, true);
|
|
12463
|
+
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
12464
|
+
runLifecycleHooks(this.hookConfig, "PostToolUse", { tool: call.name, status: "error", error: message }, { configDir: this.hookConfigDir });
|
|
12465
|
+
return { callId: call.id, content: message, isError: true };
|
|
12466
|
+
}
|
|
12467
|
+
}
|
|
12468
|
+
}
|
|
11951
12469
|
if (this.sessionAutoApprove && dangerLevel === "write") {
|
|
11952
12470
|
try {
|
|
11953
12471
|
const rawContent = await runTool(tool, call.arguments, call.name);
|
|
@@ -12055,20 +12573,20 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
|
12055
12573
|
};
|
|
12056
12574
|
|
|
12057
12575
|
// src/core/system-prompt-builder.ts
|
|
12058
|
-
import { existsSync as
|
|
12059
|
-
import { join as
|
|
12576
|
+
import { existsSync as existsSync22, readFileSync as readFileSync17 } from "fs";
|
|
12577
|
+
import { join as join15 } from "path";
|
|
12060
12578
|
|
|
12061
12579
|
// src/repl/dev-state.ts
|
|
12062
|
-
import { existsSync as
|
|
12063
|
-
import { join as
|
|
12064
|
-
import { homedir as
|
|
12580
|
+
import { existsSync as existsSync21, readFileSync as readFileSync16, unlinkSync as unlinkSync4, mkdirSync as mkdirSync10 } from "fs";
|
|
12581
|
+
import { join as join14 } from "path";
|
|
12582
|
+
import { homedir as homedir7 } from "os";
|
|
12065
12583
|
function getDevStatePath() {
|
|
12066
|
-
return
|
|
12584
|
+
return join14(homedir7(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
|
|
12067
12585
|
}
|
|
12068
12586
|
function loadDevState() {
|
|
12069
12587
|
const path3 = getDevStatePath();
|
|
12070
|
-
if (!
|
|
12071
|
-
const content =
|
|
12588
|
+
if (!existsSync21(path3)) return null;
|
|
12589
|
+
const content = readFileSync16(path3, "utf-8").trim();
|
|
12072
12590
|
return content || null;
|
|
12073
12591
|
}
|
|
12074
12592
|
|
|
@@ -12126,9 +12644,9 @@ ${ctx.activeSkill.content}`);
|
|
|
12126
12644
|
return { stable: stableParts.join("\n\n---\n\n"), volatile };
|
|
12127
12645
|
}
|
|
12128
12646
|
function loadMemoryContent(configDir) {
|
|
12129
|
-
const memoryPath =
|
|
12130
|
-
if (!
|
|
12131
|
-
let content =
|
|
12647
|
+
const memoryPath = join15(configDir, MEMORY_FILE_NAME);
|
|
12648
|
+
if (!existsSync22(memoryPath)) return null;
|
|
12649
|
+
let content = readFileSync17(memoryPath, "utf-8").trim();
|
|
12132
12650
|
if (!content) return null;
|
|
12133
12651
|
if (content.length > MEMORY_MAX_CHARS) {
|
|
12134
12652
|
content = content.slice(-MEMORY_MAX_CHARS);
|
|
@@ -12337,8 +12855,8 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
|
|
|
12337
12855
|
}
|
|
12338
12856
|
|
|
12339
12857
|
// src/core/context-files.ts
|
|
12340
|
-
import { existsSync as
|
|
12341
|
-
import { join as
|
|
12858
|
+
import { existsSync as existsSync23, readFileSync as readFileSync18 } from "fs";
|
|
12859
|
+
import { join as join16, relative as relative3, resolve as resolve7 } from "path";
|
|
12342
12860
|
function uniqueNonEmpty(values) {
|
|
12343
12861
|
const seen = /* @__PURE__ */ new Set();
|
|
12344
12862
|
const out = [];
|
|
@@ -12361,14 +12879,14 @@ function displayPath(filePath, cwd) {
|
|
|
12361
12879
|
return filePath;
|
|
12362
12880
|
}
|
|
12363
12881
|
function isInsideOrEqual(parent, child) {
|
|
12364
|
-
const rel = relative3(
|
|
12882
|
+
const rel = relative3(resolve7(parent), resolve7(child));
|
|
12365
12883
|
return rel === "" || !!rel && !rel.startsWith("..") && !rel.startsWith("/") && !rel.startsWith("\\");
|
|
12366
12884
|
}
|
|
12367
12885
|
function readContextFile(level, filePath, cwd, maxBytes) {
|
|
12368
12886
|
const name = filePath.split(/[\\/]/).pop() ?? filePath;
|
|
12369
12887
|
const shown = displayPath(filePath, cwd);
|
|
12370
12888
|
try {
|
|
12371
|
-
const raw =
|
|
12889
|
+
const raw = readFileSync18(filePath);
|
|
12372
12890
|
const byteCount = raw.byteLength;
|
|
12373
12891
|
if (byteCount === 0) {
|
|
12374
12892
|
return {
|
|
@@ -12415,8 +12933,8 @@ function readContextFile(level, filePath, cwd, maxBytes) {
|
|
|
12415
12933
|
function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
12416
12934
|
const skipped = [];
|
|
12417
12935
|
for (const candidate of candidates) {
|
|
12418
|
-
const filePath =
|
|
12419
|
-
if (!
|
|
12936
|
+
const filePath = join16(dir, candidate);
|
|
12937
|
+
if (!existsSync23(filePath)) continue;
|
|
12420
12938
|
const result = readContextFile(level, filePath, cwd, maxBytes);
|
|
12421
12939
|
if (result.skipped) skipped.push(result.skipped);
|
|
12422
12940
|
if (result.layer) return { layer: result.layer, skipped };
|
|
@@ -12424,7 +12942,7 @@ function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
|
|
|
12424
12942
|
return { layer: null, skipped };
|
|
12425
12943
|
}
|
|
12426
12944
|
function loadContextFiles(options) {
|
|
12427
|
-
const cwd =
|
|
12945
|
+
const cwd = resolve7(options.cwd);
|
|
12428
12946
|
const maxBytes = options.maxBytes ?? CONTEXT_FILE_MAX_BYTES;
|
|
12429
12947
|
const candidates = options.candidates ?? buildContextFileCandidates(options.fallbackFilenames);
|
|
12430
12948
|
const skipped = [];
|
|
@@ -12432,7 +12950,7 @@ function loadContextFiles(options) {
|
|
|
12432
12950
|
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
12433
12951
|
}
|
|
12434
12952
|
if (options.setting && options.setting !== "auto") {
|
|
12435
|
-
const filePath =
|
|
12953
|
+
const filePath = resolve7(cwd, String(options.setting));
|
|
12436
12954
|
if (!isInsideOrEqual(cwd, filePath)) {
|
|
12437
12955
|
skipped.push({
|
|
12438
12956
|
level: "single",
|
|
@@ -12443,7 +12961,7 @@ function loadContextFiles(options) {
|
|
|
12443
12961
|
});
|
|
12444
12962
|
return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
|
|
12445
12963
|
}
|
|
12446
|
-
if (!
|
|
12964
|
+
if (!existsSync23(filePath)) {
|
|
12447
12965
|
skipped.push({
|
|
12448
12966
|
level: "single",
|
|
12449
12967
|
filePath,
|
|
@@ -12476,7 +12994,7 @@ function loadContextFiles(options) {
|
|
|
12476
12994
|
skipped.push(...result.skipped);
|
|
12477
12995
|
if (result.layer) layers.push(result.layer);
|
|
12478
12996
|
}
|
|
12479
|
-
if (
|
|
12997
|
+
if (resolve7(options.cwd) !== resolve7(options.projectRoot)) {
|
|
12480
12998
|
const result = findFirstContextFile("local", options.cwd, cwd, candidates, maxBytes);
|
|
12481
12999
|
skipped.push(...result.skipped);
|
|
12482
13000
|
if (result.layer) layers.push(result.layer);
|
|
@@ -12494,13 +13012,13 @@ function loadContextFiles(options) {
|
|
|
12494
13012
|
}
|
|
12495
13013
|
|
|
12496
13014
|
// src/web/session-handler.ts
|
|
12497
|
-
import { existsSync as
|
|
12498
|
-
import { join as
|
|
13015
|
+
import { existsSync as existsSync26, readFileSync as readFileSync20, writeFileSync as writeFileSync2, mkdirSync as mkdirSync11, readdirSync as readdirSync11, statSync as statSync9, createWriteStream } from "fs";
|
|
13016
|
+
import { join as join20, resolve as resolve8, dirname as dirname5 } from "path";
|
|
12499
13017
|
|
|
12500
13018
|
// src/tools/git-context.ts
|
|
12501
13019
|
import { execSync as execSync2 } from "child_process";
|
|
12502
|
-
import { existsSync as
|
|
12503
|
-
import { join as
|
|
13020
|
+
import { existsSync as existsSync24 } from "fs";
|
|
13021
|
+
import { join as join17 } from "path";
|
|
12504
13022
|
function runGit2(cmd, cwd) {
|
|
12505
13023
|
try {
|
|
12506
13024
|
return execSync2(`git ${cmd}`, {
|
|
@@ -12517,7 +13035,7 @@ function getGitRoot(cwd = process.cwd()) {
|
|
|
12517
13035
|
return runGit2("rev-parse --show-toplevel", cwd);
|
|
12518
13036
|
}
|
|
12519
13037
|
function getGitContext(cwd = process.cwd()) {
|
|
12520
|
-
if (!
|
|
13038
|
+
if (!existsSync24(join17(cwd, ".git"))) {
|
|
12521
13039
|
const result = runGit2("rev-parse --git-dir", cwd);
|
|
12522
13040
|
if (!result) return null;
|
|
12523
13041
|
}
|
|
@@ -12646,8 +13164,8 @@ If no security issues found, state "\u2705 No security vulnerabilities detected"
|
|
|
12646
13164
|
}
|
|
12647
13165
|
|
|
12648
13166
|
// src/repl/commands/project-init.ts
|
|
12649
|
-
import { existsSync as
|
|
12650
|
-
import { join as
|
|
13167
|
+
import { existsSync as existsSync25, readFileSync as readFileSync19, readdirSync as readdirSync10, statSync as statSync8 } from "fs";
|
|
13168
|
+
import { join as join18 } from "path";
|
|
12651
13169
|
var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
12652
13170
|
"node_modules",
|
|
12653
13171
|
".git",
|
|
@@ -12683,7 +13201,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
12683
13201
|
if (depth > maxDepth || count >= maxEntries) return;
|
|
12684
13202
|
let entries;
|
|
12685
13203
|
try {
|
|
12686
|
-
entries =
|
|
13204
|
+
entries = readdirSync10(d);
|
|
12687
13205
|
} catch {
|
|
12688
13206
|
return;
|
|
12689
13207
|
}
|
|
@@ -12691,11 +13209,11 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
12691
13209
|
const sorted = filtered.sort((a, b) => {
|
|
12692
13210
|
let aIsDir = false, bIsDir = false;
|
|
12693
13211
|
try {
|
|
12694
|
-
aIsDir = statSync8(
|
|
13212
|
+
aIsDir = statSync8(join18(d, a)).isDirectory();
|
|
12695
13213
|
} catch {
|
|
12696
13214
|
}
|
|
12697
13215
|
try {
|
|
12698
|
-
bIsDir = statSync8(
|
|
13216
|
+
bIsDir = statSync8(join18(d, b)).isDirectory();
|
|
12699
13217
|
} catch {
|
|
12700
13218
|
}
|
|
12701
13219
|
if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
|
|
@@ -12703,7 +13221,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
|
|
|
12703
13221
|
});
|
|
12704
13222
|
for (let i = 0; i < sorted.length && count < maxEntries; i++) {
|
|
12705
13223
|
const name = sorted[i];
|
|
12706
|
-
const fullPath =
|
|
13224
|
+
const fullPath = join18(d, name);
|
|
12707
13225
|
const isLast = i === sorted.length - 1;
|
|
12708
13226
|
const connector = isLast ? "+-- " : "|-- ";
|
|
12709
13227
|
let isDir;
|
|
@@ -12730,7 +13248,7 @@ function scanProject(cwd) {
|
|
|
12730
13248
|
configFiles: [],
|
|
12731
13249
|
directoryStructure: ""
|
|
12732
13250
|
};
|
|
12733
|
-
const check = (file) =>
|
|
13251
|
+
const check = (file) => existsSync25(join18(cwd, file));
|
|
12734
13252
|
const configCandidates = [
|
|
12735
13253
|
"package.json",
|
|
12736
13254
|
"tsconfig.json",
|
|
@@ -12757,7 +13275,7 @@ function scanProject(cwd) {
|
|
|
12757
13275
|
info.type = "node";
|
|
12758
13276
|
info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
|
|
12759
13277
|
try {
|
|
12760
|
-
const pkg = JSON.parse(
|
|
13278
|
+
const pkg = JSON.parse(readFileSync19(join18(cwd, "package.json"), "utf-8"));
|
|
12761
13279
|
const scripts = pkg.scripts ?? {};
|
|
12762
13280
|
info.buildCommand = scripts.build ? "npm run build" : void 0;
|
|
12763
13281
|
info.testCommand = scripts.test ? "npm test" : void 0;
|
|
@@ -13417,7 +13935,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
|
|
|
13417
13935
|
}
|
|
13418
13936
|
|
|
13419
13937
|
// src/hub/persist.ts
|
|
13420
|
-
import { join as
|
|
13938
|
+
import { join as join19 } from "path";
|
|
13421
13939
|
function discussionToMessages(state2) {
|
|
13422
13940
|
const out = [];
|
|
13423
13941
|
const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
|
|
@@ -13455,7 +13973,7 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
|
|
|
13455
13973
|
session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
|
|
13456
13974
|
session.titleAiGenerated = true;
|
|
13457
13975
|
await sm.save();
|
|
13458
|
-
return { id: session.id, path:
|
|
13976
|
+
return { id: session.id, path: join19(config.getHistoryDir(), `${session.id}.json`) };
|
|
13459
13977
|
}
|
|
13460
13978
|
|
|
13461
13979
|
// src/web/session-handler.ts
|
|
@@ -13615,7 +14133,19 @@ var SessionHandler = class {
|
|
|
13615
14133
|
costUsd,
|
|
13616
14134
|
providers: providerList,
|
|
13617
14135
|
branches,
|
|
13618
|
-
activeBranchId: sess?.activeBranchId ?? "main"
|
|
14136
|
+
activeBranchId: sess?.activeBranchId ?? "main",
|
|
14137
|
+
agents: listAgentRuns().slice(0, 20).map((run) => ({
|
|
14138
|
+
id: run.id,
|
|
14139
|
+
agentName: run.agentName,
|
|
14140
|
+
task: run.task,
|
|
14141
|
+
status: run.status,
|
|
14142
|
+
startedAt: run.startedAt,
|
|
14143
|
+
finishedAt: run.finishedAt,
|
|
14144
|
+
summary: run.summary,
|
|
14145
|
+
error: run.error,
|
|
14146
|
+
toolNames: run.toolNames,
|
|
14147
|
+
agentIndex: run.agentIndex
|
|
14148
|
+
}))
|
|
13619
14149
|
});
|
|
13620
14150
|
}
|
|
13621
14151
|
async handleMessage(raw) {
|
|
@@ -13639,10 +14169,10 @@ var SessionHandler = class {
|
|
|
13639
14169
|
return;
|
|
13640
14170
|
}
|
|
13641
14171
|
case "ask_user_response": {
|
|
13642
|
-
const
|
|
13643
|
-
if (
|
|
14172
|
+
const resolve10 = this.pendingAskUser.get(msg.requestId);
|
|
14173
|
+
if (resolve10) {
|
|
13644
14174
|
this.pendingAskUser.delete(msg.requestId);
|
|
13645
|
-
|
|
14175
|
+
resolve10(msg.answer);
|
|
13646
14176
|
}
|
|
13647
14177
|
return;
|
|
13648
14178
|
}
|
|
@@ -13653,10 +14183,10 @@ var SessionHandler = class {
|
|
|
13653
14183
|
case "memory_rebuild":
|
|
13654
14184
|
return this.handleMemoryRebuild(Boolean(msg.full));
|
|
13655
14185
|
case "auto_pause_response": {
|
|
13656
|
-
const
|
|
13657
|
-
if (
|
|
14186
|
+
const resolve10 = this.pendingAutoPause.get(msg.requestId);
|
|
14187
|
+
if (resolve10) {
|
|
13658
14188
|
this.pendingAutoPause.delete(msg.requestId);
|
|
13659
|
-
|
|
14189
|
+
resolve10({ action: msg.action, message: msg.message });
|
|
13660
14190
|
}
|
|
13661
14191
|
return;
|
|
13662
14192
|
}
|
|
@@ -13671,10 +14201,10 @@ var SessionHandler = class {
|
|
|
13671
14201
|
this.hubOrchestrator?.abort();
|
|
13672
14202
|
return;
|
|
13673
14203
|
case "hub_steer": {
|
|
13674
|
-
const
|
|
13675
|
-
if (
|
|
14204
|
+
const resolve10 = this.pendingHubReview.get(msg.requestId);
|
|
14205
|
+
if (resolve10) {
|
|
13676
14206
|
this.pendingHubReview.delete(msg.requestId);
|
|
13677
|
-
|
|
14207
|
+
resolve10({ action: msg.action, message: msg.message });
|
|
13678
14208
|
}
|
|
13679
14209
|
return;
|
|
13680
14210
|
}
|
|
@@ -13687,9 +14217,9 @@ var SessionHandler = class {
|
|
|
13687
14217
|
runLifecycleHooks(this.config.get("hooks") ?? void 0, "Stop", { sessionId: this.sessions.current?.id }, { configDir: this.config.getConfigDir() });
|
|
13688
14218
|
this.toolExecutor.cancelAll();
|
|
13689
14219
|
if (this.abortController) this.abortController.abort();
|
|
13690
|
-
for (const
|
|
14220
|
+
for (const resolve10 of this.pendingAskUser.values()) resolve10(null);
|
|
13691
14221
|
this.pendingAskUser.clear();
|
|
13692
|
-
for (const
|
|
14222
|
+
for (const resolve10 of this.pendingAutoPause.values()) resolve10({ action: "stop" });
|
|
13693
14223
|
this.pendingAutoPause.clear();
|
|
13694
14224
|
this.saveIfNeeded();
|
|
13695
14225
|
}
|
|
@@ -13823,9 +14353,9 @@ var SessionHandler = class {
|
|
|
13823
14353
|
this.hubOrchestrator = orchestrator;
|
|
13824
14354
|
orchestrator.onEvent = (event) => this.send({ type: "hub_event", event });
|
|
13825
14355
|
if (config.humanSteer) {
|
|
13826
|
-
orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((
|
|
14356
|
+
orchestrator.onRoundReview = ({ round, maxRounds }) => new Promise((resolve10) => {
|
|
13827
14357
|
const requestId = `hubrev_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
13828
|
-
this.pendingHubReview.set(requestId,
|
|
14358
|
+
this.pendingHubReview.set(requestId, resolve10);
|
|
13829
14359
|
this.send({ type: "hub_review", requestId, round, maxRounds });
|
|
13830
14360
|
});
|
|
13831
14361
|
}
|
|
@@ -14092,6 +14622,7 @@ Details: ${errMsg.split("\n")[0]}
|
|
|
14092
14622
|
${systemPromptVolatile}` : systemPrompt;
|
|
14093
14623
|
spawnAgentContext.modelParams = modelParams;
|
|
14094
14624
|
spawnAgentContext.configManager = this.config;
|
|
14625
|
+
spawnAgentContext.providers = this.providers;
|
|
14095
14626
|
ToolExecutor.currentMessageIndex = this.sessions.current?.messages.length ?? 0;
|
|
14096
14627
|
return this.toolExecutor.executeAll(toolCalls);
|
|
14097
14628
|
},
|
|
@@ -14182,8 +14713,8 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
|
|
|
14182
14713
|
onMcpToolUsed: (name) => this.usedMcpToolNames.add(name),
|
|
14183
14714
|
requestAutoPause: async ({ effectiveRound, maxToolRounds: totalRounds, toolSummary }) => {
|
|
14184
14715
|
const requestId = `pause_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
14185
|
-
const pauseResp = await new Promise((
|
|
14186
|
-
this.pendingAutoPause.set(requestId,
|
|
14716
|
+
const pauseResp = await new Promise((resolve10) => {
|
|
14717
|
+
this.pendingAutoPause.set(requestId, resolve10);
|
|
14187
14718
|
this.send({
|
|
14188
14719
|
type: "auto_pause_request",
|
|
14189
14720
|
requestId,
|
|
@@ -14362,8 +14893,8 @@ ${summaryContent}`,
|
|
|
14362
14893
|
}
|
|
14363
14894
|
if (chunk.done) break;
|
|
14364
14895
|
}
|
|
14365
|
-
await new Promise((
|
|
14366
|
-
fileStream.end((err) => err ? reject(err) :
|
|
14896
|
+
await new Promise((resolve10, reject) => {
|
|
14897
|
+
fileStream.end((err) => err ? reject(err) : resolve10());
|
|
14367
14898
|
});
|
|
14368
14899
|
const verdict = evaluateTeeContent(fullContent, saveToFile, priorContent);
|
|
14369
14900
|
if (verdict.kind === "reject") {
|
|
@@ -14389,7 +14920,7 @@ ${summaryContent}`,
|
|
|
14389
14920
|
} catch (err) {
|
|
14390
14921
|
if (fileStream) {
|
|
14391
14922
|
try {
|
|
14392
|
-
await new Promise((
|
|
14923
|
+
await new Promise((resolve10) => fileStream.end(() => resolve10()));
|
|
14393
14924
|
} catch {
|
|
14394
14925
|
}
|
|
14395
14926
|
}
|
|
@@ -14686,6 +15217,7 @@ Tokens: in=${this.sessionTokenUsage.inputTokens} out=${this.sessionTokenUsage.ou
|
|
|
14686
15217
|
" /export [md|json] \u2014 Export conversation",
|
|
14687
15218
|
" /skill [name|off|list|reload] \u2014 Manage agent skills",
|
|
14688
15219
|
" /memory [show|add|clear] \u2014 Persistent memory management",
|
|
15220
|
+
" /agent list|switch|stop|summary \u2014 Manage named sub-agents",
|
|
14689
15221
|
" /yolo [on|off] \u2014 Toggle auto-approve (skip confirmations)",
|
|
14690
15222
|
" /search <keyword> \u2014 Search across all session histories",
|
|
14691
15223
|
" /undo [list|<n>] \u2014 Undo file operations",
|
|
@@ -14818,7 +15350,108 @@ ${activated.meta.description || ""}` });
|
|
|
14818
15350
|
}
|
|
14819
15351
|
break;
|
|
14820
15352
|
}
|
|
15353
|
+
case "agent": {
|
|
15354
|
+
const sub = args[0]?.toLowerCase() ?? "summary";
|
|
15355
|
+
if (sub === "list") {
|
|
15356
|
+
const agents = listAgentConfigs(this.config.getConfigDir());
|
|
15357
|
+
const lines = [`Agents (${agents.length})`, ""];
|
|
15358
|
+
for (const agent of agents) {
|
|
15359
|
+
const active = agent.name === getPreferredAgentName() ? " *" : " ";
|
|
15360
|
+
const model = [agent.provider, agent.model].filter(Boolean).join("/") || "inherit";
|
|
15361
|
+
lines.push(`${active} ${agent.name} [${agent.source ?? "builtin"}] \u2014 ${agent.description ?? ""}`);
|
|
15362
|
+
lines.push(` model: ${model} \xB7 permission: ${agent.permissionProfile ?? "workspace-write"} \xB7 maxRounds: ${agent.maxToolRounds ?? "inherit"}`);
|
|
15363
|
+
}
|
|
15364
|
+
lines.push("", "Config dirs: ~/.aicli/agents/ and .aicli/agents/");
|
|
15365
|
+
this.send({ type: "info", message: lines.join("\n") });
|
|
15366
|
+
break;
|
|
15367
|
+
}
|
|
15368
|
+
if (sub === "switch") {
|
|
15369
|
+
const name2 = args[1];
|
|
15370
|
+
if (!name2) {
|
|
15371
|
+
this.send({ type: "error", message: "Usage: /agent switch <name>" });
|
|
15372
|
+
break;
|
|
15373
|
+
}
|
|
15374
|
+
const agent = resolveAgentConfig(name2, this.config.getConfigDir());
|
|
15375
|
+
if (!agent) {
|
|
15376
|
+
const available = listAgentConfigs(this.config.getConfigDir()).map((a) => a.name).join(", ");
|
|
15377
|
+
this.send({ type: "error", message: `Unknown agent: ${name2}
|
|
15378
|
+
Available: ${available}` });
|
|
15379
|
+
break;
|
|
15380
|
+
}
|
|
15381
|
+
setPreferredAgentName(agent.name);
|
|
15382
|
+
this.send({ type: "info", message: `Preferred spawn_agent role: ${agent.name}` });
|
|
15383
|
+
this.sendStatus();
|
|
15384
|
+
break;
|
|
15385
|
+
}
|
|
15386
|
+
if (sub === "stop") {
|
|
15387
|
+
const target = args[1] ?? "all";
|
|
15388
|
+
const count = requestAgentStop(target);
|
|
15389
|
+
this.send({ type: "info", message: count > 0 ? `Stop requested for ${count} running agent(s).` : "No matching running agents." });
|
|
15390
|
+
this.sendStatus();
|
|
15391
|
+
break;
|
|
15392
|
+
}
|
|
15393
|
+
if (sub === "summary") {
|
|
15394
|
+
const runs2 = listAgentRuns();
|
|
15395
|
+
if (runs2.length === 0) {
|
|
15396
|
+
this.send({ type: "info", message: "No sub-agent runs yet." });
|
|
15397
|
+
break;
|
|
15398
|
+
}
|
|
15399
|
+
const lines = [`Recent agent runs (${runs2.length})`, ""];
|
|
15400
|
+
for (const run of runs2.slice(0, 20)) {
|
|
15401
|
+
lines.push(`${run.id} ${run.status} ${run.agentName} ${run.startedAt}`);
|
|
15402
|
+
lines.push(` task: ${run.task.slice(0, 120)}${run.task.length > 120 ? "..." : ""}`);
|
|
15403
|
+
if (run.toolNames.length > 0) lines.push(` tools: ${run.toolNames.join(", ")}`);
|
|
15404
|
+
if (run.summary) lines.push(` summary: ${run.summary.slice(0, 180)}${run.summary.length > 180 ? "..." : ""}`);
|
|
15405
|
+
if (run.error) lines.push(` error: ${run.error}`);
|
|
15406
|
+
}
|
|
15407
|
+
this.send({ type: "info", message: lines.join("\n") });
|
|
15408
|
+
break;
|
|
15409
|
+
}
|
|
15410
|
+
this.send({ type: "error", message: "Usage: /agent list|switch <name>|stop <id|all>|summary" });
|
|
15411
|
+
break;
|
|
15412
|
+
}
|
|
14821
15413
|
// ── /yolo ──────────────────────────────────────────────────────
|
|
15414
|
+
case "auto": {
|
|
15415
|
+
const sub = args[0]?.toLowerCase() ?? "status";
|
|
15416
|
+
if (sub === "on") {
|
|
15417
|
+
this.toolExecutor.sessionAutoMode = true;
|
|
15418
|
+
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." });
|
|
15419
|
+
} else if (sub === "off") {
|
|
15420
|
+
this.toolExecutor.sessionAutoMode = false;
|
|
15421
|
+
this.send({ type: "info", message: "Auto Mode disabled." });
|
|
15422
|
+
} else if (sub === "status") {
|
|
15423
|
+
this.send({ type: "info", message: `Auto Mode: ${this.toolExecutor.sessionAutoMode ? "on" : "off"}
|
|
15424
|
+
Scope: current session only
|
|
15425
|
+
Denied: /permissions recently-denied` });
|
|
15426
|
+
} else {
|
|
15427
|
+
this.send({ type: "error", message: "Usage: /auto [on|off|status]" });
|
|
15428
|
+
}
|
|
15429
|
+
this.sendStatus();
|
|
15430
|
+
break;
|
|
15431
|
+
}
|
|
15432
|
+
case "permissions": {
|
|
15433
|
+
const sub = args[0]?.toLowerCase() ?? "recently-denied";
|
|
15434
|
+
if (sub === "recently-denied") {
|
|
15435
|
+
const rows = getRecentlyDeniedAutoActions();
|
|
15436
|
+
if (rows.length === 0) {
|
|
15437
|
+
this.send({ type: "info", message: "No recently denied Auto Mode actions." });
|
|
15438
|
+
} else {
|
|
15439
|
+
const lines = [`Recently denied Auto Mode actions (${rows.length})`, ""];
|
|
15440
|
+
for (const r of rows.slice(0, 20)) {
|
|
15441
|
+
lines.push(`- ${r.timestamp} ${r.tool} ${r.ruleId}`);
|
|
15442
|
+
lines.push(` ${r.reason}`);
|
|
15443
|
+
lines.push(` ${r.argsPreview}`);
|
|
15444
|
+
}
|
|
15445
|
+
this.send({ type: "info", message: lines.join("\n") });
|
|
15446
|
+
}
|
|
15447
|
+
} else if (sub === "clear-denied") {
|
|
15448
|
+
clearRecentlyDeniedAutoActions();
|
|
15449
|
+
this.send({ type: "info", message: "Cleared recently denied Auto Mode actions." });
|
|
15450
|
+
} else {
|
|
15451
|
+
this.send({ type: "error", message: "Usage: /permissions recently-denied|clear-denied" });
|
|
15452
|
+
}
|
|
15453
|
+
break;
|
|
15454
|
+
}
|
|
14822
15455
|
case "yolo": {
|
|
14823
15456
|
const sub = args[0]?.toLowerCase();
|
|
14824
15457
|
if (sub === "off") {
|
|
@@ -14919,9 +15552,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
14919
15552
|
let modifiedFiles = 0;
|
|
14920
15553
|
const diffLines2 = [];
|
|
14921
15554
|
for (const [filePath, { earliest }] of fileMap) {
|
|
14922
|
-
const currentContent =
|
|
15555
|
+
const currentContent = existsSync26(filePath) ? (() => {
|
|
14923
15556
|
try {
|
|
14924
|
-
return
|
|
15557
|
+
return readFileSync20(filePath, "utf-8");
|
|
14925
15558
|
} catch {
|
|
14926
15559
|
return null;
|
|
14927
15560
|
}
|
|
@@ -15022,7 +15655,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15022
15655
|
break;
|
|
15023
15656
|
}
|
|
15024
15657
|
const sub = args[0]?.toLowerCase();
|
|
15025
|
-
const
|
|
15658
|
+
const resolve10 = (ref) => {
|
|
15026
15659
|
const r = session.resolveBranchRef(ref);
|
|
15027
15660
|
if (r.ok) return r.id;
|
|
15028
15661
|
if (r.reason === "ambiguous") {
|
|
@@ -15076,7 +15709,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15076
15709
|
this.send({ type: "error", message: "Usage: /branch switch <id|title>" });
|
|
15077
15710
|
break;
|
|
15078
15711
|
}
|
|
15079
|
-
const id =
|
|
15712
|
+
const id = resolve10(ref);
|
|
15080
15713
|
if (!id) break;
|
|
15081
15714
|
const ok = session.switchBranch(id);
|
|
15082
15715
|
if (ok) {
|
|
@@ -15096,7 +15729,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15096
15729
|
this.send({ type: "error", message: "Usage: /branch delete <id|title>" });
|
|
15097
15730
|
break;
|
|
15098
15731
|
}
|
|
15099
|
-
const id =
|
|
15732
|
+
const id = resolve10(ref);
|
|
15100
15733
|
if (!id) break;
|
|
15101
15734
|
const ok = session.deleteBranch(id);
|
|
15102
15735
|
if (ok) {
|
|
@@ -15115,7 +15748,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15115
15748
|
this.send({ type: "error", message: "Usage: /branch rename <id|title> <new title>" });
|
|
15116
15749
|
break;
|
|
15117
15750
|
}
|
|
15118
|
-
const id =
|
|
15751
|
+
const id = resolve10(ref);
|
|
15119
15752
|
if (!id) break;
|
|
15120
15753
|
const ok = session.renameBranch(id, title);
|
|
15121
15754
|
if (ok) {
|
|
@@ -15133,7 +15766,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15133
15766
|
this.send({ type: "error", message: "Usage: /branch diff <id|title>" });
|
|
15134
15767
|
break;
|
|
15135
15768
|
}
|
|
15136
|
-
const id =
|
|
15769
|
+
const id = resolve10(ref);
|
|
15137
15770
|
if (!id) break;
|
|
15138
15771
|
const d = session.diffBranches(id);
|
|
15139
15772
|
if (!d) {
|
|
@@ -15175,7 +15808,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15175
15808
|
this.send({ type: "error", message: "Usage: /branch cherry-pick <source-id|title> <msg-index>" });
|
|
15176
15809
|
break;
|
|
15177
15810
|
}
|
|
15178
|
-
const id =
|
|
15811
|
+
const id = resolve10(ref);
|
|
15179
15812
|
if (!id) break;
|
|
15180
15813
|
const idx = parseInt(idxStr, 10);
|
|
15181
15814
|
if (Number.isNaN(idx)) {
|
|
@@ -15437,7 +16070,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15437
16070
|
case "test": {
|
|
15438
16071
|
this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
|
|
15439
16072
|
try {
|
|
15440
|
-
const { executeTests } = await import("./run-tests-
|
|
16073
|
+
const { executeTests } = await import("./run-tests-R6FL6CQ3.js");
|
|
15441
16074
|
const argStr = args.join(" ").trim();
|
|
15442
16075
|
let testArgs = {};
|
|
15443
16076
|
if (argStr) {
|
|
@@ -15454,9 +16087,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
15454
16087
|
// ── /init ───────────────────────────────────────────────────────
|
|
15455
16088
|
case "init": {
|
|
15456
16089
|
const cwd = process.cwd();
|
|
15457
|
-
const targetPath =
|
|
16090
|
+
const targetPath = join20(cwd, "AICLI.md");
|
|
15458
16091
|
const force = args.includes("--force");
|
|
15459
|
-
if (
|
|
16092
|
+
if (existsSync26(targetPath) && !force) {
|
|
15460
16093
|
this.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
|
|
15461
16094
|
Use /init --force to overwrite.` });
|
|
15462
16095
|
break;
|
|
@@ -15489,7 +16122,7 @@ Use /context reload to load it.` });
|
|
|
15489
16122
|
lines.push("**Config Files:**");
|
|
15490
16123
|
lines.push(` Dir: ${configDir}`);
|
|
15491
16124
|
const checkFile = (label, filePath) => {
|
|
15492
|
-
const exists =
|
|
16125
|
+
const exists = existsSync26(filePath);
|
|
15493
16126
|
let extra = "";
|
|
15494
16127
|
if (exists) {
|
|
15495
16128
|
try {
|
|
@@ -15499,9 +16132,9 @@ Use /context reload to load it.` });
|
|
|
15499
16132
|
}
|
|
15500
16133
|
lines.push(` ${exists ? "\u2713" : "\u2013"} ${label.padEnd(14)} ${exists ? filePath + extra : "(not found)"}`);
|
|
15501
16134
|
};
|
|
15502
|
-
checkFile("config.json",
|
|
15503
|
-
checkFile("memory.md",
|
|
15504
|
-
checkFile("dev-state.md",
|
|
16135
|
+
checkFile("config.json", join20(configDir, "config.json"));
|
|
16136
|
+
checkFile("memory.md", join20(configDir, MEMORY_FILE_NAME));
|
|
16137
|
+
checkFile("dev-state.md", join20(configDir, "dev-state.md"));
|
|
15505
16138
|
lines.push("");
|
|
15506
16139
|
if (this.mcpManager) {
|
|
15507
16140
|
lines.push("**MCP Servers:**");
|
|
@@ -15688,8 +16321,8 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
|
|
|
15688
16321
|
this.send({ type: "info", message: "\u2713 All added directories removed from context." });
|
|
15689
16322
|
break;
|
|
15690
16323
|
}
|
|
15691
|
-
const dirPath =
|
|
15692
|
-
if (!
|
|
16324
|
+
const dirPath = resolve8(sub);
|
|
16325
|
+
if (!existsSync26(dirPath)) {
|
|
15693
16326
|
this.send({ type: "error", message: `Directory not found: ${dirPath}` });
|
|
15694
16327
|
break;
|
|
15695
16328
|
}
|
|
@@ -15705,14 +16338,14 @@ It will be included in AI context for subsequent messages.` });
|
|
|
15705
16338
|
// ── /commands ───────────────────────────────────────────────────
|
|
15706
16339
|
case "commands": {
|
|
15707
16340
|
const configDir = this.config.getConfigDir();
|
|
15708
|
-
const commandsDir =
|
|
15709
|
-
if (!
|
|
16341
|
+
const commandsDir = join20(configDir, CUSTOM_COMMANDS_DIR_NAME);
|
|
16342
|
+
if (!existsSync26(commandsDir)) {
|
|
15710
16343
|
this.send({ type: "info", message: `No custom commands directory.
|
|
15711
16344
|
Create: ${commandsDir}/ with .md files.` });
|
|
15712
16345
|
break;
|
|
15713
16346
|
}
|
|
15714
16347
|
try {
|
|
15715
|
-
const files =
|
|
16348
|
+
const files = readdirSync11(commandsDir).filter((f) => f.endsWith(".md"));
|
|
15716
16349
|
if (files.length === 0) {
|
|
15717
16350
|
this.send({ type: "info", message: `No custom commands found in ${commandsDir}
|
|
15718
16351
|
Add .md files to create commands.` });
|
|
@@ -15732,7 +16365,7 @@ Add .md files to create commands.` });
|
|
|
15732
16365
|
// ── /plugins ────────────────────────────────────────────────────
|
|
15733
16366
|
case "plugins": {
|
|
15734
16367
|
const configDir = this.config.getConfigDir();
|
|
15735
|
-
const pluginsDir =
|
|
16368
|
+
const pluginsDir = join20(configDir, PLUGINS_DIR_NAME);
|
|
15736
16369
|
const pluginTools = this.toolRegistry.listPluginTools();
|
|
15737
16370
|
const lines = [`\u{1F50C} **Plugins:**`, `Dir: ${pluginsDir}`, ""];
|
|
15738
16371
|
if (pluginTools.length === 0) {
|
|
@@ -15929,11 +16562,11 @@ Add .md files to create commands.` });
|
|
|
15929
16562
|
}
|
|
15930
16563
|
memoryShow() {
|
|
15931
16564
|
const configDir = this.config.getConfigDir();
|
|
15932
|
-
const memPath =
|
|
16565
|
+
const memPath = join20(configDir, MEMORY_FILE_NAME);
|
|
15933
16566
|
let content = "";
|
|
15934
16567
|
try {
|
|
15935
|
-
if (
|
|
15936
|
-
content =
|
|
16568
|
+
if (existsSync26(memPath)) {
|
|
16569
|
+
content = readFileSync20(memPath, "utf-8");
|
|
15937
16570
|
}
|
|
15938
16571
|
} catch (err) {
|
|
15939
16572
|
process.stderr.write(`[web] Failed to read memory file: ${err instanceof Error ? err.message : err}
|
|
@@ -15947,11 +16580,11 @@ Add .md files to create commands.` });
|
|
|
15947
16580
|
}
|
|
15948
16581
|
memoryAdd(text) {
|
|
15949
16582
|
const configDir = this.config.getConfigDir();
|
|
15950
|
-
const memPath =
|
|
16583
|
+
const memPath = join20(configDir, MEMORY_FILE_NAME);
|
|
15951
16584
|
try {
|
|
15952
16585
|
mkdirSync11(configDir, { recursive: true });
|
|
15953
16586
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
|
|
15954
|
-
const previous =
|
|
16587
|
+
const previous = existsSync26(memPath) ? readFileSync20(memPath, "utf-8") : "";
|
|
15955
16588
|
const entry = `
|
|
15956
16589
|
- [${timestamp}] ${text}
|
|
15957
16590
|
`;
|
|
@@ -15963,7 +16596,7 @@ Add .md files to create commands.` });
|
|
|
15963
16596
|
}
|
|
15964
16597
|
memoryClear() {
|
|
15965
16598
|
const configDir = this.config.getConfigDir();
|
|
15966
|
-
const memPath =
|
|
16599
|
+
const memPath = join20(configDir, MEMORY_FILE_NAME);
|
|
15967
16600
|
try {
|
|
15968
16601
|
atomicWriteFileSync(memPath, "");
|
|
15969
16602
|
this.send({ type: "info", message: "\u{1F5D1}\uFE0F Persistent memory cleared." });
|
|
@@ -16197,8 +16830,8 @@ async function setupProxy(configProxy) {
|
|
|
16197
16830
|
}
|
|
16198
16831
|
|
|
16199
16832
|
// src/web/auth.ts
|
|
16200
|
-
import { existsSync as
|
|
16201
|
-
import { join as
|
|
16833
|
+
import { existsSync as existsSync27, readFileSync as readFileSync21, writeFileSync as writeFileSync3, mkdirSync as mkdirSync12, readdirSync as readdirSync12, copyFileSync } from "fs";
|
|
16834
|
+
import { join as join21 } from "path";
|
|
16202
16835
|
import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
|
|
16203
16836
|
var USERS_FILE = "users.json";
|
|
16204
16837
|
var TOKEN_EXPIRY_HOURS = 24;
|
|
@@ -16213,7 +16846,7 @@ var AuthManager = class {
|
|
|
16213
16846
|
db;
|
|
16214
16847
|
constructor(baseDir) {
|
|
16215
16848
|
this.baseDir = baseDir;
|
|
16216
|
-
this.usersFile =
|
|
16849
|
+
this.usersFile = join21(baseDir, USERS_FILE);
|
|
16217
16850
|
this.db = this.loadOrCreate();
|
|
16218
16851
|
}
|
|
16219
16852
|
// ── Public API ─────────────────────────────────────────────────
|
|
@@ -16242,9 +16875,9 @@ var AuthManager = class {
|
|
|
16242
16875
|
}
|
|
16243
16876
|
const salt = randomBytes(16).toString("hex");
|
|
16244
16877
|
const passwordHash = this.hashPassword(password, salt);
|
|
16245
|
-
const dataDir =
|
|
16246
|
-
const fullDataDir =
|
|
16247
|
-
mkdirSync12(
|
|
16878
|
+
const dataDir = join21(USERS_DIR, username);
|
|
16879
|
+
const fullDataDir = join21(this.baseDir, dataDir);
|
|
16880
|
+
mkdirSync12(join21(fullDataDir, "history"), { recursive: true });
|
|
16248
16881
|
const user = {
|
|
16249
16882
|
username,
|
|
16250
16883
|
passwordHash,
|
|
@@ -16359,7 +16992,7 @@ var AuthManager = class {
|
|
|
16359
16992
|
getUserDataDir(username) {
|
|
16360
16993
|
const user = this.db.users.find((u) => u.username === username);
|
|
16361
16994
|
if (!user) throw new Error(`User not found: ${username}`);
|
|
16362
|
-
return
|
|
16995
|
+
return join21(this.baseDir, user.dataDir);
|
|
16363
16996
|
}
|
|
16364
16997
|
/** List all usernames */
|
|
16365
16998
|
listUsers() {
|
|
@@ -16395,30 +17028,30 @@ var AuthManager = class {
|
|
|
16395
17028
|
const err = this.register(username, password);
|
|
16396
17029
|
if (err) return err;
|
|
16397
17030
|
const userDir = this.getUserDataDir(username);
|
|
16398
|
-
const globalConfig =
|
|
16399
|
-
if (
|
|
17031
|
+
const globalConfig = join21(this.baseDir, "config.json");
|
|
17032
|
+
if (existsSync27(globalConfig)) {
|
|
16400
17033
|
try {
|
|
16401
|
-
const content =
|
|
16402
|
-
writeFileSync3(
|
|
17034
|
+
const content = readFileSync21(globalConfig, "utf-8");
|
|
17035
|
+
writeFileSync3(join21(userDir, "config.json"), content, "utf-8");
|
|
16403
17036
|
} catch {
|
|
16404
17037
|
}
|
|
16405
17038
|
}
|
|
16406
|
-
const globalMemory =
|
|
16407
|
-
if (
|
|
17039
|
+
const globalMemory = join21(this.baseDir, "memory.md");
|
|
17040
|
+
if (existsSync27(globalMemory)) {
|
|
16408
17041
|
try {
|
|
16409
|
-
const content =
|
|
16410
|
-
writeFileSync3(
|
|
17042
|
+
const content = readFileSync21(globalMemory, "utf-8");
|
|
17043
|
+
writeFileSync3(join21(userDir, "memory.md"), content, "utf-8");
|
|
16411
17044
|
} catch {
|
|
16412
17045
|
}
|
|
16413
17046
|
}
|
|
16414
|
-
const globalHistory =
|
|
16415
|
-
if (
|
|
17047
|
+
const globalHistory = join21(this.baseDir, "history");
|
|
17048
|
+
if (existsSync27(globalHistory)) {
|
|
16416
17049
|
try {
|
|
16417
|
-
const files =
|
|
16418
|
-
const userHistory =
|
|
17050
|
+
const files = readdirSync12(globalHistory).filter((f) => f.endsWith(".json"));
|
|
17051
|
+
const userHistory = join21(userDir, "history");
|
|
16419
17052
|
for (const f of files) {
|
|
16420
17053
|
try {
|
|
16421
|
-
copyFileSync(
|
|
17054
|
+
copyFileSync(join21(globalHistory, f), join21(userHistory, f));
|
|
16422
17055
|
} catch {
|
|
16423
17056
|
}
|
|
16424
17057
|
}
|
|
@@ -16429,9 +17062,9 @@ var AuthManager = class {
|
|
|
16429
17062
|
}
|
|
16430
17063
|
// ── Private methods ────────────────────────────────────────────
|
|
16431
17064
|
loadOrCreate() {
|
|
16432
|
-
if (
|
|
17065
|
+
if (existsSync27(this.usersFile)) {
|
|
16433
17066
|
try {
|
|
16434
|
-
return JSON.parse(
|
|
17067
|
+
return JSON.parse(readFileSync21(this.usersFile, "utf-8"));
|
|
16435
17068
|
} catch {
|
|
16436
17069
|
}
|
|
16437
17070
|
}
|
|
@@ -16483,7 +17116,7 @@ function getModuleDir() {
|
|
|
16483
17116
|
}
|
|
16484
17117
|
} catch {
|
|
16485
17118
|
}
|
|
16486
|
-
return
|
|
17119
|
+
return resolve9(".");
|
|
16487
17120
|
}
|
|
16488
17121
|
async function startWebServer(options = {}) {
|
|
16489
17122
|
const port = options.port ?? 3e3;
|
|
@@ -16548,8 +17181,8 @@ async function startWebServer(options = {}) {
|
|
|
16548
17181
|
}
|
|
16549
17182
|
}
|
|
16550
17183
|
let skillManager = null;
|
|
16551
|
-
const skillsDir =
|
|
16552
|
-
if (
|
|
17184
|
+
const skillsDir = join22(config.getConfigDir(), SKILLS_DIR_NAME);
|
|
17185
|
+
if (existsSync28(skillsDir)) {
|
|
16553
17186
|
skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
|
|
16554
17187
|
skillManager.loadSkills();
|
|
16555
17188
|
const count = skillManager.listSkills().length;
|
|
@@ -16620,18 +17253,18 @@ async function startWebServer(options = {}) {
|
|
|
16620
17253
|
next();
|
|
16621
17254
|
};
|
|
16622
17255
|
const moduleDir = getModuleDir();
|
|
16623
|
-
let clientDir =
|
|
16624
|
-
if (!
|
|
16625
|
-
clientDir =
|
|
17256
|
+
let clientDir = join22(moduleDir, "web", "client");
|
|
17257
|
+
if (!existsSync28(clientDir)) {
|
|
17258
|
+
clientDir = join22(moduleDir, "client");
|
|
16626
17259
|
}
|
|
16627
|
-
if (!
|
|
16628
|
-
clientDir =
|
|
17260
|
+
if (!existsSync28(clientDir)) {
|
|
17261
|
+
clientDir = join22(moduleDir, "..", "..", "src", "web", "client");
|
|
16629
17262
|
}
|
|
16630
|
-
if (!
|
|
16631
|
-
clientDir =
|
|
17263
|
+
if (!existsSync28(clientDir)) {
|
|
17264
|
+
clientDir = join22(process.cwd(), "src", "web", "client");
|
|
16632
17265
|
}
|
|
16633
17266
|
console.log(` Static files: ${clientDir}`);
|
|
16634
|
-
app.use("/vendor", express.static(
|
|
17267
|
+
app.use("/vendor", express.static(join22(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
|
|
16635
17268
|
app.use(express.static(clientDir));
|
|
16636
17269
|
app.get("/api/status", (_req, res) => {
|
|
16637
17270
|
res.json({
|
|
@@ -16698,10 +17331,10 @@ async function startWebServer(options = {}) {
|
|
|
16698
17331
|
app.get("/api/files", requireAuth, (req, res) => {
|
|
16699
17332
|
const cwd = process.cwd();
|
|
16700
17333
|
const prefix = req.query.prefix || "";
|
|
16701
|
-
const targetDir =
|
|
17334
|
+
const targetDir = join22(cwd, prefix);
|
|
16702
17335
|
try {
|
|
16703
|
-
const canonicalTarget = realpathSync(
|
|
16704
|
-
const canonicalCwd = realpathSync(
|
|
17336
|
+
const canonicalTarget = realpathSync(resolve9(targetDir));
|
|
17337
|
+
const canonicalCwd = realpathSync(resolve9(cwd));
|
|
16705
17338
|
if (!canonicalTarget.startsWith(canonicalCwd + sep3) && canonicalTarget !== canonicalCwd) {
|
|
16706
17339
|
res.json({ files: [] });
|
|
16707
17340
|
return;
|
|
@@ -16712,10 +17345,10 @@ async function startWebServer(options = {}) {
|
|
|
16712
17345
|
}
|
|
16713
17346
|
try {
|
|
16714
17347
|
const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "dist-cjs", "release", "__pycache__", ".next", ".nuxt", "coverage", ".cache"]);
|
|
16715
|
-
const entries =
|
|
17348
|
+
const entries = readdirSync13(targetDir, { withFileTypes: true });
|
|
16716
17349
|
const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
|
|
16717
17350
|
name: e.name,
|
|
16718
|
-
path: relative4(cwd,
|
|
17351
|
+
path: relative4(cwd, join22(targetDir, e.name)).replace(/\\/g, "/"),
|
|
16719
17352
|
isDir: e.isDirectory()
|
|
16720
17353
|
}));
|
|
16721
17354
|
res.json({ files });
|
|
@@ -16751,8 +17384,8 @@ async function startWebServer(options = {}) {
|
|
|
16751
17384
|
try {
|
|
16752
17385
|
const authUser = req._authUser;
|
|
16753
17386
|
const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
|
|
16754
|
-
const filePath =
|
|
16755
|
-
if (!
|
|
17387
|
+
const filePath = join22(histDir, `${id}.json`);
|
|
17388
|
+
if (!existsSync28(filePath)) {
|
|
16756
17389
|
res.status(404).json({ error: "Session not found" });
|
|
16757
17390
|
return;
|
|
16758
17391
|
}
|
|
@@ -16767,7 +17400,7 @@ async function startWebServer(options = {}) {
|
|
|
16767
17400
|
res.status(404).json({ error: "Session not found" });
|
|
16768
17401
|
return;
|
|
16769
17402
|
}
|
|
16770
|
-
const data = JSON.parse(
|
|
17403
|
+
const data = JSON.parse(readFileSync22(filePath, "utf-8"));
|
|
16771
17404
|
res.json({ session: data });
|
|
16772
17405
|
} catch (err) {
|
|
16773
17406
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
@@ -16780,10 +17413,10 @@ async function startWebServer(options = {}) {
|
|
|
16780
17413
|
return;
|
|
16781
17414
|
}
|
|
16782
17415
|
const cwd = process.cwd();
|
|
16783
|
-
const fullPath =
|
|
17416
|
+
const fullPath = resolve9(join22(cwd, filePath));
|
|
16784
17417
|
try {
|
|
16785
17418
|
const canonicalFull = realpathSync(fullPath);
|
|
16786
|
-
const canonicalCwd = realpathSync(
|
|
17419
|
+
const canonicalCwd = realpathSync(resolve9(cwd));
|
|
16787
17420
|
if (!canonicalFull.startsWith(canonicalCwd + sep3) && canonicalFull !== canonicalCwd) {
|
|
16788
17421
|
res.json({ error: "Access denied" });
|
|
16789
17422
|
return;
|
|
@@ -16798,7 +17431,7 @@ async function startWebServer(options = {}) {
|
|
|
16798
17431
|
res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
|
|
16799
17432
|
return;
|
|
16800
17433
|
}
|
|
16801
|
-
const content =
|
|
17434
|
+
const content = readFileSync22(fullPath, "utf-8");
|
|
16802
17435
|
res.json({ content, size: stat.size });
|
|
16803
17436
|
} catch {
|
|
16804
17437
|
res.json({ error: "Cannot read file" });
|
|
@@ -17002,7 +17635,7 @@ async function startWebServer(options = {}) {
|
|
|
17002
17635
|
});
|
|
17003
17636
|
const MAX_PORT_ATTEMPTS = 10;
|
|
17004
17637
|
let actualPort = port;
|
|
17005
|
-
const result = await new Promise((
|
|
17638
|
+
const result = await new Promise((resolve10, reject) => {
|
|
17006
17639
|
const tryListen = (attempt) => {
|
|
17007
17640
|
server.once("error", (err) => {
|
|
17008
17641
|
if (err.code === "EADDRINUSE" && attempt < MAX_PORT_ATTEMPTS) {
|
|
@@ -17033,7 +17666,7 @@ async function startWebServer(options = {}) {
|
|
|
17033
17666
|
}
|
|
17034
17667
|
console.log(` Press Ctrl+C to stop
|
|
17035
17668
|
`);
|
|
17036
|
-
|
|
17669
|
+
resolve10({ port: actualPort, host, url });
|
|
17037
17670
|
});
|
|
17038
17671
|
};
|
|
17039
17672
|
tryListen(1);
|
|
@@ -17053,17 +17686,17 @@ function resolveProjectMcpPath() {
|
|
|
17053
17686
|
const cwd = process.cwd();
|
|
17054
17687
|
const gitRoot = getGitRoot(cwd);
|
|
17055
17688
|
const projectRoot = gitRoot ?? cwd;
|
|
17056
|
-
const configPath =
|
|
17057
|
-
return
|
|
17689
|
+
const configPath = join22(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
17690
|
+
return existsSync28(configPath) ? configPath : null;
|
|
17058
17691
|
}
|
|
17059
17692
|
function loadProjectMcpConfig() {
|
|
17060
17693
|
const cwd = process.cwd();
|
|
17061
17694
|
const gitRoot = getGitRoot(cwd);
|
|
17062
17695
|
const projectRoot = gitRoot ?? cwd;
|
|
17063
|
-
const configPath =
|
|
17064
|
-
if (!
|
|
17696
|
+
const configPath = join22(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
17697
|
+
if (!existsSync28(configPath)) return null;
|
|
17065
17698
|
try {
|
|
17066
|
-
const raw = JSON.parse(
|
|
17699
|
+
const raw = JSON.parse(readFileSync22(configPath, "utf-8"));
|
|
17067
17700
|
return raw.mcpServers ?? raw;
|
|
17068
17701
|
} catch {
|
|
17069
17702
|
return null;
|