jinzd-ai-cli 0.4.15 → 0.4.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-HOOMQACE.js → chunk-NLT5FT2W.js} +202 -78
- package/dist/{chunk-KYWU74D5.js → chunk-OBFFL5DJ.js} +81 -62
- package/dist/{hub-34MLKYPL.js → hub-WF6CNNUT.js} +1 -1
- package/dist/index.js +40 -20
- package/dist/{server-CL6Q5R2O.js → server-KSH5U7QY.js} +14 -4
- package/dist/{task-orchestrator-AFMHSW4N.js → task-orchestrator-FVBUXFLC.js} +1 -1
- package/package.json +1 -1
|
@@ -129,6 +129,61 @@ var UndoStack = class {
|
|
|
129
129
|
};
|
|
130
130
|
var undoStack = new UndoStack();
|
|
131
131
|
|
|
132
|
+
// src/core/errors.ts
|
|
133
|
+
var AiCliError = class extends Error {
|
|
134
|
+
constructor(message, options) {
|
|
135
|
+
super(message, options);
|
|
136
|
+
this.name = "AiCliError";
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
var ProviderError = class extends AiCliError {
|
|
140
|
+
constructor(providerId, message, cause) {
|
|
141
|
+
super(`[${providerId}] ${message}`, cause !== void 0 ? { cause } : void 0);
|
|
142
|
+
this.providerId = providerId;
|
|
143
|
+
this.name = "ProviderError";
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
var AuthError = class extends ProviderError {
|
|
147
|
+
constructor(providerId) {
|
|
148
|
+
super(providerId, "Invalid or missing API key. Run: ai-cli config");
|
|
149
|
+
this.name = "AuthError";
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
var RateLimitError = class extends ProviderError {
|
|
153
|
+
constructor(providerId) {
|
|
154
|
+
super(providerId, "Rate limit exceeded. Please wait before trying again.");
|
|
155
|
+
this.name = "RateLimitError";
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
var ConfigError = class extends AiCliError {
|
|
159
|
+
constructor(message) {
|
|
160
|
+
super(message);
|
|
161
|
+
this.name = "ConfigError";
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
var ProviderNotFoundError = class extends AiCliError {
|
|
165
|
+
constructor(providerId) {
|
|
166
|
+
super(
|
|
167
|
+
`Provider '${providerId}' is not configured. Run: ai-cli config`
|
|
168
|
+
);
|
|
169
|
+
this.name = "ProviderNotFoundError";
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
var ToolError = class extends AiCliError {
|
|
173
|
+
constructor(toolName, message, cause) {
|
|
174
|
+
super(`[${toolName}] ${message}`, cause !== void 0 ? { cause } : void 0);
|
|
175
|
+
this.toolName = toolName;
|
|
176
|
+
this.name = "ToolError";
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
var NetworkError = class extends AiCliError {
|
|
180
|
+
constructor(message, statusCode, cause) {
|
|
181
|
+
super(message, cause !== void 0 ? { cause } : void 0);
|
|
182
|
+
this.statusCode = statusCode;
|
|
183
|
+
this.name = "NetworkError";
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
132
187
|
// src/tools/builtin/bash.ts
|
|
133
188
|
var IS_WINDOWS = platform() === "win32";
|
|
134
189
|
var SHELL = IS_WINDOWS ? "powershell.exe" : process.env["SHELL"] ?? "/bin/bash";
|
|
@@ -174,7 +229,7 @@ Important rules:
|
|
|
174
229
|
const timeout = Math.min(Math.max(Number(args["timeout"] ?? 3e4), 1e3), MAX_TIMEOUT);
|
|
175
230
|
const cwdArg = args["cwd"] ? String(args["cwd"]) : void 0;
|
|
176
231
|
if (!command.trim()) {
|
|
177
|
-
throw new
|
|
232
|
+
throw new ToolError("bash", "command is required");
|
|
178
233
|
}
|
|
179
234
|
if (!existsSync2(persistentCwd)) {
|
|
180
235
|
const fallback = process.cwd();
|
|
@@ -188,7 +243,8 @@ Important rules:
|
|
|
188
243
|
if (cwdArg) {
|
|
189
244
|
const resolved = resolve(persistentCwd, cwdArg);
|
|
190
245
|
if (!existsSync2(resolved)) {
|
|
191
|
-
throw new
|
|
246
|
+
throw new ToolError(
|
|
247
|
+
"bash",
|
|
192
248
|
`cwd directory does not exist: "${resolved}". Create it first (e.g. mkdir -p "${resolved}") before specifying it as cwd.`
|
|
193
249
|
);
|
|
194
250
|
}
|
|
@@ -232,7 +288,8 @@ Important rules:
|
|
|
232
288
|
const stderr = IS_WINDOWS && Buffer.isBuffer(execErr.stderr) ? execErr.stderr.toString("utf-8").trim() : execErr.stderr?.toString().trim() ?? "";
|
|
233
289
|
const stdout = IS_WINDOWS && Buffer.isBuffer(execErr.stdout) ? execErr.stdout.toString("utf-8").trim() : execErr.stdout?.toString().trim() ?? "";
|
|
234
290
|
const combined = [stdout, stderr].filter(Boolean).join("\n");
|
|
235
|
-
throw new
|
|
291
|
+
throw new ToolError(
|
|
292
|
+
"bash",
|
|
236
293
|
`Exit code ${execErr.status}:
|
|
237
294
|
${combined || (execErr.message ?? "Unknown error")}
|
|
238
295
|
|
|
@@ -479,12 +536,13 @@ var readFileTool = {
|
|
|
479
536
|
async execute(args) {
|
|
480
537
|
const filePath = String(args["path"] ?? "");
|
|
481
538
|
const encoding = args["encoding"] ?? "utf-8";
|
|
482
|
-
if (!filePath) throw new
|
|
539
|
+
if (!filePath) throw new ToolError("read_file", "path is required");
|
|
483
540
|
const normalizedPath = resolve2(filePath);
|
|
484
541
|
if (!existsSync3(normalizedPath)) {
|
|
485
542
|
const suggestions = findSimilarFiles(filePath);
|
|
486
543
|
if (suggestions.length > 0) {
|
|
487
|
-
throw new
|
|
544
|
+
throw new ToolError(
|
|
545
|
+
"read_file",
|
|
488
546
|
`File not found: ${filePath}
|
|
489
547
|
Current working directory: ${process.cwd()}
|
|
490
548
|
Found similar files, did you mean:
|
|
@@ -492,7 +550,8 @@ Found similar files, did you mean:
|
|
|
492
550
|
Please retry with the correct relative path.`
|
|
493
551
|
);
|
|
494
552
|
}
|
|
495
|
-
throw new
|
|
553
|
+
throw new ToolError(
|
|
554
|
+
"read_file",
|
|
496
555
|
`File not found: ${filePath}
|
|
497
556
|
Current working directory: ${process.cwd()}
|
|
498
557
|
Please use list_dir to verify the file path and retry.`
|
|
@@ -535,14 +594,8 @@ Suggestion: read existing text versions (.md / .txt) in the project, or install
|
|
|
535
594
|
return `[Binary file: ${filePath} (${ext})]
|
|
536
595
|
This is a binary file and cannot be read as text. If there is a text version (.md / .txt) in the project, please read that instead.`;
|
|
537
596
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
if (fstat.size > MAX_FILE_BYTES) {
|
|
541
|
-
return `[File too large: ${filePath} | ${(fstat.size / 1024 / 1024).toFixed(1)} MB exceeds ${MAX_FILE_BYTES / 1024 / 1024} MB limit]`;
|
|
542
|
-
}
|
|
543
|
-
} catch {
|
|
544
|
-
}
|
|
545
|
-
const buf = readFileSync2(normalizedPath);
|
|
597
|
+
const { readFile: readFile2 } = await import("fs/promises");
|
|
598
|
+
const buf = size > 1048576 ? await readFile2(normalizedPath) : readFileSync2(normalizedPath);
|
|
546
599
|
if (encoding === "base64") {
|
|
547
600
|
return `[File: ${filePath} | base64]
|
|
548
601
|
|
|
@@ -599,7 +652,7 @@ Important: For long content (over 500 lines or 3000 chars), you MUST split into
|
|
|
599
652
|
const content = String(args["content"] ?? "");
|
|
600
653
|
const encoding = args["encoding"] ?? "utf-8";
|
|
601
654
|
const appendMode = String(args["append"] ?? "false").toLowerCase() === "true";
|
|
602
|
-
if (!filePath) throw new
|
|
655
|
+
if (!filePath) throw new ToolError("write_file", "path is required");
|
|
603
656
|
undoStack.push(filePath, `write_file${appendMode ? " (append)" : ""}: ${filePath}`);
|
|
604
657
|
mkdirSync(dirname2(filePath), { recursive: true });
|
|
605
658
|
if (appendMode) {
|
|
@@ -739,15 +792,15 @@ Note: Path can be absolute or relative to the current working directory.`,
|
|
|
739
792
|
async execute(args) {
|
|
740
793
|
const filePath = String(args["path"] ?? "");
|
|
741
794
|
const encoding = args["encoding"] ?? "utf-8";
|
|
742
|
-
if (!filePath) throw new
|
|
743
|
-
if (!existsSync4(filePath)) throw new
|
|
795
|
+
if (!filePath) throw new ToolError("edit_file", "path is required");
|
|
796
|
+
if (!existsSync4(filePath)) throw new ToolError("edit_file", `File not found: ${filePath}`);
|
|
744
797
|
const original = readFileSync3(filePath, encoding);
|
|
745
798
|
if (args["old_str"] !== void 0) {
|
|
746
799
|
const oldStr = String(args["old_str"]);
|
|
747
800
|
const newStr = String(args["new_str"] ?? "");
|
|
748
801
|
const ignoreWs = Boolean(args["ignore_whitespace"]);
|
|
749
802
|
const replaceAll = Boolean(args["replace_all"]);
|
|
750
|
-
if (oldStr === "") throw new
|
|
803
|
+
if (oldStr === "") throw new ToolError("edit_file", "old_str cannot be empty");
|
|
751
804
|
if (ignoreWs) {
|
|
752
805
|
const fileLines = original.split("\n");
|
|
753
806
|
const searchLines = oldStr.split("\n");
|
|
@@ -825,7 +878,7 @@ Tip: You can also try ignore_whitespace: true to match ignoring indentation diff
|
|
|
825
878
|
const content = String(args["insert_content"] ?? "");
|
|
826
879
|
const lines = original.split("\n");
|
|
827
880
|
if (afterLine < 0 || afterLine > lines.length) {
|
|
828
|
-
throw new
|
|
881
|
+
throw new ToolError("edit_file", `insert_after_line ${afterLine} is out of range (file has ${lines.length} lines)`);
|
|
829
882
|
}
|
|
830
883
|
undoStack.push(filePath, `edit_file (insert): ${filePath}`);
|
|
831
884
|
lines.splice(afterLine, 0, content);
|
|
@@ -837,7 +890,8 @@ Tip: You can also try ignore_whitespace: true to match ignoring indentation diff
|
|
|
837
890
|
const toLine = Number(args["delete_to_line"] ?? args["delete_from_line"]);
|
|
838
891
|
const lines = original.split("\n");
|
|
839
892
|
if (fromLine < 1 || toLine < fromLine || toLine > lines.length) {
|
|
840
|
-
throw new
|
|
893
|
+
throw new ToolError(
|
|
894
|
+
"edit_file",
|
|
841
895
|
`Invalid line range: ${fromLine}-${toLine} (file has ${lines.length} lines, lines are 1-indexed)`
|
|
842
896
|
);
|
|
843
897
|
}
|
|
@@ -846,7 +900,8 @@ Tip: You can also try ignore_whitespace: true to match ignoring indentation diff
|
|
|
846
900
|
writeFileSync3(filePath, lines.join("\n"), encoding);
|
|
847
901
|
return `Successfully deleted lines ${fromLine}-${toLine} (${deleted.length} lines) from ${filePath}`;
|
|
848
902
|
}
|
|
849
|
-
throw new
|
|
903
|
+
throw new ToolError(
|
|
904
|
+
"edit_file",
|
|
850
905
|
"No operation specified. Provide either: (old_str + new_str) for replace, (insert_after_line + insert_content) for insert, or (delete_from_line + delete_to_line) for delete."
|
|
851
906
|
);
|
|
852
907
|
}
|
|
@@ -894,7 +949,8 @@ var listDirTool = {
|
|
|
894
949
|
} catch {
|
|
895
950
|
}
|
|
896
951
|
if (suggestions.length > 0) {
|
|
897
|
-
throw new
|
|
952
|
+
throw new ToolError(
|
|
953
|
+
"list_dir",
|
|
898
954
|
`Directory not found: ${dirPath}
|
|
899
955
|
Current working directory: ${cwd}
|
|
900
956
|
Found similar directories:
|
|
@@ -902,7 +958,8 @@ Found similar directories:
|
|
|
902
958
|
Please retry with the correct relative path.`
|
|
903
959
|
);
|
|
904
960
|
}
|
|
905
|
-
throw new
|
|
961
|
+
throw new ToolError(
|
|
962
|
+
"list_dir",
|
|
906
963
|
`Directory not found: ${dirPath}
|
|
907
964
|
Current working directory: ${cwd}
|
|
908
965
|
Please use list_dir (without path) to see the current directory structure first.`
|
|
@@ -958,6 +1015,7 @@ function formatSize(bytes) {
|
|
|
958
1015
|
|
|
959
1016
|
// src/tools/builtin/grep-files.ts
|
|
960
1017
|
import { readdirSync as readdirSync4, readFileSync as readFileSync4, statSync as statSync4, existsSync as existsSync6 } from "fs";
|
|
1018
|
+
import { readFile } from "fs/promises";
|
|
961
1019
|
import { join as join2, relative } from "path";
|
|
962
1020
|
var grepFilesTool = {
|
|
963
1021
|
definition: {
|
|
@@ -1008,11 +1066,11 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
|
|
|
1008
1066
|
const ignoreCase = Boolean(args["ignore_case"] ?? false);
|
|
1009
1067
|
const contextLines = Math.max(0, Number(args["context_lines"] ?? 0));
|
|
1010
1068
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 50));
|
|
1011
|
-
if (!pattern) throw new
|
|
1012
|
-
if (!existsSync6(rootPath)) throw new
|
|
1069
|
+
if (!pattern) throw new ToolError("grep_files", "pattern is required");
|
|
1070
|
+
if (!existsSync6(rootPath)) throw new ToolError("grep_files", `Path not found: ${rootPath}`);
|
|
1013
1071
|
const MAX_PATTERN_LENGTH = 1e3;
|
|
1014
1072
|
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
1015
|
-
throw new
|
|
1073
|
+
throw new ToolError("grep_files", `Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`);
|
|
1016
1074
|
}
|
|
1017
1075
|
let regex;
|
|
1018
1076
|
try {
|
|
@@ -1026,7 +1084,22 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
|
|
|
1026
1084
|
if (stat.isFile()) {
|
|
1027
1085
|
searchInFile(rootPath, rootPath, regex, contextLines, maxResults, results);
|
|
1028
1086
|
} else {
|
|
1029
|
-
|
|
1087
|
+
const filePaths = collectFilePaths(rootPath, filePattern, maxResults);
|
|
1088
|
+
const BATCH_SIZE = 16;
|
|
1089
|
+
for (let i = 0; i < filePaths.length && results.length < maxResults; i += BATCH_SIZE) {
|
|
1090
|
+
const batch = filePaths.slice(i, i + BATCH_SIZE);
|
|
1091
|
+
const batchResults = await Promise.all(
|
|
1092
|
+
batch.map(
|
|
1093
|
+
({ fullPath, relPath }) => searchInFileAsync(fullPath, relPath, regex, contextLines, maxResults - results.length)
|
|
1094
|
+
)
|
|
1095
|
+
);
|
|
1096
|
+
for (const br of batchResults) {
|
|
1097
|
+
for (const r of br) {
|
|
1098
|
+
if (results.length >= maxResults) break;
|
|
1099
|
+
results.push(r);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1030
1103
|
}
|
|
1031
1104
|
if (results.length === 0) {
|
|
1032
1105
|
return `No matches found for pattern: ${pattern}
|
|
@@ -1074,27 +1147,69 @@ function isBinary(filename) {
|
|
|
1074
1147
|
const ext = filename.slice(filename.lastIndexOf(".")).toLowerCase();
|
|
1075
1148
|
return BINARY_EXTS.has(ext);
|
|
1076
1149
|
}
|
|
1077
|
-
function
|
|
1078
|
-
|
|
1079
|
-
|
|
1150
|
+
function collectFilePaths(rootPath, filePattern, maxFiles) {
|
|
1151
|
+
const paths = [];
|
|
1152
|
+
function walk(dirPath) {
|
|
1153
|
+
if (paths.length >= maxFiles) return;
|
|
1154
|
+
let entries;
|
|
1155
|
+
try {
|
|
1156
|
+
entries = readdirSync4(dirPath, { withFileTypes: true });
|
|
1157
|
+
} catch {
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
for (const entry of entries) {
|
|
1161
|
+
if (paths.length >= maxFiles) return;
|
|
1162
|
+
if (entry.isDirectory()) {
|
|
1163
|
+
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
1164
|
+
walk(join2(dirPath, entry.name));
|
|
1165
|
+
} else if (entry.isFile()) {
|
|
1166
|
+
if (isBinary(entry.name)) continue;
|
|
1167
|
+
if (filePattern && !matchesFilePattern(entry.name, filePattern)) continue;
|
|
1168
|
+
const fullPath = join2(dirPath, entry.name);
|
|
1169
|
+
try {
|
|
1170
|
+
if (statSync4(fullPath).size > 1e6) continue;
|
|
1171
|
+
} catch {
|
|
1172
|
+
continue;
|
|
1173
|
+
}
|
|
1174
|
+
paths.push({ fullPath, relPath: relative(rootPath, fullPath) });
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
walk(rootPath);
|
|
1179
|
+
return paths;
|
|
1180
|
+
}
|
|
1181
|
+
async function searchInFileAsync(fullPath, displayPath, regex, contextLines, maxResults) {
|
|
1182
|
+
let content;
|
|
1080
1183
|
try {
|
|
1081
|
-
|
|
1184
|
+
content = await readFile(fullPath, "utf-8");
|
|
1082
1185
|
} catch {
|
|
1083
|
-
return;
|
|
1186
|
+
return [];
|
|
1084
1187
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1188
|
+
const results = [];
|
|
1189
|
+
const lines = content.split("\n");
|
|
1190
|
+
regex.lastIndex = 0;
|
|
1191
|
+
for (let i = 0; i < lines.length && results.length < maxResults; i++) {
|
|
1192
|
+
regex.lastIndex = 0;
|
|
1193
|
+
if (regex.test(lines[i])) {
|
|
1194
|
+
const match = {
|
|
1195
|
+
file: displayPath.replace(/\\/g, "/"),
|
|
1196
|
+
lineNumber: i + 1,
|
|
1197
|
+
lineText: lines[i]
|
|
1198
|
+
};
|
|
1199
|
+
if (contextLines > 0) {
|
|
1200
|
+
match.contextBefore = [];
|
|
1201
|
+
for (let j = Math.max(0, i - contextLines); j < i; j++) {
|
|
1202
|
+
match.contextBefore.push([j + 1, lines[j]]);
|
|
1203
|
+
}
|
|
1204
|
+
match.contextAfter = [];
|
|
1205
|
+
for (let j = i + 1; j <= Math.min(lines.length - 1, i + contextLines); j++) {
|
|
1206
|
+
match.contextAfter.push([j + 1, lines[j]]);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
results.push(match);
|
|
1096
1210
|
}
|
|
1097
1211
|
}
|
|
1212
|
+
return results;
|
|
1098
1213
|
}
|
|
1099
1214
|
function searchInFile(fullPath, displayPath, regex, contextLines, maxResults, results) {
|
|
1100
1215
|
try {
|
|
@@ -1169,8 +1284,8 @@ Results sorted by most recent modification time. Automatically skips node_module
|
|
|
1169
1284
|
const pattern = String(args["pattern"] ?? "");
|
|
1170
1285
|
const rootPath = String(args["path"] ?? process.cwd());
|
|
1171
1286
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 100));
|
|
1172
|
-
if (!pattern) throw new
|
|
1173
|
-
if (!existsSync7(rootPath)) throw new
|
|
1287
|
+
if (!pattern) throw new ToolError("glob_files", "pattern is required");
|
|
1288
|
+
if (!existsSync7(rootPath)) throw new ToolError("glob_files", `Path not found: ${rootPath}`);
|
|
1174
1289
|
const regex = globToRegex(pattern);
|
|
1175
1290
|
const matches = [];
|
|
1176
1291
|
collectMatchingFiles(rootPath, rootPath, regex, matches, maxResults);
|
|
@@ -1319,7 +1434,7 @@ var runInteractiveTool = {
|
|
|
1319
1434
|
})() : [];
|
|
1320
1435
|
const timeout = Math.min(Math.max(Number(args["timeout"] ?? 2e4), 1e3), 3e5);
|
|
1321
1436
|
if (!executable) {
|
|
1322
|
-
throw new
|
|
1437
|
+
throw new ToolError("run_interactive", "executable is required");
|
|
1323
1438
|
}
|
|
1324
1439
|
const env = {
|
|
1325
1440
|
...process.env,
|
|
@@ -1461,7 +1576,7 @@ async function resolveAndCheck(hostname) {
|
|
|
1461
1576
|
try {
|
|
1462
1577
|
const { address } = await dnsPromises.lookup(h);
|
|
1463
1578
|
if (isPrivateHost(address)) {
|
|
1464
|
-
throw new
|
|
1579
|
+
throw new NetworkError(`Blocked: "${hostname}" resolves to private address ${address}. web_fetch is restricted to public URLs.`);
|
|
1465
1580
|
}
|
|
1466
1581
|
} catch (e) {
|
|
1467
1582
|
if (e.message.startsWith("Blocked:")) throw e;
|
|
@@ -1488,17 +1603,17 @@ var webFetchTool = {
|
|
|
1488
1603
|
const url = String(args["url"] ?? "").trim();
|
|
1489
1604
|
const selector = args["selector"] ? String(args["selector"]).trim() : "";
|
|
1490
1605
|
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
|
1491
|
-
throw new
|
|
1606
|
+
throw new NetworkError(`Invalid URL: "${url}". URL must start with http:// or https://`);
|
|
1492
1607
|
}
|
|
1493
1608
|
try {
|
|
1494
1609
|
const parsedUrl = new URL(url);
|
|
1495
1610
|
if (isPrivateHost(parsedUrl.hostname)) {
|
|
1496
|
-
throw new
|
|
1611
|
+
throw new NetworkError(`Blocked: "${url}" resolves to a private/internal address. web_fetch is restricted to public URLs.`);
|
|
1497
1612
|
}
|
|
1498
1613
|
await resolveAndCheck(parsedUrl.hostname);
|
|
1499
1614
|
} catch (e) {
|
|
1500
1615
|
if (e.message.startsWith("Blocked:")) throw e;
|
|
1501
|
-
throw new
|
|
1616
|
+
throw new NetworkError(`Invalid URL: "${url}"`);
|
|
1502
1617
|
}
|
|
1503
1618
|
const controller = new AbortController();
|
|
1504
1619
|
const timeoutId = setTimeout(() => controller.abort(), 2e4);
|
|
@@ -1517,7 +1632,7 @@ var webFetchTool = {
|
|
|
1517
1632
|
for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
|
|
1518
1633
|
const parsedHop = new URL(currentUrl);
|
|
1519
1634
|
if (isPrivateHost(parsedHop.hostname)) {
|
|
1520
|
-
throw new
|
|
1635
|
+
throw new NetworkError(`Blocked: redirect to private/internal address "${currentUrl}".`);
|
|
1521
1636
|
}
|
|
1522
1637
|
await resolveAndCheck(parsedHop.hostname);
|
|
1523
1638
|
const r = await fetch(currentUrl, {
|
|
@@ -1528,7 +1643,7 @@ var webFetchTool = {
|
|
|
1528
1643
|
});
|
|
1529
1644
|
if (r.status >= 300 && r.status < 400) {
|
|
1530
1645
|
if (hop >= MAX_REDIRECTS) {
|
|
1531
|
-
throw new
|
|
1646
|
+
throw new NetworkError(`Too many redirects (>${MAX_REDIRECTS}): ${url}`);
|
|
1532
1647
|
}
|
|
1533
1648
|
const location = r.headers.get("Location");
|
|
1534
1649
|
if (!location) {
|
|
@@ -1542,18 +1657,18 @@ var webFetchTool = {
|
|
|
1542
1657
|
break;
|
|
1543
1658
|
}
|
|
1544
1659
|
clearTimeout(timeoutId);
|
|
1545
|
-
if (!resp) throw new
|
|
1660
|
+
if (!resp) throw new NetworkError(`Too many redirects (>${MAX_REDIRECTS}): ${url}`);
|
|
1546
1661
|
finalUrl = currentUrl;
|
|
1547
1662
|
contentType = resp.headers.get("content-type") ?? "";
|
|
1548
1663
|
if (!resp.ok) {
|
|
1549
|
-
throw new
|
|
1664
|
+
throw new NetworkError(`HTTP ${resp.status} ${resp.statusText}`, resp.status);
|
|
1550
1665
|
}
|
|
1551
1666
|
const buf = await resp.arrayBuffer();
|
|
1552
1667
|
rawHtml = new TextDecoder("utf-8", { fatal: false }).decode(buf.slice(0, 2e6));
|
|
1553
1668
|
} catch (err) {
|
|
1554
1669
|
clearTimeout(timeoutId);
|
|
1555
1670
|
if (err.name === "AbortError") {
|
|
1556
|
-
throw new
|
|
1671
|
+
throw new NetworkError(`Request timed out after 20s: ${url}`, void 0, err);
|
|
1557
1672
|
}
|
|
1558
1673
|
throw err;
|
|
1559
1674
|
}
|
|
@@ -1625,10 +1740,10 @@ var saveLastResponseTool = {
|
|
|
1625
1740
|
},
|
|
1626
1741
|
async execute(args) {
|
|
1627
1742
|
const filePath = String(args["path"] ?? "");
|
|
1628
|
-
if (!filePath) throw new
|
|
1743
|
+
if (!filePath) throw new ToolError("save_last_response", "path is required");
|
|
1629
1744
|
const content = lastResponseStore.content;
|
|
1630
1745
|
if (!content) {
|
|
1631
|
-
throw new
|
|
1746
|
+
throw new ToolError("save_last_response", "No content to save: AI has not produced any response yet, or the last response was empty.");
|
|
1632
1747
|
}
|
|
1633
1748
|
undoStack.push(filePath, `save_last_response: ${filePath}`);
|
|
1634
1749
|
mkdirSync2(dirname3(filePath), { recursive: true });
|
|
@@ -1665,7 +1780,7 @@ var saveMemoryTool = {
|
|
|
1665
1780
|
},
|
|
1666
1781
|
async execute(args) {
|
|
1667
1782
|
const content = String(args["content"] ?? "").trim();
|
|
1668
|
-
if (!content) throw new
|
|
1783
|
+
if (!content) throw new ToolError("save_memory", "content is required");
|
|
1669
1784
|
const memoryPath = getMemoryFilePath();
|
|
1670
1785
|
const configDir = join4(homedir2(), CONFIG_DIR_NAME);
|
|
1671
1786
|
if (!existsSync8(configDir)) {
|
|
@@ -1702,9 +1817,9 @@ var askUserTool = {
|
|
|
1702
1817
|
},
|
|
1703
1818
|
async execute(args) {
|
|
1704
1819
|
const question = String(args["question"] ?? "").trim();
|
|
1705
|
-
if (!question) throw new
|
|
1820
|
+
if (!question) throw new ToolError("ask_user", "question parameter is required");
|
|
1706
1821
|
if (!askUserContext.rl) {
|
|
1707
|
-
throw new
|
|
1822
|
+
throw new ToolError("ask_user", "readline not initialized \u2014 not available in this context");
|
|
1708
1823
|
}
|
|
1709
1824
|
const answer = await promptUser(askUserContext.rl, question);
|
|
1710
1825
|
if (answer === null) {
|
|
@@ -1769,30 +1884,30 @@ Valid statuses: pending, in_progress, completed.`,
|
|
|
1769
1884
|
let parsed;
|
|
1770
1885
|
if (typeof raw === "string") {
|
|
1771
1886
|
const trimmed = raw.trim();
|
|
1772
|
-
if (!trimmed) throw new
|
|
1887
|
+
if (!trimmed) throw new ToolError("write_todos", "todos parameter is required");
|
|
1773
1888
|
try {
|
|
1774
1889
|
parsed = JSON.parse(trimmed);
|
|
1775
1890
|
} catch (err) {
|
|
1776
|
-
throw new
|
|
1891
|
+
throw new ToolError("write_todos", `Invalid JSON in todos parameter: ${err.message}`, err);
|
|
1777
1892
|
}
|
|
1778
1893
|
} else if (Array.isArray(raw)) {
|
|
1779
1894
|
parsed = raw;
|
|
1780
1895
|
} else {
|
|
1781
|
-
throw new
|
|
1896
|
+
throw new ToolError("write_todos", "todos parameter must be a JSON array string");
|
|
1782
1897
|
}
|
|
1783
1898
|
if (!Array.isArray(parsed)) {
|
|
1784
|
-
throw new
|
|
1899
|
+
throw new ToolError("write_todos", "todos must be a JSON array");
|
|
1785
1900
|
}
|
|
1786
1901
|
const todos = parsed.map((item, i) => {
|
|
1787
1902
|
if (typeof item !== "object" || item === null) {
|
|
1788
|
-
throw new
|
|
1903
|
+
throw new ToolError("write_todos", `todos[${i}] must be an object`);
|
|
1789
1904
|
}
|
|
1790
1905
|
const obj = item;
|
|
1791
1906
|
const title = String(obj["title"] ?? "").trim();
|
|
1792
1907
|
const status = String(obj["status"] ?? "").trim();
|
|
1793
|
-
if (!title) throw new
|
|
1908
|
+
if (!title) throw new ToolError("write_todos", `todos[${i}].title is required`);
|
|
1794
1909
|
if (!VALID_STATUSES.has(status)) {
|
|
1795
|
-
throw new
|
|
1910
|
+
throw new ToolError("write_todos", `todos[${i}].status must be one of: pending, in_progress, completed (got "${status}")`);
|
|
1796
1911
|
}
|
|
1797
1912
|
return { title, status };
|
|
1798
1913
|
});
|
|
@@ -1907,7 +2022,7 @@ var googleSearchTool = {
|
|
|
1907
2022
|
},
|
|
1908
2023
|
async execute(args) {
|
|
1909
2024
|
const query = String(args["query"] ?? "").trim();
|
|
1910
|
-
if (!query) throw new
|
|
2025
|
+
if (!query) throw new ToolError("google_search", "query parameter is required");
|
|
1911
2026
|
const numResults = Math.min(
|
|
1912
2027
|
Math.max(Math.floor(Number(args["num_results"] ?? DEFAULT_RESULTS)), 1),
|
|
1913
2028
|
MAX_RESULTS
|
|
@@ -1931,24 +2046,26 @@ var googleSearchTool = {
|
|
|
1931
2046
|
if (!response.ok) {
|
|
1932
2047
|
const errorBody = await response.text().catch(() => "");
|
|
1933
2048
|
if (response.status === 403) {
|
|
1934
|
-
throw new
|
|
2049
|
+
throw new NetworkError(
|
|
1935
2050
|
`Google Search API 403 Forbidden \u2014 Invalid API Key or daily free quota exceeded (100/day).
|
|
1936
|
-
Please check your API Key and Search Engine ID configuration
|
|
2051
|
+
Please check your API Key and Search Engine ID configuration.`,
|
|
2052
|
+
403
|
|
1937
2053
|
);
|
|
1938
2054
|
}
|
|
1939
2055
|
if (response.status === 429) {
|
|
1940
|
-
throw new
|
|
2056
|
+
throw new NetworkError("Google Search API 429 \u2014 Too many requests, please try again later.", 429);
|
|
1941
2057
|
}
|
|
1942
|
-
throw new
|
|
2058
|
+
throw new NetworkError(
|
|
1943
2059
|
`Google Search API error: HTTP ${response.status} ${response.statusText}
|
|
1944
|
-
${errorBody.slice(0, 500)}
|
|
2060
|
+
${errorBody.slice(0, 500)}`,
|
|
2061
|
+
response.status
|
|
1945
2062
|
);
|
|
1946
2063
|
}
|
|
1947
2064
|
const data = await response.json();
|
|
1948
2065
|
return formatResults(query, data, numResults);
|
|
1949
2066
|
} catch (err) {
|
|
1950
2067
|
if (err instanceof Error && err.name === "AbortError") {
|
|
1951
|
-
throw new
|
|
2068
|
+
throw new NetworkError(`Google Search request timed out (${REQUEST_TIMEOUT_MS / 1e3}s). Please check your network or proxy configuration.`, void 0, err);
|
|
1952
2069
|
}
|
|
1953
2070
|
throw err;
|
|
1954
2071
|
} finally {
|
|
@@ -1967,12 +2084,14 @@ function resolveConfig() {
|
|
|
1967
2084
|
cx = EnvLoader.getGoogleSearchEngineId();
|
|
1968
2085
|
}
|
|
1969
2086
|
if (!apiKey) {
|
|
1970
|
-
throw new
|
|
2087
|
+
throw new ToolError(
|
|
2088
|
+
"google_search",
|
|
1971
2089
|
'Google Search API Key not configured.\nConfigure via one of:\n 1. Run /config \u2192 Configure Google Search\n 2. Set env var AICLI_API_KEY_GOOGLESEARCH\n 3. Add apiKeys["google-search"] to ~/.aicli/config.json'
|
|
1972
2090
|
);
|
|
1973
2091
|
}
|
|
1974
2092
|
if (!cx) {
|
|
1975
|
-
throw new
|
|
2093
|
+
throw new ToolError(
|
|
2094
|
+
"google_search",
|
|
1976
2095
|
"Google Search Engine ID (cx) not configured.\nConfigure via one of:\n 1. Run /config \u2192 Configure Google Search\n 2. Set env var AICLI_GOOGLE_CX\n 3. Add googleSearchEngineId to ~/.aicli/config.json\n\nGet one at: https://programmablesearchengine.google.com/ \u2192 Create search engine \u2192 Copy Search Engine ID"
|
|
1977
2096
|
);
|
|
1978
2097
|
}
|
|
@@ -2309,7 +2428,7 @@ var spawnAgentTool = {
|
|
|
2309
2428
|
},
|
|
2310
2429
|
async execute(args) {
|
|
2311
2430
|
const task = String(args["task"] ?? "").trim();
|
|
2312
|
-
if (!task) throw new
|
|
2431
|
+
if (!task) throw new ToolError("spawn_agent", "task parameter is required");
|
|
2313
2432
|
const rawMaxRounds = Number(args["max_rounds"] ?? SUBAGENT_DEFAULT_MAX_ROUNDS);
|
|
2314
2433
|
const maxRounds = Math.min(
|
|
2315
2434
|
Math.max(Math.round(rawMaxRounds), 1),
|
|
@@ -2317,7 +2436,7 @@ var spawnAgentTool = {
|
|
|
2317
2436
|
);
|
|
2318
2437
|
const ctx = spawnAgentContext;
|
|
2319
2438
|
if (!ctx.provider) {
|
|
2320
|
-
throw new
|
|
2439
|
+
throw new ToolError("spawn_agent", "provider not initialized (context not injected)");
|
|
2321
2440
|
}
|
|
2322
2441
|
const subRegistry = new ToolRegistry();
|
|
2323
2442
|
for (const tool of subRegistry.listAll()) {
|
|
@@ -2533,6 +2652,11 @@ var ToolRegistry = class {
|
|
|
2533
2652
|
|
|
2534
2653
|
export {
|
|
2535
2654
|
EnvLoader,
|
|
2655
|
+
ProviderError,
|
|
2656
|
+
AuthError,
|
|
2657
|
+
RateLimitError,
|
|
2658
|
+
ConfigError,
|
|
2659
|
+
ProviderNotFoundError,
|
|
2536
2660
|
isFileWriteTool,
|
|
2537
2661
|
getDangerLevel,
|
|
2538
2662
|
schemaToJsonSchema,
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
AuthError,
|
|
4
|
+
ConfigError,
|
|
3
5
|
EnvLoader,
|
|
6
|
+
ProviderError,
|
|
7
|
+
ProviderNotFoundError,
|
|
8
|
+
RateLimitError,
|
|
4
9
|
schemaToJsonSchema
|
|
5
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-NLT5FT2W.js";
|
|
6
11
|
import {
|
|
7
12
|
APP_NAME,
|
|
8
13
|
CONFIG_DIR_NAME,
|
|
@@ -152,47 +157,6 @@ var ConfigSchema = z.object({
|
|
|
152
157
|
allowPlugins: z.boolean().default(false)
|
|
153
158
|
});
|
|
154
159
|
|
|
155
|
-
// src/core/errors.ts
|
|
156
|
-
var AiCliError = class extends Error {
|
|
157
|
-
constructor(message, options) {
|
|
158
|
-
super(message, options);
|
|
159
|
-
this.name = "AiCliError";
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
var ProviderError = class extends AiCliError {
|
|
163
|
-
constructor(providerId, message, cause) {
|
|
164
|
-
super(`[${providerId}] ${message}`, cause !== void 0 ? { cause } : void 0);
|
|
165
|
-
this.providerId = providerId;
|
|
166
|
-
this.name = "ProviderError";
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
var AuthError = class extends ProviderError {
|
|
170
|
-
constructor(providerId) {
|
|
171
|
-
super(providerId, "Invalid or missing API key. Run: ai-cli config");
|
|
172
|
-
this.name = "AuthError";
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
var RateLimitError = class extends ProviderError {
|
|
176
|
-
constructor(providerId) {
|
|
177
|
-
super(providerId, "Rate limit exceeded. Please wait before trying again.");
|
|
178
|
-
this.name = "RateLimitError";
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
var ConfigError = class extends AiCliError {
|
|
182
|
-
constructor(message) {
|
|
183
|
-
super(message);
|
|
184
|
-
this.name = "ConfigError";
|
|
185
|
-
}
|
|
186
|
-
};
|
|
187
|
-
var ProviderNotFoundError = class extends AiCliError {
|
|
188
|
-
constructor(providerId) {
|
|
189
|
-
super(
|
|
190
|
-
`Provider '${providerId}' is not configured. Run: ai-cli config`
|
|
191
|
-
);
|
|
192
|
-
this.name = "ProviderNotFoundError";
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
|
-
|
|
196
160
|
// src/config/config-manager.ts
|
|
197
161
|
var ConfigManager = class {
|
|
198
162
|
configDir;
|
|
@@ -978,10 +942,13 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
978
942
|
}
|
|
979
943
|
wrapError(err) {
|
|
980
944
|
if (err instanceof Error) {
|
|
981
|
-
|
|
945
|
+
const msg = err.message;
|
|
946
|
+
const statusMatch = msg.match(/\b(4\d{2}|5\d{2})\b/);
|
|
947
|
+
const status = statusMatch ? Number(statusMatch[1]) : void 0;
|
|
948
|
+
if (msg.includes("API key") || msg.includes("PERMISSION_DENIED") || status === 401 || status === 403) {
|
|
982
949
|
return new AuthError("gemini");
|
|
983
950
|
}
|
|
984
|
-
if (
|
|
951
|
+
if (status === 429 || msg.includes("RESOURCE_EXHAUSTED") || msg.includes("quota")) {
|
|
985
952
|
return new ProviderError(
|
|
986
953
|
"gemini",
|
|
987
954
|
`Rate limit exceeded (429): The current model requires a paid plan or the free quota has been exhausted.
|
|
@@ -990,17 +957,17 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
990
957
|
err
|
|
991
958
|
);
|
|
992
959
|
}
|
|
993
|
-
if (
|
|
960
|
+
if (msg.includes("fetch failed") || msg.includes("ECONNREFUSED") || msg.includes("ETIMEDOUT") || msg.includes("ENOTFOUND") || msg.includes("socket hang up")) {
|
|
994
961
|
return new ProviderError(
|
|
995
962
|
"gemini",
|
|
996
|
-
`Network connection failed: ${
|
|
963
|
+
`Network connection failed: ${msg}
|
|
997
964
|
Node.js does not automatically use system proxies. Try one of the following:
|
|
998
965
|
1. Set environment variable: set HTTPS_PROXY=http://127.0.0.1:<proxy-port>
|
|
999
966
|
2. Configure an accessible Gemini API mirror URL in config.json under customBaseUrls.gemini`,
|
|
1000
967
|
err
|
|
1001
968
|
);
|
|
1002
969
|
}
|
|
1003
|
-
return new ProviderError("gemini",
|
|
970
|
+
return new ProviderError("gemini", msg, err);
|
|
1004
971
|
}
|
|
1005
972
|
return new ProviderError("gemini", String(err));
|
|
1006
973
|
}
|
|
@@ -2083,7 +2050,7 @@ var ProviderRegistry = class {
|
|
|
2083
2050
|
};
|
|
2084
2051
|
|
|
2085
2052
|
// src/session/session-manager.ts
|
|
2086
|
-
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, unlinkSync, renameSync } from "fs";
|
|
2053
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, unlinkSync, renameSync, openSync, readSync, closeSync } from "fs";
|
|
2087
2054
|
import { join as join2 } from "path";
|
|
2088
2055
|
import { v4 as uuidv4 } from "uuid";
|
|
2089
2056
|
|
|
@@ -2289,6 +2256,11 @@ function safeDate(value) {
|
|
|
2289
2256
|
const d = new Date(value);
|
|
2290
2257
|
return isNaN(d.getTime()) ? /* @__PURE__ */ new Date(0) : d;
|
|
2291
2258
|
}
|
|
2259
|
+
function extractJsonField(header, field) {
|
|
2260
|
+
const re = new RegExp(`"${field}"\\s*:\\s*"([^"]*)"`, "i");
|
|
2261
|
+
const m = header.match(re);
|
|
2262
|
+
return m ? m[1] : void 0;
|
|
2263
|
+
}
|
|
2292
2264
|
var SessionManager = class {
|
|
2293
2265
|
constructor(config) {
|
|
2294
2266
|
this.config = config;
|
|
@@ -2333,18 +2305,8 @@ var SessionManager = class {
|
|
|
2333
2305
|
const metas = [];
|
|
2334
2306
|
for (const file of files) {
|
|
2335
2307
|
try {
|
|
2336
|
-
const
|
|
2337
|
-
|
|
2338
|
-
);
|
|
2339
|
-
metas.push({
|
|
2340
|
-
id: data.id,
|
|
2341
|
-
provider: data.provider,
|
|
2342
|
-
model: data.model,
|
|
2343
|
-
messageCount: data.messages?.length ?? 0,
|
|
2344
|
-
created: safeDate(data.created),
|
|
2345
|
-
updated: safeDate(data.updated),
|
|
2346
|
-
title: data.title
|
|
2347
|
-
});
|
|
2308
|
+
const meta = this.readSessionMeta(join2(this.historyDir, file));
|
|
2309
|
+
if (meta) metas.push(meta);
|
|
2348
2310
|
} catch (err) {
|
|
2349
2311
|
process.stderr.write(
|
|
2350
2312
|
`[Warning] Skipping corrupted session file "${file}": ${err instanceof Error ? err.message : String(err)}
|
|
@@ -2354,6 +2316,59 @@ var SessionManager = class {
|
|
|
2354
2316
|
}
|
|
2355
2317
|
return metas.sort((a, b) => b.updated.getTime() - a.updated.getTime());
|
|
2356
2318
|
}
|
|
2319
|
+
/**
|
|
2320
|
+
* P1-B: Read only the first ~1KB of a session file to extract metadata fields.
|
|
2321
|
+
* Session JSON format puts id/provider/model/created/updated/title before the
|
|
2322
|
+
* large "messages" array, so a small header read suffices for metadata extraction.
|
|
2323
|
+
* Falls back to full file read if header parsing fails.
|
|
2324
|
+
*/
|
|
2325
|
+
readSessionMeta(filePath) {
|
|
2326
|
+
const HEADER_SIZE = 1024;
|
|
2327
|
+
let header;
|
|
2328
|
+
try {
|
|
2329
|
+
const fd = openSync(filePath, "r");
|
|
2330
|
+
const buf = Buffer.alloc(HEADER_SIZE);
|
|
2331
|
+
const bytesRead = readSync(fd, buf, 0, HEADER_SIZE, 0);
|
|
2332
|
+
closeSync(fd);
|
|
2333
|
+
header = buf.toString("utf-8", 0, bytesRead);
|
|
2334
|
+
} catch {
|
|
2335
|
+
return null;
|
|
2336
|
+
}
|
|
2337
|
+
const id = extractJsonField(header, "id");
|
|
2338
|
+
const provider = extractJsonField(header, "provider");
|
|
2339
|
+
const model = extractJsonField(header, "model");
|
|
2340
|
+
const created = extractJsonField(header, "created");
|
|
2341
|
+
const updated = extractJsonField(header, "updated");
|
|
2342
|
+
const title = extractJsonField(header, "title");
|
|
2343
|
+
if (id && provider && model) {
|
|
2344
|
+
let messageCount = 0;
|
|
2345
|
+
try {
|
|
2346
|
+
const full = readFileSync2(filePath, "utf-8");
|
|
2347
|
+
const matches = full.match(/"role"\s*:/g);
|
|
2348
|
+
messageCount = matches ? matches.length : 0;
|
|
2349
|
+
} catch {
|
|
2350
|
+
}
|
|
2351
|
+
return {
|
|
2352
|
+
id,
|
|
2353
|
+
provider,
|
|
2354
|
+
model,
|
|
2355
|
+
messageCount,
|
|
2356
|
+
created: safeDate(created),
|
|
2357
|
+
updated: safeDate(updated),
|
|
2358
|
+
title: title || void 0
|
|
2359
|
+
};
|
|
2360
|
+
}
|
|
2361
|
+
const data = JSON.parse(readFileSync2(filePath, "utf-8"));
|
|
2362
|
+
return {
|
|
2363
|
+
id: data.id,
|
|
2364
|
+
provider: data.provider,
|
|
2365
|
+
model: data.model,
|
|
2366
|
+
messageCount: data.messages?.length ?? 0,
|
|
2367
|
+
created: safeDate(data.created),
|
|
2368
|
+
updated: safeDate(data.updated),
|
|
2369
|
+
title: data.title
|
|
2370
|
+
};
|
|
2371
|
+
}
|
|
2357
2372
|
deleteSession(id) {
|
|
2358
2373
|
const filePath = join2(this.historyDir, `${id}.json`);
|
|
2359
2374
|
if (!existsSync2(filePath)) return false;
|
|
@@ -2920,8 +2935,12 @@ var McpManager = class {
|
|
|
2920
2935
|
* 关闭所有 MCP 服务器连接。
|
|
2921
2936
|
*/
|
|
2922
2937
|
async closeAll() {
|
|
2923
|
-
const promises = [...this.clients.
|
|
2924
|
-
|
|
2938
|
+
const promises = [...this.clients.entries()].map(
|
|
2939
|
+
([id, c]) => c.close().catch((err) => {
|
|
2940
|
+
process.stderr.write(`[mcp] Failed to close ${id}: ${err instanceof Error ? err.message : err}
|
|
2941
|
+
`);
|
|
2942
|
+
})
|
|
2943
|
+
);
|
|
2925
2944
|
await Promise.allSettled(promises);
|
|
2926
2945
|
this.clients.clear();
|
|
2927
2946
|
}
|
|
@@ -381,7 +381,7 @@ ${content}`);
|
|
|
381
381
|
}
|
|
382
382
|
}
|
|
383
383
|
async function runTaskMode(config, providers, configManager, topic) {
|
|
384
|
-
const { TaskOrchestrator } = await import("./task-orchestrator-
|
|
384
|
+
const { TaskOrchestrator } = await import("./task-orchestrator-FVBUXFLC.js");
|
|
385
385
|
const orchestrator = new TaskOrchestrator(config, providers, configManager);
|
|
386
386
|
let interrupted = false;
|
|
387
387
|
const onSigint = () => {
|
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
saveDevState,
|
|
24
24
|
sessionHasMeaningfulContent,
|
|
25
25
|
setupProxy
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-OBFFL5DJ.js";
|
|
27
27
|
import {
|
|
28
28
|
ToolRegistry,
|
|
29
29
|
askUserContext,
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
theme,
|
|
39
39
|
truncateOutput,
|
|
40
40
|
undoStack
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-NLT5FT2W.js";
|
|
42
42
|
import {
|
|
43
43
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
44
44
|
AUTHOR,
|
|
@@ -442,8 +442,20 @@ var Renderer = class {
|
|
|
442
442
|
}
|
|
443
443
|
renderError(err) {
|
|
444
444
|
const message = err instanceof Error ? err.message : String(err);
|
|
445
|
+
const lines = [message];
|
|
446
|
+
if (err instanceof Error && err.cause) {
|
|
447
|
+
let cause = err.cause;
|
|
448
|
+
let depth = 0;
|
|
449
|
+
while (cause && depth < 3) {
|
|
450
|
+
const causeMsg = cause instanceof Error ? cause.message : String(cause);
|
|
451
|
+
lines.push(` Caused by: ${causeMsg}`);
|
|
452
|
+
cause = cause instanceof Error ? cause.cause : void 0;
|
|
453
|
+
depth++;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
const typeName = err instanceof Error && err.name !== "Error" ? ` [${err.name}]` : "";
|
|
445
457
|
console.error(theme.error(`
|
|
446
|
-
Error: ${
|
|
458
|
+
Error${typeName}: ${lines.join("\n")}
|
|
447
459
|
`));
|
|
448
460
|
}
|
|
449
461
|
/**
|
|
@@ -4158,21 +4170,27 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4158
4170
|
}
|
|
4159
4171
|
if (Object.keys(mergedMcpServers).length > 0) {
|
|
4160
4172
|
this.mcpManager = new McpManager();
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
+
const mcpProjectCount = Object.keys(projectMcpServers).length;
|
|
4174
|
+
this.mcpManager.connectAll(mergedMcpServers).then(() => {
|
|
4175
|
+
const mcpTools = this.mcpManager.getAllTools();
|
|
4176
|
+
for (const tool of mcpTools) {
|
|
4177
|
+
this.toolRegistry.registerMcpTool(tool);
|
|
4178
|
+
}
|
|
4179
|
+
const connectedCount = this.mcpManager.getConnectedCount();
|
|
4180
|
+
const totalTools = this.mcpManager.getTotalToolCount();
|
|
4181
|
+
if (connectedCount > 0) {
|
|
4182
|
+
const sourceInfo = mcpProjectCount > 0 ? ` (${mcpProjectCount} from .mcp.json)` : "";
|
|
4183
|
+
process.stdout.write(
|
|
4184
|
+
theme.dim(`
|
|
4185
|
+
\u{1F50C} MCP: ${connectedCount} server(s), ${totalTools} tool(s)${sourceInfo}
|
|
4173
4186
|
`)
|
|
4174
|
-
|
|
4175
|
-
|
|
4187
|
+
);
|
|
4188
|
+
this.showPrompt();
|
|
4189
|
+
}
|
|
4190
|
+
}).catch((err) => {
|
|
4191
|
+
process.stderr.write(`[mcp] connectAll error: ${err instanceof Error ? err.message : err}
|
|
4192
|
+
`);
|
|
4193
|
+
});
|
|
4176
4194
|
}
|
|
4177
4195
|
this.setupClipboardPaste();
|
|
4178
4196
|
this.rl.on("SIGINT", () => {
|
|
@@ -5436,7 +5454,9 @@ Tip: You can continue the conversation by asking the AI to proceed.`
|
|
|
5436
5454
|
if (sessionId) {
|
|
5437
5455
|
this.events.emit("session.end", { sessionId });
|
|
5438
5456
|
}
|
|
5439
|
-
this.mcpManager?.closeAll().catch(() => {
|
|
5457
|
+
this.mcpManager?.closeAll().catch((err) => {
|
|
5458
|
+
process.stderr.write(`[mcp] cleanup error: ${err instanceof Error ? err.message : err}
|
|
5459
|
+
`);
|
|
5440
5460
|
});
|
|
5441
5461
|
this.rl.close();
|
|
5442
5462
|
console.log(theme.dim("\nGoodbye!"));
|
|
@@ -5528,7 +5548,7 @@ program.command("web").description("Start Web UI server with browser-based chat
|
|
|
5528
5548
|
console.error("Error: Invalid port number. Must be between 1 and 65535.");
|
|
5529
5549
|
process.exit(1);
|
|
5530
5550
|
}
|
|
5531
|
-
const { startWebServer } = await import("./server-
|
|
5551
|
+
const { startWebServer } = await import("./server-KSH5U7QY.js");
|
|
5532
5552
|
await startWebServer({ port, host: options.host });
|
|
5533
5553
|
});
|
|
5534
5554
|
program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
|
|
@@ -5761,7 +5781,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
|
|
|
5761
5781
|
}),
|
|
5762
5782
|
config.get("customProviders")
|
|
5763
5783
|
);
|
|
5764
|
-
const { startHub } = await import("./hub-
|
|
5784
|
+
const { startHub } = await import("./hub-WF6CNNUT.js");
|
|
5765
5785
|
await startHub(
|
|
5766
5786
|
{
|
|
5767
5787
|
topic: topic ?? "",
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
renderDiff,
|
|
19
19
|
runHook,
|
|
20
20
|
setupProxy
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-OBFFL5DJ.js";
|
|
22
22
|
import {
|
|
23
23
|
AuthManager
|
|
24
24
|
} from "./chunk-BYNY5JPB.js";
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
spawnAgentContext,
|
|
33
33
|
truncateOutput,
|
|
34
34
|
undoStack
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-NLT5FT2W.js";
|
|
36
36
|
import {
|
|
37
37
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
38
38
|
AUTHOR,
|
|
@@ -658,7 +658,15 @@ var SessionHandler = class _SessionHandler {
|
|
|
658
658
|
await this.handleChatSimple(provider, session.messages);
|
|
659
659
|
}
|
|
660
660
|
} catch (err) {
|
|
661
|
-
const
|
|
661
|
+
const parts = [];
|
|
662
|
+
let current = err;
|
|
663
|
+
let depth = 0;
|
|
664
|
+
while (current && depth < 3) {
|
|
665
|
+
parts.push(current instanceof Error ? current.message : String(current));
|
|
666
|
+
current = current instanceof Error ? current.cause : void 0;
|
|
667
|
+
depth++;
|
|
668
|
+
}
|
|
669
|
+
const message = parts.join(" \u2192 Caused by: ");
|
|
662
670
|
this.send({ type: "error", message });
|
|
663
671
|
} finally {
|
|
664
672
|
this.processing = false;
|
|
@@ -1963,7 +1971,9 @@ Add .md files to create commands.` });
|
|
|
1963
1971
|
if (existsSync3(memPath)) {
|
|
1964
1972
|
content = readFileSync3(memPath, "utf-8");
|
|
1965
1973
|
}
|
|
1966
|
-
} catch {
|
|
1974
|
+
} catch (err) {
|
|
1975
|
+
process.stderr.write(`[web] Failed to read memory file: ${err instanceof Error ? err.message : err}
|
|
1976
|
+
`);
|
|
1967
1977
|
}
|
|
1968
1978
|
this.send({
|
|
1969
1979
|
type: "memory_content",
|